changes display of notes in tables (headers on the side)

corrects code for 'notes' modules according to PEP8 guidelines
This commit is contained in:
artus40
2017-12-27 12:42:40 +01:00
parent 8092859cb4
commit f8e618e08a
10 changed files with 171 additions and 132 deletions

View File

@@ -1,5 +1,4 @@
from .models import Sujet
from statistiques.models import FicheStatistique, NSP
from statistiques.models import NSP
def merge_stats(main, merged):
@@ -28,6 +27,7 @@ def merge_stats(main, merged):
if getattr(main.statistiques, field) == NSP: # Ignore if already filled
setattr(main.statistiques, field, getattr(merged.statistiques, field, NSP))
def merge_two(main, merged):
""" Merge 'merged' sujet into 'main' one """
merge_stats(main, merged) # Merge statistics and informations

View File

@@ -1,9 +1,10 @@
from django.apps import AppConfig
from watson import search as watson
class NotesConfig(AppConfig):
name = 'notes'
def ready(self):
Sujet = self.get_model("Sujet")
watson.register(Sujet, fields=('nom', 'prenom', 'surnom'))
sujet_model = self.get_model("Sujet")
watson.register(sujet_model, fields=('nom', 'prenom', 'surnom'))

View File

@@ -6,8 +6,8 @@ from utilisateurs.models import Professionnel
from django import forms
from django_select2.forms import Select2Widget
### NOTES
# NOTES
class NoteForm(forms.ModelForm):
""" Generic Note form """
class Meta:
@@ -15,11 +15,12 @@ class NoteForm(forms.ModelForm):
fields = ['sujet', 'text', 'created_by', 'created_date', 'created_time']
widgets = {
'sujet': Select2Widget(),
'text': forms.Textarea(attrs={'rows':4}),
'text': forms.Textarea(
attrs={'rows': 4}
),
}
class SimpleNoteForm(forms.ModelForm):
""" Simple note with only 'sujet' and 'text' fields.
@@ -30,7 +31,6 @@ class SimpleNoteForm(forms.ModelForm):
fields = ['sujet', 'text']
class UserNoteForm(NoteForm):
""" Form that sets 'created_by' with current user id.
@@ -56,7 +56,6 @@ class UserNoteForm(NoteForm):
return instance
class AutoNoteForm(UserNoteForm):
class Meta(UserNoteForm.Meta):
fields = ['text']
@@ -73,22 +72,22 @@ class AutoNoteForm(UserNoteForm):
return inst
### SUJETS
# SUJETS
current_year = datetime.date.today().year
YEAR_CHOICE = tuple(year - 2 for year in range(current_year, current_year + 10))
class SujetCreateForm(forms.ModelForm):
class Meta:
model = Sujet
fields = ['nom', 'surnom', 'prenom', 'genre', 'premiere_rencontre']
widgets = {
'premiere_rencontre': forms.SelectDateWidget( empty_label=("Année", "Mois", "Jour"),
years = YEAR_CHOICE,
widgets = {'premiere_rencontre': forms.SelectDateWidget(
empty_label=("Année", "Mois", "Jour"),
years=YEAR_CHOICE,
),
}
class SelectSujetForm(forms.Form):
sujet = forms.ModelChoiceField(queryset=Sujet.objects.all(), widget=Select2Widget)

View File

@@ -5,10 +5,7 @@ from django.db.models.query import QuerySet
class NoteQuerySet(QuerySet):
def _ordered_by_field(self, field, reverse=False):
return self.order_by( '%s%s' % ( "-" if reverse else "",
field
)
)
return self.order_by('%s%s' % ("-" if reverse else "", field))
def by_date(self, reverse=False):
return self._ordered_by_field('created_date', reverse=reverse)
@@ -16,6 +13,7 @@ class NoteQuerySet(QuerySet):
def by_time(self, reverse=False):
return self._ordered_by_field('created_time', reverse=reverse)
class NoteManager(Manager):
def get_queryset(self):
@@ -28,5 +26,3 @@ class NoteManager(Manager):
def by_time(self, **kwargs):
return self.get_queryset().by_time(**kwargs)

View File

@@ -1,34 +1,39 @@
from django.views.generic.edit import FormMixin
from django.contrib import messages
from .forms import *
class NoteFormMixin(FormMixin):
""" A mixin that allows to easily embed multiple distinct forms on a view.
Only one form can be processed by request !
"""
# 'forms' shall be a dict of form classes, indexed by a prefix
forms = None
def get_form(self, prefix):
kwargs = self.get_form_kwargs()
kwargs['prefix'] = prefix
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['request'] = self.request
form_class = self.forms[prefix]
return form_class(**kwargs)
return kwargs
def get_form_by_prefix(self, prefix):
try:
return self.get_form(form_class=self.forms[prefix])
except KeyError:
raise ValueError("The form with prefix %s is not declared in %s" % (prefix, self.forms))
def post(self, request, **kwargs):
""" Save the first valid form found or reload page displaying a warning """
for prefix in self.forms.keys():
form = self.get_form(prefix)
form = self.get_form_by_prefix(prefix)
if form.is_valid():
form.save()
messages.success(self.request, "Un nouveau %s a été enregistré" % prefix)
return self.form_valid(form)
return self.form_invalid(form)
def form_valid(self, form):
messages.success(self.request, "Un nouveau %s a été enregistré" % form.prefix)
return super().form_valid(form)
messages.warning(request, "Il y a eu une erreur lors du traitement du formulaire")
return self.get(request, **kwargs)
def get_context_data(self, **kwargs):
context = super(FormMixin, self).get_context_data(**kwargs)
for prefix in self.forms.keys():
context['%s_form' % prefix] = self.get_form(prefix)
context['%s_form' % prefix] = self.get_form_by_prefix(prefix)
return context

View File

@@ -16,6 +16,7 @@ GENRE_CHOICES = (
(FEMME, 'Femme'),
)
class Sujet(models.Model):
""" Personne faisant l'objet d'un suivi par la maraude
"""
@@ -38,9 +39,12 @@ class Sujet(models.Model):
def __str__(self):
string = '%s ' % self.genre
if self.nom: string += '%s ' % self.nom
if self.surnom: string += '"%s" ' % self.surnom
if self.prenom: string += '%s' % self.prenom
if self.nom:
string += '%s ' % self.nom
if self.surnom:
string += '"%s" ' % self.surnom
if self.prenom:
string += '%s' % self.prenom
return string
def clean(self):
@@ -65,6 +69,27 @@ class Sujet(models.Model):
return reverse("notes:details-sujet", kwargs={"pk": self.pk})
# Attributes used by 'notes' template tags
# bg_color : background color of header
# labels : list of strings to put in labels
def cached_attr(name):
""" Cached property set on return value of 'note_ATTR' method on
child instance.
"""
private_name = '_%s' % name
def getter(self):
if not hasattr(self, private_name):
setattr(self,
private_name,
# Call *child instance* method
getattr(self.cast(), 'note_%s' % name)()
)
return getattr(self, private_name)
return getter
class Note(models.Model):
""" Note relative à un sujet.
@@ -121,7 +146,7 @@ class Note(models.Model):
""" Returns (header background color, labels color).
Values must be valid bootstrap color name
"""
return ("default", "info")
return "default", "info"
def note_labels(self):
""" Returns list of objects that are printed as bootstrap labels """
@@ -144,7 +169,6 @@ class Note(models.Model):
self._child_class = self._child_instance.__class__
return
@property
def type_name(self):
return self.child_class.__qualname__
@@ -160,23 +184,7 @@ class Note(models.Model):
self._get_child_class_and_instance()
return self._child_instance
## Attributes used by 'notes' template tags
# bg_color : background color of header
# labels : list of strings to put in labels
def cached_attr(name):
""" Cached property set on return value of 'note_ATTR' method on
child instance.
"""
private_name = '_%s' % name
def getter(self):
if not hasattr(self, private_name):
setattr(self,
private_name,
# Call *child instance* method
getattr(self.cast(), 'note_%s' % name)()
)
return getattr(self, private_name)
return getter
# Define specials properties
bg_colors = property(cached_attr('bg_colors'), doc="background color of header")
labels = property(cached_attr('labels'), doc="list of string to display as labels")

View File

@@ -1,16 +1,14 @@
<tr>
<th class="bg-{{bg_color}}" >
<th class="bg-{{bg_color}}">
{% if link %}
<a href="{{link}}"><strong>{{header}}</strong></a>
{% else %}
<strong>{{header}}</strong>
{% endif %} <small>{{small}}</small>
<span class="pull-right">
<br />
{% for label in labels %}
<span class="label label-{{bg_label_color}}">{{label}}</span>
{% endfor %}</span>
<span class="label label-{{bg_label_color}}">{{label}}</span><br />
{% endfor %}
</th>
</tr>
<tr>
<td style="background-color:#fff"><p>{{text | linebreaks }}</p></td>
</tr>

View File

@@ -1,8 +1,8 @@
from django import template
from django.urls import reverse
register = template.Library()
@register.inclusion_tag("notes/table_inline.html")
def inline_table(note, header=None):
from maraudes.models import Maraude
@@ -10,7 +10,7 @@ def inline_table(note, header=None):
if not header:
header = "date"
if not header in ['sujet', 'date']:
if header not in ['sujet', 'date']:
raise ValueError('header must be "sujet" or "date"')
if header == "date":
@@ -24,10 +24,8 @@ def inline_table(note, header=None):
header_field = "sujet"
link = note.sujet.get_absolute_url()
header = getattr(note, header_field)
return {
'header': header,
'header': getattr(note, header_field),
'link': link,
'small': note.child_class.__qualname__,
'bg_color': bg_color or "default",

View File

@@ -24,6 +24,7 @@ class SujetModelTestCase(TestCase):
with self.assertRaises(ValidationError):
Sujet.objects.create(age=25)
class NoteManagerTestCase(TestCase):
""" managers.NoteManager Test Case """
@@ -32,8 +33,11 @@ class NoteManagerTestCase(TestCase):
self.note_manager = NoteManager
def test_note_default_manager_is_NoteManager(self):
self.assertIsInstance(Note.objects, self.note_manager,
msg="%s is not Note default manager" % self.note_manager)
self.assertIsInstance(
Note.objects,
self.note_manager,
msg="%s is not Note default manager" % self.note_manager
)
def test_by_date(self):
prev_note = None
@@ -41,8 +45,11 @@ class NoteManagerTestCase(TestCase):
if not prev_note:
prev_note = note
else:
self.assertLessEqual(prev_note.created_date, note.created_date,
msg="%s is not same date or prior to %s" % (prev_note, note))
self.assertLessEqual(
prev_note.created_date,
note.created_date,
msg="%s is not same date or prior to %s" % (prev_note, note)
)
prev_note = note
def test_by_date_reversed(self):
@@ -51,8 +58,11 @@ class NoteManagerTestCase(TestCase):
if not prev_note:
prev_note = note
else:
self.assertGreaterEqual(prev_note.created_date, note.created_date,
msg="%s is not same date or later to %s" % (prev_note, note))
self.assertGreaterEqual(
prev_note.created_date,
note.created_date,
msg="%s is not same date or later to %s" % (prev_note, note)
)
prev_note = note
def test_by_time(self):
@@ -61,8 +71,11 @@ class NoteManagerTestCase(TestCase):
if not prev_note:
prev_note = note
else:
self.assertLessEqual(prev_note.created_time, note.created_time,
msg="%s is not same time or prior to %s" % (prev_note, note))
self.assertLessEqual(
prev_note.created_time,
note.created_time,
msg="%s is not same time or prior to %s" % (prev_note, note)
)
prev_note = note
def test_by_time_reversed(self):
@@ -71,10 +84,13 @@ class NoteManagerTestCase(TestCase):
if not prev_note:
prev_note = note
else:
self.assertLessEqual(prev_note.created_time, note.created_time,
self.assertLessEqual(
prev_note.created_time,
note.created_time,
msg="%s is not same time or later to %s" % (prev_note, note))
prev_note = note
class NoteQuerySetTestCase(TestCase):
def setUp(self):
@@ -86,7 +102,9 @@ class NoteQuerySetTestCase(TestCase):
if not prev_note:
prev_note = note
else:
self.assertLessEqual(prev_note.created_date, note.created_date,
self.assertLessEqual(
prev_note.created_date,
note.created_date,
msg="%s is not same date or prior to %s" % (prev_note, note))
prev_note = note
@@ -96,8 +114,11 @@ class NoteQuerySetTestCase(TestCase):
if not prev_note:
prev_note = note
else:
self.assertGreaterEqual(prev_note.created_date, note.created_date,
msg="%s is not same date or later to %s" % (prev_note, note))
self.assertGreaterEqual(
prev_note.created_date,
note.created_date,
msg="%s is not same date or later to %s" % (prev_note, note)
)
prev_note = note
def test_by_time(self):
@@ -106,8 +127,11 @@ class NoteQuerySetTestCase(TestCase):
if not prev_note:
prev_note = note
else:
self.assertLessEqual(prev_note.created_time, note.created_time,
msg="%s is not same time or prior to %s" % (prev_note, note))
self.assertLessEqual(
prev_note.created_time,
note.created_time,
msg="%s is not same time or prior to %s" % (prev_note, note)
)
prev_note = note
def test_by_time_reversed(self):
@@ -116,6 +140,9 @@ class NoteQuerySetTestCase(TestCase):
if not prev_note:
prev_note = note
else:
self.assertLessEqual(prev_note.created_time, note.created_time,
msg="%s is not same time or later to %s" % (prev_note, note))
self.assertLessEqual(
prev_note.created_time,
note.created_time,
msg="%s is not same time or later to %s" % (prev_note, note)
)
prev_note = note

View File

@@ -1,13 +1,12 @@
import logging
import datetime
from django.shortcuts import redirect, reverse
from django.views import generic
from django.utils import timezone
from django.contrib import messages
from django.http.response import HttpResponseNotAllowed
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from watson import search as watson
from website.mixins import AjaxTemplateMixin
from utilisateurs.mixins import MaraudeurMixin
from maraudes.models import Maraude, CompteRendu
from maraudes.notes import Observation, Signalement
@@ -20,13 +19,13 @@ logger = logging.getLogger(__name__)
# Create your views here.
class IndexView(MaraudeurMixin, generic.TemplateView):
template_name = "notes/index.html"
def get(self, *args, **kwargs):
return redirect("notes:liste-sujet")
class Filter:
def __init__(self, title, name, filter_func):
self.title = title
@@ -38,7 +37,6 @@ class Filter:
return self._filter_func(qs)
class ListView(MaraudeurMixin, generic.ListView):
""" Base ListView for Maraude and Sujet lists """
paginate_by = 30
@@ -47,8 +45,8 @@ class ListView(MaraudeurMixin, generic.ListView):
filters = []
active_filter = None
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def __init__(self, **kwargs):
super().__init__(**kwargs)
self._filters = {}
if self.filters:
@@ -95,38 +93,37 @@ class MaraudeListView(ListView):
class SujetListView(ListView):
#ListView
# ListView
model = Sujet
template_name = "notes/liste_sujets.html"
cell_template = "notes/table_cell_sujets.html"
table_header = "Liste des sujets"
@staticmethod
def info_completed_filter(qs):
COMPLETED_RATIO = 70 # % of total fields completed
completed_ratio = 70 # % of total fields completed
excluded_set = set()
for sujet in qs:
if sujet.statistiques.info_completed >= COMPLETED_RATIO:
if sujet.statistiques.info_completed >= completed_ratio:
excluded_set.add(sujet.pk)
return qs.exclude(pk__in=excluded_set)
@staticmethod
def rencontre_dans_le_mois(qs):
""" Renvoie les sujets du queryset pour lesquelles une observation a été enregistrée
au cours des 30 derniers jours """
DAYS_NUMBER = 30
LIMIT_DATE = timezone.now().date() - datetime.timedelta(DAYS_NUMBER)
days_number = 30
limit_date = timezone.now().date() - datetime.timedelta(days_number)
included_set = set()
for sujet in qs:
# Try to find an observation in the range
most_recent_obs = Observation.objects.filter(sujet=sujet).order_by("-created_date").first()
if most_recent_obs and most_recent_obs.created_date >= LIMIT_DATE:
if most_recent_obs and most_recent_obs.created_date >= limit_date:
included_set.add(sujet.pk)
return qs.filter(pk__in=included_set)
filters = [
("Connu(e)s cette année",
lambda qs: qs.filter(
@@ -138,10 +135,9 @@ class SujetListView(ListView):
]
def post(self, request, **kwargs):
from watson import search as watson
search_text = request.POST.get('q')
results = watson.filter(Sujet, search_text)
#logger.warning("SEARCH for %s : %s" % (search_text, results))
# logger.warning("SEARCH for %s : %s" % (search_text, results))
if results.count() == 1:
return redirect(results[0].get_absolute_url())
self.queryset = results
@@ -156,6 +152,7 @@ class SujetListView(ListView):
class DetailView(MaraudeurMixin, generic.DetailView):
template_name = "notes/details.html"
class CompteRenduDetailsView(DetailView):
""" Vue détaillé d'un compte-rendu de maraude """
@@ -165,7 +162,10 @@ class CompteRenduDetailsView(DetailView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['notes'] = sorted(Note.objects.get_queryset().filter(created_date=self.object.date), key=lambda n: n.created_time)
context['notes'] = sorted(Note.objects
.get_queryset()
.filter(created_date=self.object.date),
key=lambda n: n.created_time)
context['next_maraude'] = Maraude.objects.get_future(
date=self.object.date + datetime.timedelta(1)
).filter(
@@ -180,17 +180,20 @@ class CompteRenduDetailsView(DetailView):
class SuiviSujetView(NoteFormMixin, DetailView):
#NoteFormMixin
# NoteFormMixin
forms = {
'note': AutoNoteForm,
}
def get_success_url(self):
return reverse('notes:details-sujet', kwargs={'pk': self.get_object().pk})
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['sujet'] = self.get_object()
return kwargs
#DetailView
# DetailView
model = Sujet
template_name = "notes/details_sujet.html"
context_object_name = "sujet"
@@ -205,8 +208,8 @@ class SuiviSujetView(NoteFormMixin, DetailView):
self.page = self.request.GET.get('page', 1)
return super().get(*args, **kwargs)
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
try:
notes = self.paginator.page(self.page)
except PageNotAnInteger:
@@ -217,10 +220,9 @@ class SuiviSujetView(NoteFormMixin, DetailView):
return context
### Sujet Management Views
# Sujet Management Views
class SujetAjaxDetailsView(generic.DetailView):
#DetailView
# DetailView
template_name = "notes/details_sujet_inner.html"
model = Sujet
@@ -232,8 +234,9 @@ class SujetAjaxDetailsView(generic.DetailView):
return redirect("notes:details-sujet", pk=self.get_object().pk)
return super().get(*args, **kwargs)
class SujetAjaxUpdateView(generic.edit.UpdateView):
#UpdateView
""" View for 'sujet' updates, can be retrieved by ajax requests. """
template_name = "notes/details_sujet_update.html"
model = Sujet
fields = '__all__'
@@ -241,24 +244,28 @@ class SujetAjaxUpdateView(generic.edit.UpdateView):
def get_success_url(self):
return reverse("notes:details-sujet", kwargs={'pk': self.object.pk})
from website.mixins import AjaxTemplateMixin
class SujetCreateView(AjaxTemplateMixin, generic.edit.CreateView):
#CreateView
""" View for 'sujet' creation, can be retrieved by ajax requests. """
template_name = "notes/sujet_create.html"
form_class = SujetCreateForm
def post(self, request, *args, **kwargs):
if 'next' in self.request.POST:
self.success_url = self.request.POST["next"]
return super().post(self, request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
try: context['next'] = self.request.GET['next']
except:context['next'] = None
try:
context['next'] = self.request.GET['next']
except:
context['next'] = None
return context
class MergeView(generic.DetailView, generic.FormView):
""" Implement actions.merge_two as a view """
""" Merge two 'sujet' objects by implementing actions.merge_two as a view """
template_name = "notes/sujet_merge.html"
model = Sujet