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
This commit is contained in:
@@ -0,0 +1 @@
|
||||
default_app_config = 'notes.apps.NotesConfig'
|
||||
|
||||
38
notes/actions.py
Normal file
38
notes/actions.py
Normal file
@@ -0,0 +1,38 @@
|
||||
from .models import Sujet
|
||||
from statistiques.models import FicheStatistique, NSP
|
||||
|
||||
|
||||
def merge_stats(main, merged):
|
||||
""" Merge stats of two sujets according to priority order : main, then merged """
|
||||
# TODO: replace hardcoded field names with more flexible getters
|
||||
|
||||
# Fields of 'Sujet' model
|
||||
for field in ('nom', 'prenom', 'surnom', 'age',):
|
||||
if not getattr(main, field):
|
||||
setattr(main, field, getattr(merged, field, None))
|
||||
|
||||
# Première rencontre : retenir la plus ancienne
|
||||
if merged.premiere_rencontre:
|
||||
if not main.premiere_rencontre or main.premiere_rencontre > merged.premiere_rencontre:
|
||||
main.premiere_rencontre = merged.premiere_rencontre
|
||||
|
||||
# Fields of 'FicheStatistique' model
|
||||
# NullBoolean fields
|
||||
for field in ('prob_psychiatrie', 'prob_somatique',
|
||||
'prob_administratif', 'prob_addiction',
|
||||
'connu_siao', 'lien_familial'):
|
||||
if not getattr(main.statistiques, field): # Ignore if already filled
|
||||
setattr(main.statistiques, field, getattr(merged.statistiques, field, None))
|
||||
# Choice fields, None is NSP
|
||||
for field in ('habitation', 'ressources', 'parcours_de_vie'):
|
||||
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
|
||||
for note in merged.notes.all(): # Move all notes
|
||||
note.sujet = main
|
||||
note.save()
|
||||
main.save()
|
||||
merged.delete()
|
||||
@@ -3,6 +3,15 @@ from django.contrib import admin
|
||||
from .models import *
|
||||
# Register your models here.
|
||||
|
||||
@admin.register(Sujet)
|
||||
class SujetAdmin(admin.ModelAdmin):
|
||||
|
||||
fieldsets = [
|
||||
('Identité', {'fields': [('nom', 'prenom'), 'genre']}),
|
||||
('Informations', {'fields': ['age', ]}),
|
||||
]
|
||||
|
||||
|
||||
@admin.register(Note)
|
||||
class NoteAdmin(admin.ModelAdmin):
|
||||
|
||||
@@ -16,4 +25,4 @@ class NoteAdmin(admin.ModelAdmin):
|
||||
]
|
||||
|
||||
list_display = ['created_date', 'sujet', 'child_class', 'text']
|
||||
list_filter = ('sujet', 'created_date', 'created_by')
|
||||
list_filter = ('created_date', 'created_by')
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
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'))
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
from .models import Note
|
||||
import datetime
|
||||
|
||||
from .models import Note, Sujet
|
||||
from utilisateurs.models import Professionnel
|
||||
|
||||
from django import forms
|
||||
from django_select2.forms import Select2Widget
|
||||
from django.forms import Textarea
|
||||
|
||||
|
||||
### NOTES
|
||||
|
||||
class NoteForm(forms.ModelForm):
|
||||
""" Generic Note form """
|
||||
@@ -14,7 +15,7 @@ class NoteForm(forms.ModelForm):
|
||||
fields = ['sujet', 'text', 'created_by', 'created_date', 'created_time']
|
||||
widgets = {
|
||||
'sujet': Select2Widget(),
|
||||
'text': Textarea(attrs={'rows':4}),
|
||||
'text': forms.Textarea(attrs={'rows':4}),
|
||||
}
|
||||
|
||||
|
||||
@@ -54,6 +55,8 @@ class UserNoteForm(NoteForm):
|
||||
instance.save()
|
||||
return instance
|
||||
|
||||
|
||||
|
||||
class AutoNoteForm(UserNoteForm):
|
||||
class Meta(UserNoteForm.Meta):
|
||||
fields = ['text']
|
||||
@@ -68,3 +71,24 @@ class AutoNoteForm(UserNoteForm):
|
||||
if commit:
|
||||
inst.save()
|
||||
return inst
|
||||
|
||||
|
||||
|
||||
### 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,
|
||||
),
|
||||
}
|
||||
|
||||
class SelectSujetForm(forms.Form):
|
||||
|
||||
sujet = forms.ModelChoiceField(queryset=Sujet.objects.all(), widget=Select2Widget)
|
||||
|
||||
@@ -1,9 +1,74 @@
|
||||
import logging
|
||||
|
||||
from django.utils import timezone
|
||||
from django.utils.html import format_html
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.urls import reverse
|
||||
|
||||
from django.db import models
|
||||
from . import managers
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
HOMME = 'M'
|
||||
FEMME = 'Mme'
|
||||
GENRE_CHOICES = (
|
||||
(HOMME, 'Homme'),
|
||||
(FEMME, 'Femme'),
|
||||
)
|
||||
|
||||
class Sujet(models.Model):
|
||||
""" Personne faisant l'objet d'un suivi par la maraude
|
||||
"""
|
||||
|
||||
genre = models.CharField("Genre",
|
||||
max_length=3,
|
||||
choices=GENRE_CHOICES,
|
||||
default=HOMME)
|
||||
nom = models.CharField(max_length=32, blank=True)
|
||||
prenom = models.CharField(max_length=32, blank=True)
|
||||
surnom = models.CharField(max_length=64, blank=True)
|
||||
|
||||
premiere_rencontre = models.DateField(
|
||||
blank=True, null=True,
|
||||
default=timezone.now
|
||||
)
|
||||
age = models.SmallIntegerField(
|
||||
blank=True, null=True
|
||||
)
|
||||
|
||||
# referent = models.ForeignKey("utilisateurs.Professionnel", related_name="suivis")
|
||||
|
||||
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
|
||||
return string
|
||||
|
||||
def clean(self):
|
||||
if not any([self.nom, self.prenom, self.surnom]):
|
||||
raise ValidationError("Vous devez remplir au moins un nom, prénom ou surnom")
|
||||
return super().clean()
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.clean()
|
||||
if not self.id:
|
||||
from statistiques.models import FicheStatistique
|
||||
super().save(*args, **kwargs)
|
||||
fiche = FicheStatistique.objects.create(sujet=self)
|
||||
else:
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Sujet"
|
||||
ordering = ('surnom', 'nom', 'prenom')
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse("notes:details-sujet", kwargs={"pk": self.pk })
|
||||
|
||||
|
||||
|
||||
class Note(models.Model):
|
||||
""" Note relative à un sujet.
|
||||
|
||||
@@ -19,11 +84,12 @@ class Note(models.Model):
|
||||
objects = managers.NoteManager()
|
||||
|
||||
sujet = models.ForeignKey(
|
||||
'sujets.Sujet',
|
||||
Sujet,
|
||||
related_name="notes",
|
||||
on_delete=models.CASCADE
|
||||
)
|
||||
text = models.TextField("Texte")
|
||||
|
||||
created_by = models.ForeignKey(
|
||||
'utilisateurs.Professionnel',
|
||||
blank=True,
|
||||
@@ -42,7 +108,11 @@ class Note(models.Model):
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
def __str__(self):
|
||||
return "%s" % (self.child_class.__qualname__)
|
||||
return "<%s: %s>" % (self.child_class.__qualname__, self.sujet)
|
||||
|
||||
@classmethod
|
||||
def __str__(cls):
|
||||
return "<%s>" % cls.__qualname__
|
||||
|
||||
def note_author(self):
|
||||
return None
|
||||
|
||||
14
notes/templates/notes/base.html
Normal file
14
notes/templates/notes/base.html
Normal file
@@ -0,0 +1,14 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Notes >{% endblock %}
|
||||
|
||||
{% block breadcrumbs %}
|
||||
<li>Notes</li>
|
||||
{% endblock %}
|
||||
|
||||
{% block sidebar %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-body">
|
||||
{% include "notes/menu.html" %}
|
||||
</div>
|
||||
</div>{% endblock %}
|
||||
33
notes/templates/notes/details.html
Normal file
33
notes/templates/notes/details.html
Normal file
@@ -0,0 +1,33 @@
|
||||
{% extends "notes/base.html" %}
|
||||
{% load bootstrap3 notes %}
|
||||
|
||||
{% block page_content %}
|
||||
<div class="col-lg-8 col-md-12">
|
||||
<div class="panel-group" id="accordion" role="tablist" aria-multiselectable="true">
|
||||
<div class="panel panel-primary">
|
||||
<div class="panel-heading" role="tab" id="notesHeading">
|
||||
<h3 class="panel-title">
|
||||
<a role="button" data-toggle="collapse" data-parent="#accordion" href="#collapseNotes" aria-expanded="true" aria-controls="collapseOne">
|
||||
Notes</a>
|
||||
</h3>
|
||||
</div>
|
||||
{% block pre_content %}{% endblock %}
|
||||
<table class="table table-striped table-bordered">
|
||||
{% for note in notes %}
|
||||
{% if maraude %}
|
||||
{% inline_table note header="sujet" %}
|
||||
{% elif sujet %}
|
||||
{% inline_table note header="date" %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% block post_content %}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-4 col-md-12">
|
||||
{% block right_column %}{% endblock %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
47
notes/templates/notes/details_maraude.html
Normal file
47
notes/templates/notes/details_maraude.html
Normal file
@@ -0,0 +1,47 @@
|
||||
{% extends "notes/details.html" %}
|
||||
|
||||
{% block title %}
|
||||
{{ block.super }} {{ maraude }}
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumbs %}
|
||||
{{ block.super }}
|
||||
<li><a href="{% url "notes:liste-maraude" %}">Maraudes</a></li>
|
||||
<li>{{ maraude }}</li>
|
||||
{% endblock %}
|
||||
|
||||
{% block sidebar %}
|
||||
<div class="panel panel-primary text-right">
|
||||
<div class="panel-body text-right">
|
||||
<h4 class="panel-title">Navigation</h4>
|
||||
<nav aria-label="Maraudes navigation">
|
||||
<ul class="pagination">
|
||||
<li {% if not prev_maraude %}class="disabled"{% endif %}>
|
||||
<a href="{% if prev_maraude %}{% url "notes:details-maraude" prev_maraude.pk %}{% else %}#{% endif %}" aria-label="Previous">
|
||||
<span aria-hidden="true" class="glyphicon glyphicon-chevron-left"></span>
|
||||
</a>
|
||||
</li>
|
||||
<li {% if not next_maraude %}class="disabled"{% endif %}>
|
||||
<a href="{% if next_maraude %}{% url "notes:details-maraude" next_maraude.pk %}{%else%}#{% endif %}" aria-label="Next">
|
||||
<span aria-hidden="true" class="glyphicon glyphicon-chevron-right"></span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block right_column %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading" role="tab" id="notesSujetHeading">
|
||||
<h3 class="panel-title">
|
||||
Informations</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<p><strong>Maraudeurs :</strong> {{ maraude.binome }} & {{ maraude.referent }}</p>
|
||||
<p><strong>Nombre de rencontres</strong> {{ maraude.rencontres.count }}</p>
|
||||
<p><strong>Nombre de personnes rencontrées</strong> {{ maraude.observations_count }}</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
103
notes/templates/notes/details_sujet.html
Normal file
103
notes/templates/notes/details_sujet.html
Normal file
@@ -0,0 +1,103 @@
|
||||
{% extends "notes/details.html" %}
|
||||
{% load bootstrap3 %}
|
||||
|
||||
{% block title %}
|
||||
{{ block.super }} {{ sujet }}
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumbs %}
|
||||
{{ block.super }}
|
||||
<li><a href="{% url "notes:liste-sujet" %}">Sujets</a></li>
|
||||
<li>{{ sujet }}</li>
|
||||
{% endblock %}
|
||||
|
||||
{% block pre_content %}
|
||||
<div id="collapseNotes" class="panel-collapse collapse in" role="tabpanel" aria-labelledby="notesHeading">
|
||||
{% endblock %}
|
||||
|
||||
{% block post_content %}
|
||||
{% if notes.has_other_pages %}<div class="panel-footer text-center">
|
||||
<ul class="pagination">
|
||||
{% for num in notes.paginator.page_range %}
|
||||
<li {% if notes.number == num %} class="active" {%endif%}><a href="?page={{num}}">{{num}}</a></li>
|
||||
{%endfor%}
|
||||
</ul>
|
||||
</div>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading" role="tab" id="notesAjoutHeading">
|
||||
<h4 class="panel-title">
|
||||
<a class="collapsed" role="button" data-toggle="collapse" data-parent="#accordion" href="#collapseNotesAjout" aria-expanded="false" aria-controls="collapseTwo">
|
||||
{% bootstrap_icon "plus" %} Ajouter une note
|
||||
</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div id="collapseNotesAjout" class="panel-collapse collapse" role="tabpanel" aria-labelledby="notesAjoutHeading">
|
||||
<div class="panel-body">
|
||||
<form method="POST" action="">{% csrf_token %}
|
||||
{% bootstrap_form note_form show_label=False %}
|
||||
</div>
|
||||
<div class="panel-footer text-right">
|
||||
{% bootstrap_button "Enregistrer" button_type="submit" %}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block right_column %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title">Informations</h4>
|
||||
</div>
|
||||
{% include "notes/details_sujet_inner.html" %}
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"><h4 class="panel-title">Statistiques</h4></div>
|
||||
<div id="stats-content">
|
||||
{% include "statistiques/fiche_stats_details.html" with object=sujet.statistiques %}
|
||||
</div>
|
||||
<div class="panel-footer text-right">
|
||||
<span class="text-right" id="normal-buttons">
|
||||
<p>{% bootstrap_icon "tasks" %} {{ sujet.statistiques.info_completed }}%
|
||||
<span class="btn btn-primary btn-sm" id="update-stats">Mettre à jour</span></p>
|
||||
</span>
|
||||
<span class="text-right" id="update-buttons">
|
||||
<label for="submit-form" class="btn btn-primary">{% bootstrap_icon "floppy-save" %} Enregistrer</label>
|
||||
<span class="btn btn-primary btn-sm" id="cancel">Annuler</span>
|
||||
</span>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
$(function() {
|
||||
$("#update-buttons").hide();
|
||||
$("#update-stats").click(function() {
|
||||
$("#stats-content").load("{% url "statistiques:update" sujet.pk %}");
|
||||
$("#normal-buttons").hide();
|
||||
$("#update-buttons").show();
|
||||
});
|
||||
$("#cancel").click(function() {
|
||||
$("#stats-content").load("{% url "statistiques:details" sujet.pk %}");
|
||||
$("#update-buttons").hide();
|
||||
$("#normal-buttons").show();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block sidebar %}
|
||||
{{ block.super }}
|
||||
<hr />
|
||||
{% if user.is_superuser %}
|
||||
<div class="panel panel-primary text-right"><div class="panel-heading"><h4 class="panel-title"><strong>Administration :</strong></h4></div>
|
||||
<div class="panel-body">
|
||||
<div class="btn-group" role="group" aria-label="...">
|
||||
<a href="{% url 'admin:notes_note_changelist' %}?sujet__exact={{sujet.pk}}" class="btn btn-primary">{% bootstrap_icon "wrench" %} Éditer les notes</a>
|
||||
<a href="{% url 'notes:sujets-merge' pk=object.pk %}" class="btn btn-default">{% bootstrap_icon "paste" %} Fusionner</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
22
notes/templates/notes/details_sujet_inner.html
Normal file
22
notes/templates/notes/details_sujet_inner.html
Normal file
@@ -0,0 +1,22 @@
|
||||
<div id="sujet-content">
|
||||
<table class="table table-striped">
|
||||
{% with "-" as none %}
|
||||
<tr><th>Nom</th><th>Surnom</th><th>Prénom</th></tr>
|
||||
<tr><td>{{ sujet.nom|default:none }}</td><td>{{ sujet.surnom|default:none}}</td><td>{{ sujet.prenom|default:none}}</td></tr>
|
||||
<tr><th>Âge</th><th colspan="2">Première rencontre</th></tr>
|
||||
<tr><td>{{ sujet.age|default_if_none:none }}</td><td colspan="2">{{ sujet.premiere_rencontre|default_if_none:none }}</td></tr>
|
||||
{% endwith %}
|
||||
</table>
|
||||
<div class="panel-footer text-right" id="sujet-buttons">
|
||||
<span class="text-right"><span class="btn btn-primary btn-sm" id="update-sujet">Mettre à jour</a></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
$(function() {
|
||||
$("#update-sujet").click(function() {
|
||||
console.log("update !")
|
||||
$("#sujet-content").load("{% url "notes:update-sujet" sujet.pk %}");
|
||||
});
|
||||
});
|
||||
</script>
|
||||
24
notes/templates/notes/details_sujet_update.html
Normal file
24
notes/templates/notes/details_sujet_update.html
Normal file
@@ -0,0 +1,24 @@
|
||||
{% load bootstrap3 %}
|
||||
<form action="{% url "notes:update-sujet" form.instance.pk %}" method="post">{% csrf_token %}
|
||||
<table class="table table-striped">
|
||||
{% with "-" as none %}
|
||||
<tr><th>Nom</th><th>Surnom</th><th>Prénom</th></tr>
|
||||
<tr><td>{% bootstrap_field form.nom show_label=False %}</td><td>{% bootstrap_field form.surnom show_label=False %}</td><td>{% bootstrap_field form.prenom show_label=False %}</td></tr>
|
||||
<tr><th>Âge</th><th>Genre</th><th>Première rencontre</th></tr>
|
||||
<tr><td>{% bootstrap_field form.age show_label=False %}</td><td>{% bootstrap_field form.genre show_label=False %}</td><td>{% bootstrap_field form.premiere_rencontre show_label=False %}</td></tr>
|
||||
{% endwith %}
|
||||
</table>
|
||||
<div class="panel-footer text-right">
|
||||
<span class="text-right">{% bootstrap_button "Enregistrer" button_type="submit" %}
|
||||
<span class="btn btn-primary btn-sm" id="cancel">Annuler</span></span>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script type="text/javascript">
|
||||
$(function() {
|
||||
$("#cancel").click(function() {
|
||||
console.log("cancelled !")
|
||||
$("#sujet-content").load("{% url "notes:sujet" form.instance.pk %}");
|
||||
});
|
||||
});
|
||||
</script>
|
||||
12
notes/templates/notes/form_appel.html
Normal file
12
notes/templates/notes/form_appel.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading" role="tab" id="appelHeading">
|
||||
<h4 class="panel-title">
|
||||
<a role="button" data-toggle="collapse" data-parent="#accordion" href="#appelCollapse" aria-expanded="true" aria-controls="collapseOne">
|
||||
<span class="glyphicon glyphicon-earphone"></span> Appel</a></h4>
|
||||
</div>
|
||||
<div id="appelCollapse" class="panel-collapse collapse" role="tabpanel" aria-labelledby="appelHeading">
|
||||
<div class="panel-body">
|
||||
{% include "notes/form_appel_inner.html" %}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
17
notes/templates/notes/form_appel_inner.html
Normal file
17
notes/templates/notes/form_appel_inner.html
Normal file
@@ -0,0 +1,17 @@
|
||||
{% load bootstrap3 %}
|
||||
<form action="" method="POST">{% csrf_token %}
|
||||
{% with "inline" as layout %}
|
||||
<div class="form-{{layout}} well well-sm text-center">
|
||||
{% bootstrap_field form.created_date layout=layout %}
|
||||
{% bootstrap_field form.created_time layout=layout %}
|
||||
{% bootstrap_field form.entrant layout=layout %}
|
||||
</div> {% endwith %}
|
||||
{% with "horizontal" as layout %}
|
||||
<div class="form-{{layout}}">
|
||||
{% bootstrap_field form.sujet layout=layout %}
|
||||
{% bootstrap_field form.text layout=layout %}
|
||||
</div> {% endwith %}
|
||||
<div class="pull-right">{% bootstrap_button "Enregistrer l'appel" button_type="submit" %}</div>
|
||||
</form>
|
||||
{{ form.media.js }}{{ form.media.css }}
|
||||
|
||||
12
notes/templates/notes/form_signalement.html
Normal file
12
notes/templates/notes/form_signalement.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading" role="tab" id="signalementHeading">
|
||||
<h4 class="panel-title">
|
||||
<a role="button" data-toggle="collapse" data-parent="#accordion" href="#signalementCollapse" aria-expanded="true" aria-controls="collapseOne">
|
||||
<span class="glyphicon glyphicon-warning-sign"></span> Signalement</a></h4>
|
||||
</div>
|
||||
<div id="signalementCollapse" class="panel-collapse collapse" role="tabpanel" aria-labelledby="signalementHeading">
|
||||
<div class="panel-body">
|
||||
{% include "notes/form_signalement_inner.html" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
27
notes/templates/notes/form_signalement_inner.html
Normal file
27
notes/templates/notes/form_signalement_inner.html
Normal file
@@ -0,0 +1,27 @@
|
||||
{% load bootstrap3 %}
|
||||
<form action="" method="POST">{% csrf_token %}
|
||||
{% bootstrap_form_errors form %}
|
||||
{% with "inline" as layout %}
|
||||
<div class="form-group form-{{layout}} well">
|
||||
<label class="control-label col-md-2" for="id_source">
|
||||
Source
|
||||
</label>
|
||||
{% bootstrap_field form.source layout=layout %}
|
||||
{% bootstrap_field form.created_date layout=layout %}
|
||||
{% bootstrap_field form.created_time layout=layout %}
|
||||
</div> {% endwith %}
|
||||
<div class="well">
|
||||
{% with "horizontal" as layout %}
|
||||
<div class="form-group form-{{layout}}">
|
||||
{% bootstrap_field form.nom layout=layout %}
|
||||
{% bootstrap_field form.prenom layout=layout %}
|
||||
{% bootstrap_field form.genre layout=layout %}
|
||||
{% bootstrap_field form.age layout=layout %}
|
||||
</div> {% endwith %}
|
||||
</div>
|
||||
{% with "horizontal" as layout %}
|
||||
<div class="form-group form-{{layout}}">
|
||||
{% bootstrap_field form.text layout=layout %}
|
||||
</div> {% endwith %}
|
||||
<div class="pull-right">{% bootstrap_button "Enregistrer le signalement" button_type="submit" %}</div>
|
||||
</form>
|
||||
16
notes/templates/notes/index.html
Normal file
16
notes/templates/notes/index.html
Normal file
@@ -0,0 +1,16 @@
|
||||
{% extends "notes/base.html" %}
|
||||
|
||||
{% block title %}{{block.super}} Tableau de bord{% endblock %}
|
||||
|
||||
{% block breadcrumbs %}
|
||||
{{ block.super }}
|
||||
<li>Tableau de bord</li>
|
||||
{% endblock %}
|
||||
|
||||
{% block page_content %}
|
||||
<div class="col-md-12 col-lg-6">
|
||||
<h2 class="page-header">TODO</h2>
|
||||
<p>Liste des sujets qui ont été ajoutés depuis la dernière connexion</p>
|
||||
<p>Liste des compte-rendus qui ont été ajoutés depuis la dernière connexion</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
34
notes/templates/notes/liste.html
Normal file
34
notes/templates/notes/liste.html
Normal file
@@ -0,0 +1,34 @@
|
||||
{% extends "notes/base.html" %}
|
||||
{% load tables %}
|
||||
|
||||
{% block sidebar %}
|
||||
{{ block.super }}
|
||||
{% block sidebar_insert %}{% endblock %}
|
||||
{% if filters %}
|
||||
<div class="well">
|
||||
<h4 class="text-right"><span class="glyphicon glyphicon-filter"></span> <strong>Filtres</strong></h4>
|
||||
<ul class="nav nav-pills nav-stacked text-right">
|
||||
{% for filter in filters %}
|
||||
<li role="presentation" {% if filter.active %} class="active" {% endif %}>
|
||||
<a href="?filter={{filter.parameter_name}}">{{ filter.title }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block page_content %}
|
||||
{% block search %}{% endblock %}
|
||||
<!-- Table -->
|
||||
{% table object_list cols=3 cell_template=table_cell_template header=table_header %}
|
||||
{% if is_paginated %}
|
||||
<div class="text-center">
|
||||
<ul class="pagination">{% with request.GET.filter as filter %}
|
||||
{% for num in page_obj.paginator.page_range %}
|
||||
<li {% if page_obj.number == num %} class="active" {%endif%}><a href="?{% if filter %}filter={{ filter }}&{%endif%}page={{num}}">{{num}}</a></li>
|
||||
{%endfor%}{% endwith %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
14
notes/templates/notes/liste_maraudes.html
Normal file
14
notes/templates/notes/liste_maraudes.html
Normal file
@@ -0,0 +1,14 @@
|
||||
{% extends "notes/liste.html" %}
|
||||
|
||||
{% block title %}
|
||||
{{ block.super }} Liste des maraudes
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumbs %}
|
||||
{{block.super}}
|
||||
<li><a href="{% url "notes:liste-maraude" %}">Maraudes</a></li>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
||||
|
||||
39
notes/templates/notes/liste_sujets.html
Normal file
39
notes/templates/notes/liste_sujets.html
Normal file
@@ -0,0 +1,39 @@
|
||||
{% extends "notes/liste.html" %}
|
||||
|
||||
{% block title %}{{block.super}} Liste des sujets {% endblock %}
|
||||
|
||||
{% block breadcrumbs %}
|
||||
{{ block.super }}
|
||||
<li><a href="{% url "notes:liste-sujet" %}">Sujets</a></li>
|
||||
{% if query_text %}<li>'{{query_text}}'</li>{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block sidebar_insert %}
|
||||
<div class="panel panel-primary text-right">
|
||||
<div class="panel-body">
|
||||
<h4><strong>Rechercher</strong></h4>
|
||||
<form action="{% url "notes:liste-sujet" %}" method="POST" class="form form-group">{% csrf_token %}
|
||||
<div class="input-group">
|
||||
<input type="text" name="q" class="form-control" placeholder="Chercher un sujet" aria-describedby="basic-addon1">
|
||||
<span class="input-group-btn">
|
||||
<button type="submit" class="btn btn-primary"><span class=" glyphicon glyphicon-search"></span> </button>
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
<hr/>
|
||||
<h4><strong>Outils</strong></h4>
|
||||
<a class="btn btn-primary" href="{% url "notes:create-sujet" %}">
|
||||
<span class="glyphicon glyphicon-plus"></span> Ajouter un sujet</a> </div></div>
|
||||
{% endblock %}
|
||||
|
||||
{% block search %}
|
||||
{% if query_text %}<div class="well well-sm text-center">
|
||||
<h4><span class="label label-primary">'{{query_text}}'</span>
|
||||
<span class="label label-danger">
|
||||
{% if not object_list %}Aucun résultat
|
||||
{% else %} {{ object_list.count }} résultats
|
||||
{% endif %}
|
||||
</span></h4>
|
||||
</div>{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
13
notes/templates/notes/menu.html
Normal file
13
notes/templates/notes/menu.html
Normal file
@@ -0,0 +1,13 @@
|
||||
{% load navbar %}
|
||||
<ul class="nav nav-pills nav-stacked text-right">
|
||||
<li role="presentation" {% active namespace="notes" viewname="liste-sujet" %}>
|
||||
<a href="{% url "notes:liste-sujet" %}">Par sujet
|
||||
<span class="glyphicon glyphicon-user"></span>
|
||||
</a>
|
||||
</li>
|
||||
<li role="presentation" {% active namespace="notes" viewname="liste-maraude" %}>
|
||||
<a href="{% url "notes:liste-maraude" %}">Par maraude
|
||||
<span class="glyphicon glyphicon-road"></span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
9
notes/templates/notes/sujet_create.html
Normal file
9
notes/templates/notes/sujet_create.html
Normal file
@@ -0,0 +1,9 @@
|
||||
{% extends "notes/base.html" %}
|
||||
|
||||
{% block page_content %}
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
{% include "notes/sujet_create_inner.html" %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
8
notes/templates/notes/sujet_create_inner.html
Normal file
8
notes/templates/notes/sujet_create_inner.html
Normal file
@@ -0,0 +1,8 @@
|
||||
{% load bootstrap3 %}
|
||||
<form class="form-horizontal" action="{% url "notes:create-sujet" %}" method="post">{% csrf_token %}
|
||||
{% bootstrap_form form layout="horizontal"%}
|
||||
<div class="pull-right">
|
||||
{% bootstrap_button "Ajouter un sujet" button_type="submit" button_class="btn btn-primary" icon="plus" %}
|
||||
</div>
|
||||
{% if next %}<input type="text" hidden=True name="next" value="{{ next }}" />{%endif%}
|
||||
</form>
|
||||
12
notes/templates/notes/sujet_merge.html
Normal file
12
notes/templates/notes/sujet_merge.html
Normal file
@@ -0,0 +1,12 @@
|
||||
{% extends "notes/base.html" %}
|
||||
|
||||
{% block breadcrumbs %}
|
||||
{{ block.super }}
|
||||
<li><a href="{% url "notes:liste-sujet" %}">Sujets</a></li>
|
||||
<li><a href="{% url "notes:details-sujet" object.pk %}">{{ object }}</a></li>
|
||||
<li>Fusionner vers...</li>
|
||||
{% endblock %}
|
||||
|
||||
{% block page_content %}
|
||||
{% include 'notes/sujet_merge_inner.html' %}
|
||||
{% endblock %}
|
||||
8
notes/templates/notes/sujet_merge_inner.html
Normal file
8
notes/templates/notes/sujet_merge_inner.html
Normal file
@@ -0,0 +1,8 @@
|
||||
{% load bootstrap3 %}
|
||||
<p> Vous allez fusionner la fiche de <strong>{{object}}</strong> et ses {{object.notes.count}} notes vers :</p>
|
||||
<form action="{% url 'notes:sujets-merge' pk=object.pk %}" method="POST">
|
||||
{% csrf_token %}
|
||||
{{ form.media }}
|
||||
{% bootstrap_field form.sujet %}
|
||||
<div class="pull-right">{% bootstrap_button 'Fusionner' button_type='submit' icon='paste' %}</div>
|
||||
</form>
|
||||
6
notes/templates/notes/table_cell_maraudes.html
Normal file
6
notes/templates/notes/table_cell_maraudes.html
Normal file
@@ -0,0 +1,6 @@
|
||||
{% if object.est_terminee %}<a href="{% url 'notes:details-maraude' object.id %}" class="btn btn-link">
|
||||
{% else %}<a href="#" class="btn btn-link disabled">{% endif %}{{ object }}</a>
|
||||
<div class="pull-right">
|
||||
<span class="label label-info">{{ object.binome }} & {{ object.referent }}</span>
|
||||
{% if object.est_terminee %}<span class="label label-success">{{object.rencontres.count}} rencontres</span>{% endif %}
|
||||
</div>
|
||||
9
notes/templates/notes/table_cell_sujets.html
Normal file
9
notes/templates/notes/table_cell_sujets.html
Normal file
@@ -0,0 +1,9 @@
|
||||
<a href="{% url 'notes:details-sujet' object.pk %}" class="btn btn-link">{{object}}</a>
|
||||
<div class="pull-right" style="padding-right: 20px;">
|
||||
<span class="label label-info">{{ object.notes.count }} notes</span>
|
||||
{% with object.statistiques.info_completed as completed %}
|
||||
<span class="label label-{% if completed <= 80 %}warning{%else%}success{%endif%}">{{ completed }} %</span>
|
||||
{% endwith %}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ register = template.Library()
|
||||
|
||||
@register.inclusion_tag("notes/table_inline.html")
|
||||
def inline_table(note, header=None):
|
||||
from maraudes.models import Maraude
|
||||
bg_color, bg_label_color = note.bg_colors
|
||||
|
||||
if not header:
|
||||
@@ -14,10 +15,14 @@ def inline_table(note, header=None):
|
||||
|
||||
if header == "date":
|
||||
header_field = "created_date"
|
||||
link = None
|
||||
try:
|
||||
maraude = Maraude.objects.get(date=note.created_date)
|
||||
link = maraude.get_absolute_url()
|
||||
except Maraude.DoesNotExist:
|
||||
link = None
|
||||
elif header == "sujet":
|
||||
header_field = "sujet"
|
||||
link = reverse("suivi:details", kwargs={'pk': note.sujet.pk})
|
||||
link = note.sujet.get_absolute_url()
|
||||
|
||||
header = getattr(note, header_field)
|
||||
|
||||
|
||||
@@ -1,7 +1,28 @@
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.test import TestCase
|
||||
from .models import Note
|
||||
from .models import Note, Sujet
|
||||
# Create your tests here.
|
||||
|
||||
# TODO: test 'actions.py'
|
||||
|
||||
|
||||
class SujetModelTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
pass
|
||||
|
||||
def test_statistiques_is_autocreated(self):
|
||||
new_sujet = Sujet.objects.create(prenom="Astérix")
|
||||
self.assertIsNotNone(new_sujet.statistiques)
|
||||
|
||||
def test_at_least_one_in_name_surname_firstname(self):
|
||||
self.assertIsInstance(Sujet.objects.create(nom="DeGaulle"), Sujet)
|
||||
self.assertIsInstance(Sujet.objects.create(surnom="Le Gaulois"), Sujet)
|
||||
self.assertIsInstance(Sujet.objects.create(prenom="Astérix"), Sujet)
|
||||
|
||||
def test_raises_validation_error_if_no_name(self):
|
||||
with self.assertRaises(ValidationError):
|
||||
Sujet.objects.create(age=25)
|
||||
|
||||
class NoteManagerTestCase(TestCase):
|
||||
""" managers.NoteManager Test Case """
|
||||
|
||||
16
notes/urls.py
Normal file
16
notes/urls.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from django.conf.urls import url
|
||||
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^$', views.IndexView.as_view(), name="index"),
|
||||
url(r'sujets/$', views.SujetListView.as_view(), name="liste-sujet"),
|
||||
url(r'sujets/(?P<pk>[0-9]+)/$', views.SuiviSujetView.as_view(), name="details-sujet"),
|
||||
url(r'sujets/(?P<pk>[0-9]+)/merge/$', views.MergeView.as_view(), name="sujets-merge"),
|
||||
url(r'maraudes/$', views.MaraudeListView.as_view(), name="liste-maraude"),
|
||||
url(r'maraudes/(?P<pk>[0-9]+)/$', views.CompteRenduDetailsView.as_view(), name="details-maraude"),
|
||||
# Manage Sujet
|
||||
url(r'sujets/create/$', views.SujetCreateView.as_view(), name="create-sujet"),
|
||||
url(r'sujet/(?P<pk>[0-9]+)/$', views.SujetAjaxDetailsView.as_view(), name="sujet"),
|
||||
url(r'sujet/(?P<pk>[0-9]+)/update/$', views.SujetAjaxUpdateView.as_view(), name="update-sujet"),
|
||||
]
|
||||
254
notes/views.py
Normal file
254
notes/views.py
Normal file
@@ -0,0 +1,254 @@
|
||||
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 utilisateurs.mixins import MaraudeurMixin
|
||||
from maraudes.models import Maraude, CompteRendu
|
||||
from .models import Sujet, Note
|
||||
from .forms import SujetCreateForm, AutoNoteForm, SelectSujetForm
|
||||
from .mixins import NoteFormMixin
|
||||
from .actions import merge_two
|
||||
|
||||
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
|
||||
self.parameter_name = name
|
||||
self.active = False
|
||||
self._filter_func = filter_func
|
||||
|
||||
def filter(self, qs):
|
||||
return self._filter_func(qs)
|
||||
|
||||
|
||||
|
||||
class ListView(MaraudeurMixin, generic.ListView):
|
||||
""" Base ListView for Maraude and Sujet lists """
|
||||
paginate_by = 30
|
||||
cell_template = None
|
||||
|
||||
filters = []
|
||||
active_filter = None
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._filters = {}
|
||||
|
||||
if self.filters:
|
||||
for i, (title, func) in enumerate(self.filters):
|
||||
_id = "filter_%i" % i
|
||||
self._filters[_id] = Filter(title, _id, func)
|
||||
|
||||
def get(self, request, **kwargs):
|
||||
filter_name = self.request.GET.get('filter', None)
|
||||
if filter_name:
|
||||
self.active_filter = self._filters.get(filter_name, None)
|
||||
if self.active_filter:
|
||||
self.active_filter.active = True
|
||||
|
||||
return super().get(request, **kwargs)
|
||||
|
||||
def get_queryset(self):
|
||||
qs = super().get_queryset()
|
||||
if self.active_filter:
|
||||
qs = self.active_filter.filter(qs)
|
||||
return qs
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["filters"] = self._filters.values()
|
||||
context["table_cell_template"] = getattr(self, 'cell_template', None)
|
||||
context["table_header"] = getattr(self, 'table_header', None)
|
||||
return context
|
||||
|
||||
|
||||
class MaraudeListView(ListView):
|
||||
""" Vue de la liste des compte-rendus de maraude """
|
||||
|
||||
model = CompteRendu
|
||||
template_name = "notes/liste_maraudes.html"
|
||||
cell_template = "notes/table_cell_maraudes.html"
|
||||
table_header = "Liste des maraudes"
|
||||
|
||||
queryset = Maraude.objects.get_past().order_by("-date")
|
||||
|
||||
filters = [
|
||||
("Ce mois-ci", lambda qs: qs.filter(date__month=timezone.now().date().month)),
|
||||
]
|
||||
|
||||
|
||||
class SujetListView(ListView):
|
||||
#ListView
|
||||
model = Sujet
|
||||
template_name = "notes/liste_sujets.html"
|
||||
cell_template = "notes/table_cell_sujets.html"
|
||||
table_header = "Liste des sujets"
|
||||
|
||||
|
||||
def info_completed_filter(qs):
|
||||
excluded_set = set()
|
||||
for sujet in qs:
|
||||
if sujet.statistiques.info_completed >= 50:
|
||||
excluded_set.add(sujet.pk)
|
||||
|
||||
return qs.exclude(pk__in=excluded_set)
|
||||
|
||||
filters = [
|
||||
("Rencontré(e)s cette année", lambda qs: qs.filter(premiere_rencontre__year=timezone.now().date().year)),
|
||||
("Statistiques incomplètes", info_completed_filter),
|
||||
]
|
||||
|
||||
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))
|
||||
if results.count() == 1:
|
||||
return redirect(results[0].get_absolute_url())
|
||||
self.queryset = results
|
||||
return self.get(request, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['query_text'] = self.request.POST.get('q', None)
|
||||
return context
|
||||
|
||||
|
||||
class DetailView(MaraudeurMixin, generic.DetailView):
|
||||
template_name = "notes/details.html"
|
||||
|
||||
class CompteRenduDetailsView(DetailView):
|
||||
""" Vue détaillé d'un compte-rendu de maraude """
|
||||
|
||||
model = CompteRendu
|
||||
context_object_name = "maraude"
|
||||
template_name = "notes/details_maraude.html"
|
||||
|
||||
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['next_maraude'] = Maraude.objects.get_future(
|
||||
date=self.object.date + datetime.timedelta(1)
|
||||
).filter(
|
||||
heure_fin__isnull=False
|
||||
).first()
|
||||
context['prev_maraude'] = Maraude.objects.get_past(
|
||||
date=self.object.date
|
||||
).filter(
|
||||
heure_fin__isnull=False
|
||||
).last()
|
||||
return context
|
||||
|
||||
|
||||
class SuiviSujetView(NoteFormMixin, DetailView):
|
||||
#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
|
||||
model = Sujet
|
||||
template_name = "notes/details_sujet.html"
|
||||
context_object_name = "sujet"
|
||||
# Paginator
|
||||
per_page = 5
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
self.paginator = Paginator(
|
||||
self.get_object().notes.by_date(reverse=True),
|
||||
self.per_page
|
||||
)
|
||||
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)
|
||||
try:
|
||||
notes = self.paginator.page(self.page)
|
||||
except PageNotAnInteger:
|
||||
notes = self.paginator.page(1)
|
||||
except EmptyPage:
|
||||
notes = self.paginator.page(self.paginator.num_pages)
|
||||
context['notes'] = notes
|
||||
return context
|
||||
|
||||
|
||||
### Sujet Management Views
|
||||
|
||||
class SujetAjaxDetailsView(generic.DetailView):
|
||||
#DetailView
|
||||
template_name = "notes/details_sujet_inner.html"
|
||||
model = Sujet
|
||||
|
||||
http_method_names = ["get"]
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
""" Redirect to complete details view if request is not ajax """
|
||||
if not self.request.is_ajax():
|
||||
return redirect("notes:details-sujet", pk=self.get_object().pk)
|
||||
return super().get(*args, **kwargs)
|
||||
|
||||
class SujetAjaxUpdateView(generic.edit.UpdateView):
|
||||
#UpdateView
|
||||
template_name = "notes/details_sujet_update.html"
|
||||
model = Sujet
|
||||
fields = '__all__'
|
||||
|
||||
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
|
||||
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
|
||||
return context
|
||||
|
||||
class MergeView(generic.DetailView, generic.FormView):
|
||||
""" Implement actions.merge_two as a view """
|
||||
|
||||
template_name = "notes/sujet_merge.html"
|
||||
model = Sujet
|
||||
form_class = SelectSujetForm
|
||||
|
||||
def form_valid(self, form):
|
||||
slave = self.get_object()
|
||||
master = form.cleaned_data['sujet']
|
||||
try:
|
||||
merge_two(master, slave)
|
||||
except Exception as e:
|
||||
logger.error("Merge: ", e)
|
||||
messages.error(self.request, "La fusion vers %s a échoué !" % master)
|
||||
return redirect(slave)
|
||||
messages.success(self.request, "%s vient d'être fusionné" % slave)
|
||||
return redirect(master)
|
||||
Reference in New Issue
Block a user