Files
django-maraudes/maraudes/management/commands/load_csv.py
artus40 be087464fc Remaster (#38)
* setup new 'statistiques' module

* added 'graphos' package and created first test graph

* put graphos in requirements, deleted local folder

* added "load_csv" management command !

* added update of premiere_rencontre field in 'load_csv' management command

* added missing urls.py file

* added 'merge' action and view

* added 'info_completed' ratio

* linked sujets:merge views inside suivi:details

* added link to maraudes:details in notes table headers, if any

* Major reorganisation, moved 'suivi' and 'sujets' to 'notes', cleanup in 'maraudes', dropping 'website' mixins (mostly useless)

* small cleanup

* worked on Maraude and Sujet lists

* corrected missing line in notes.__init__

* restored 'details' view for maraudes and sujets insie 'notes' module

* worked on 'notes': added navigation between maraude's compte-rendu, right content in details, header to list tables

* changed queryset for CompteRenduDetailsView to all notes of same date, minor layout changes

* added right content to 'details-sujet', created 'statistiques' view and update templates

* restored 'statistiques' ajax view in 'details-sujet', fixed 'merge_two' util function

* added auto-creation of FicheStatistique (plus some tests), pagination for notes in 'details-sujet'

* added error-prone cases in paginator

* fixed non-working modals, added titles

* added UpdateStatistiques capacity in CompteRenduCreate view

* fixed missing AjaxTemplateMixin for CreateSujetView, worked on compte-rendu creation scripts

* fixed MaraudeManager.all_of() for common Maraudeurs, added color hints in planning

* re-instated statistiques module link and first test page

* added FinalizeView to send a mail before finalizing compte-rendu

* Added PieChart view for FicheStatistique fields

* small style updates, added 'age' and 'genre' fields from sujets in statistiques.PieChartView

* worked on statistiques, fixed small issues in 'notes' list views

* small theme change

* removed some dead code

* fixed notes.tests, fixed statistiques.info_completed display, added filter in SujetLisView

* added some tests

* added customised admin templates

* added authenticate in CustomAuthenticatationBackend, more verbose login thanks to messages

* added django-nose for test coverage

* Corrected raising exception on first migration

On first migration, qs.exists() would previously be called and raising an Exception, sot he migrations would fail.

* Better try block

* cleaned up custom settings.py, added some overrides of django base_settings

* corrected bad dictionnary key
2017-06-11 17:16:17 +02:00

217 lines
9.6 KiB
Python

import datetime
import csv
def parse_rows(data_file):
reader = csv.DictReader(data_file, delimiter=";")
for i, row in enumerate(reader):
date = datetime.datetime.strptime(row['DATE'] + ".2016", "%d.%m.%Y").date()
lieu = row['LIEUX']
nom, prenom = row['NOM'], row['PRENOM']
prem_rencontre = True if row['1 ER CONTACT'].lower() == "oui" else False
yield i, date, lieu, nom, prenom, prem_rencontre
from django.core.management.base import BaseCommand, CommandError
class Command(BaseCommand):
help = "Load data for rencontre from csv files"
def add_arguments(self, parser):
parser.add_argument('file', help="path to the files to load", nargs="+", type=str)
parser.add_argument('--commit', help="commit changes to the database",
action="store_true", dest="commit", default=False)
parser.add_argument('--check', action='store_true', dest='check', default=False,
help="Check that all lines from file are written into database")
@property
def cache(self):
if not hasattr(self, '_cache'):
self._cache = {'maraude': {}, 'lieu': {}, 'sujet': {}, 'rencontre': [], 'observation': []}
return self._cache
def new_object(self, model, data, cache_key=None):
""" Create new object, add it to cache (in dict if cache_key is given, in list otherwise).
Save it only if --commit option is given
"""
obj = model(**data)
msg = "[%i]+ Created %s " % (self.cur_line, obj)
if self._commit:
obj.save()
msg += " successfully saved to db"
if cache_key:
self.cache[model.__qualname__.lower()][cache_key] = obj
msg += " and added to cache."
self.stdout.write(self.style.SUCCESS(msg))
return obj
@property
def referent_maraude(self):
if not hasattr(self, '_referent'):
from utilisateurs.models import Maraudeur
self._referent = Maraudeur.objects.get_referent()
return self._referent
def find_maraude(self, date):
from maraudes.models import Maraude
try: # First, try to retrieve from database
obj = Maraude.objects.get(date=date)
except Maraude.DoesNotExist:
# Try to retrieve from cache
try:
obj = self.cache['maraude'][date]
except KeyError:
# Create a new object and put it into cache
obj = self.new_object(
Maraude,
{'date':date, 'referent':self.referent_maraude, 'binome':self.referent_maraude},
cache_key=date)
return obj
def find_sujet(self, nom, prenom):
from sujets.models import Sujet
from watson import search
search_text = "%s %s" % (nom, prenom)
sujet = self.cache['sujet'].get(search_text, None)
while not sujet:
create = False #Set to True if creation is needed at and of loop
self.stdout.write(self.style.WARNING("In line %i, searching : %s. " % (self.cur_line, search_text)), ending='')
results = search.filter(Sujet, search_text)
if results.count() == 1: # Always ask to review result a first time
sujet = results[0]
self.stdout.write(self.style.SUCCESS("Found %s '%s' %s" % (sujet.nom, sujet.surnom, sujet.prenom)))
action = input("Confirm ? (y/n/type new search)> ")
if action == "n":
sujet = None
search_text = "%s %s" % (nom, prenom)
elif action == "y":
continue
else: # In case the result is no good at all !
sujet = None
search_text = action
elif results.count() > 1: # Ask to find the appropriate result
self.stdout.write(self.style.WARNING("Multiple results for %s" % search_text))
for i, result in enumerate(results):
self.stdout.write("%i. %s '%s' %s" % (i, result.nom, result.surnom, result.prenom))
choice = input("Choose the right number - Type new search - C to create '%s %s': " % (nom, prenom))
if choice == "C":
create = True
else:
try: sujet = results[int(choice)]
except (IndexError, ValueError):
search_text = str(choice) #New search
continue
else: # No results, try with name only, or ask for new search
if search_text == "%s %s" % (nom, prenom):
# Search only with nom
self.stdout.write(self.style.WARNING("Nothing, trying name only..."), ending='')
search_text = nom if nom else prenom
continue
else:
self.stdout.write(self.style.ERROR("No result !"))
action = input("New search or C to create '%s %s': " % (nom, prenom))
if action == "C":
create = True
else:
search_text = str(action)
if create:
sujet = self.new_object(
Sujet,
{'nom':nom, 'prenom':prenom}
)
self.stdout.write('Created, %s' % sujet)
# Always store sujet in cache because it may or may not be updated, safer to save in all cases.
self.cache['sujet']["%s %s" % (nom, prenom)] = sujet
return sujet
def find_lieu(self, nom):
from maraudes.models import Lieu
try:
lieu = Lieu.objects.get(nom=nom)
except Lieu.DoesNotExist:
lieu = self.cache['lieu'].get(nom, None)
while not lieu:
self.stdout.write(self.style.WARNING("At line %i, le lieu '%s' n'a pas été trouvé" % (self.cur_line, nom)))
action = input('%s (Créer/Sélectionner)> ' % nom)
if action == "C":
lieu = self.new_object(Lieu, {'nom': nom}, cache_key=nom)
elif action == "S":
choices = {l.pk:l.nom for l in Lieu.objects.all()}
for key, name in choices.items():
self.stdout.write("%i. %s" % (key, name))
while not lieu:
chosen_key = input('Choose a number: ')
try:
lieu = Lieu.objects.get(pk=chosen_key)
confirm = input("Associer %s à %s ? (o/n)> " % (nom, lieu.nom))
if confirm == "n":
lieu = None
else:
self.cache['lieu'][nom] = lieu
except (Lieu.DoesNotExist, ValueError):
lieu = None
else:
continue
return lieu
def add_rencontre(self, maraude, sujet, lieu):
from maraudes.models import Rencontre
from maraudes.notes import Observation
rencontre = self.new_object(Rencontre,
{'maraude':maraude, 'lieu':lieu, 'heure_debut':datetime.time(20, 0),
'duree':15})
observation = self.new_object(Observation, {'rencontre':rencontre,
'sujet':sujet,
'text':"Chargé depuis '%s'" % self._file.name})
self.cache['rencontre'].append(rencontre)
self.cache['observation'].append(observation)
def handle(self, **options):
""" Parsing all given files, look for existing objects and create new Rencontre
and Observation objects. Ask for help finding related object, creating new ones
if needed. All creation/updates are stored in cache and commited only after
user confirmation
"""
self._commit = options.get('commit', False)
for file_path in options['file']:
with open(file_path, 'r') as data_file:
self.stdout.write("Working with '%s'" % data_file.name)
self._file = data_file
for line, date, lieu, nom, prenom, prems in parse_rows(data_file):
self.cur_line = line
maraude = self.find_maraude(date)
lieu = self.find_lieu(lieu)
sujet = self.find_sujet(nom, prenom)
assert sujet is not None
assert lieu is not None
assert maraude is not None
if prems and self._commit:
sujet.premiere_rencontre = date
sujet.save()
self.stdout.write(self.style.SUCCESS("[%i]* Updated premiere_rencontre on %s" % (self.cur_line, sujet)))
self.add_rencontre(maraude, sujet, lieu)
#Summary
self.stdout.write(" ## %s : %i lines ##" % (data_file.name, self.cur_line))
self.stdout.write("Trouvé %s nouvelles observations" % len(self.cache['observation']))
self.stdout.write("Nécessite l'ajout/modification de : \n- %i maraudes\n- %i lieux\n- %i sujets" %
(len(self.cache['maraude']), len(self.cache['lieu']), len(self.cache['sujet'])))
view = input('Voulez-vous voir la liste des changements ? (o/n)> ')
if view == "o": self.stdout.write(" ## Changements ## \n%s" % self.cache)