Improve survey, comment code

This commit is contained in:
Yohann D'ANELLO 2020-04-20 01:26:53 +02:00
parent 473d3c3546
commit 16af9ac0ea
15 changed files with 274 additions and 67 deletions

View File

@ -4,7 +4,7 @@ from datetime import timedelta, datetime
from django import forms from django import forms
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.utils.translation import gettext as _ from django.utils.translation import gettext_lazy as _
from member.models import Club from member.models import Club
from note.models import NoteUser, Note from note.models import NoteUser, Note
from note_kfet.inputs import DateTimePickerInput, Autocomplete from note_kfet.inputs import DateTimePickerInput, Autocomplete

View File

@ -46,6 +46,7 @@ class UserCreateView(CreateView):
del wei_form.fields["user"] del wei_form.fields["user"]
del wei_form.fields["caution_check"] del wei_form.fields["caution_check"]
del wei_form.fields["first_year"] del wei_form.fields["first_year"]
del wei_form.fields["information_json"]
context["wei_form"] = wei_form context["wei_form"] = wei_form
context["wei_registration_form"] = WEISignupForm() context["wei_registration_form"] = WEISignupForm()

View File

@ -3,11 +3,11 @@
from django.contrib import admin from django.contrib import admin
from .models import WEIClub, WEIRegistration, WEIRole, Bus, BusTeam from .models import WEIClub, WEIRegistration, WEIMembership, WEIRole, Bus, BusTeam
admin.site.register(WEIClub) admin.site.register(WEIClub)
admin.site.register(WEIRegistration) admin.site.register(WEIRegistration)
admin.site.register(WEIMembership)
admin.site.register(WEIRole) admin.site.register(WEIRole)
admin.site.register(Bus) admin.site.register(Bus)
admin.site.register(BusTeam) admin.site.register(BusTeam)

View File

@ -1,6 +1,6 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.filters import SearchFilter from rest_framework.filters import SearchFilter
from api.viewsets import ReadProtectedModelViewSet from api.viewsets import ReadProtectedModelViewSet
@ -16,8 +16,9 @@ class WEIClubViewSet(ReadProtectedModelViewSet):
""" """
queryset = WEIClub.objects.all() queryset = WEIClub.objects.all()
serializer_class = WEIClubSerializer serializer_class = WEIClubSerializer
filter_backends = [SearchFilter] filter_backends = [SearchFilter, DjangoFilterBackend]
search_fields = ['$name', ] search_fields = ['$name', ]
filterset_fields = ['name', 'year', ]
class BusViewSet(ReadProtectedModelViewSet): class BusViewSet(ReadProtectedModelViewSet):
@ -28,8 +29,9 @@ class BusViewSet(ReadProtectedModelViewSet):
""" """
queryset = Bus.objects.all() queryset = Bus.objects.all()
serializer_class = BusSerializer serializer_class = BusSerializer
filter_backends = [SearchFilter] filter_backends = [SearchFilter, DjangoFilterBackend]
search_fields = ['$name', ] search_fields = ['$name', ]
filterset_fields = ['name', 'wei', ]
class BusTeamViewSet(ReadProtectedModelViewSet): class BusTeamViewSet(ReadProtectedModelViewSet):
@ -40,5 +42,6 @@ class BusTeamViewSet(ReadProtectedModelViewSet):
""" """
queryset = BusTeam.objects.all() queryset = BusTeam.objects.all()
serializer_class = BusTeamSerializer serializer_class = BusTeamSerializer
filter_backends = [SearchFilter] filter_backends = [SearchFilter, DjangoFilterBackend]
search_fields = ['$name', ] search_fields = ['$name', ]
filterset_fields = ['name', 'bus', 'wei', ]

View File

@ -1,8 +1,8 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from .registration import * from .registration import WEIForm, WEIRegistrationForm, WEIMembershipForm, BusForm, BusTeamForm
from .surveys import * from .surveys import WEISurvey, WEISurveyInformation, WEISurveyAlgorithm, CurrentSurvey
__all__ = [ __all__ = [
'WEIForm', 'WEIRegistrationForm', 'WEIMembershipForm', 'BusForm', 'BusTeamForm', 'WEIForm', 'WEIRegistrationForm', 'WEIMembershipForm', 'BusForm', 'BusTeamForm',

View File

@ -3,9 +3,10 @@
from django import forms from django import forms
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.utils.translation import gettext_lazy as _
from note_kfet.inputs import AmountInput, DatePickerInput, Autocomplete, ColorWidget from note_kfet.inputs import AmountInput, DatePickerInput, Autocomplete, ColorWidget
from wei.models import WEIClub, WEIRegistration, Bus, BusTeam, WEIMembership, WEIRole from ..models import WEIClub, WEIRegistration, Bus, BusTeam, WEIMembership, WEIRole
class WEIForm(forms.ModelForm): class WEIForm(forms.ModelForm):
@ -25,7 +26,7 @@ class WEIForm(forms.ModelForm):
class WEIRegistrationForm(forms.ModelForm): class WEIRegistrationForm(forms.ModelForm):
class Meta: class Meta:
model = WEIRegistration model = WEIRegistration
exclude = ('wei', 'information_json', ) exclude = ('wei', )
widgets = { widgets = {
"user": Autocomplete( "user": Autocomplete(
User, User,
@ -42,6 +43,12 @@ class WEIRegistrationForm(forms.ModelForm):
class WEIMembershipForm(forms.ModelForm): class WEIMembershipForm(forms.ModelForm):
roles = forms.ModelMultipleChoiceField(queryset=WEIRole.objects) roles = forms.ModelMultipleChoiceField(queryset=WEIRole.objects)
def clean(self):
cleaned_data = super().clean()
if cleaned_data["team"] is not None and cleaned_data["team"].bus != cleaned_data["bus"]:
self.add_error('bus', _("This team doesn't belong to the given team."))
return cleaned_data
class Meta: class Meta:
model = WEIMembership model = WEIMembership
fields = ('roles', 'bus', 'team',) fields = ('roles', 'bus', 'team',)

View File

@ -1,46 +1,18 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from typing import Optional
from django.db.models import QuerySet
from django.forms import Form
from ...models import WEIClub, WEIRegistration, Bus from ...models import WEIClub, WEIRegistration, Bus
class WEISurvey:
year = None
step = 0
def __init__(self, registration):
self.registration = registration
self.information = self.get_survey_information_class()(registration)
def get_wei(self):
return WEIClub.objects.get(year=self.year)
def get_survey_information_class(self):
raise NotImplementedError
def get_form_class(self):
raise NotImplementedError
def update_form(self, form):
pass
@staticmethod
def get_algorithm_class():
raise NotImplementedError
def form_valid(self, form):
raise NotImplementedError
def save(self):
self.information.save(self.registration)
def select_bus(self, bus):
self.information.selected_bus_pk = bus.pk
self.information.selected_bus_name = bus.name
self.information.valid = True
class WEISurveyInformation: class WEISurveyInformation:
"""
Abstract data of the survey.
"""
valid = False valid = False
selected_bus_pk = None selected_bus_pk = None
selected_bus_name = None selected_bus_name = None
@ -48,22 +20,142 @@ class WEISurveyInformation:
def __init__(self, registration): def __init__(self, registration):
self.__dict__.update(registration.information) self.__dict__.update(registration.information)
def get_selected_bus(self): def get_selected_bus(self) -> Optional[Bus]:
"""
If the algorithm ran, return the prefered bus according to the survey.
In the other case, return None.
"""
if not self.valid: if not self.valid:
return None return None
return Bus.objects.get(pk=self.selected_bus_pk) return Bus.objects.get(pk=self.selected_bus_pk)
def save(self, registration): def save(self, registration) -> None:
"""
Store the data of the survey into the database, with the information of the registration.
"""
registration.information = self.__dict__ registration.information = self.__dict__
registration.save() registration.save()
class WEISurveyAlgorithm: class WEISurveyAlgorithm:
def get_survey_class(self): """
Abstract algorithm that attributes a bus to each new member.
"""
@classmethod
def get_survey_class(cls):
"""
The class of the survey associated with this algorithm.
"""
raise NotImplementedError raise NotImplementedError
def get_registrations(self): @classmethod
return WEIRegistration.objects.filter(wei__year=self.get_survey_class().year, first_year=True).all() def get_registrations(cls) -> QuerySet:
"""
Queryset of all first year registrations
"""
return WEIRegistration.objects.filter(wei__year=cls.get_survey_class().get_year(), first_year=True)
def run_algorithm(self): @classmethod
def get_buses(cls) -> QuerySet:
"""
Queryset of all buses of the associated wei.
"""
return Bus.objects.filter(wei__year=cls.get_survey_class().get_year())
def run_algorithm(self) -> None:
"""
Once this method implemented, run the algorithm that attributes a bus to each first year member.
This method can be run in command line through ``python manage.py wei_algorithm``
See ``wei.management.commmands.wei_algorithm``
This method must call Survey.select_bus for each survey.
"""
raise NotImplementedError raise NotImplementedError
class WEISurvey:
"""
Survey associated to a first year WEI registration.
The data is stored into WEISurveyInformation, this class acts as a manager.
This is an abstract class: this has to be extended each year to implement custom methods.
"""
def __init__(self, registration: WEIRegistration):
self.registration = registration
self.information = self.get_survey_information_class()(registration)
@classmethod
def get_year(cls) -> int:
"""
Get year of the wei concerned by the type of the survey.
"""
raise NotImplementedError
@classmethod
def get_wei(cls) -> WEIClub:
"""
The WEI associated to this kind of survey.
"""
return WEIClub.objects.get(year=cls.get_year())
@classmethod
def get_survey_information_class(cls):
"""
The class of the data (extending WEISurveyInformation).
"""
raise NotImplementedError
def get_form_class(self) -> Form:
"""
The form class of the survey.
This is proper to the status of the survey: the form class can evolve according to the progress of the survey.
"""
raise NotImplementedError
def update_form(self, form) -> None:
"""
Once the form is instanciated, the information can be updated with the information of the registration
and the information of the survey.
This method is called once the form is created.
"""
pass
def form_valid(self, form) -> None:
"""
Called when the information of the form are validated.
This method should update the information of the survey.
"""
raise NotImplementedError
def is_complete(self) -> bool:
"""
Return True if the survey is complete.
If the survey is complete, then the button "Next" will display some text for the end of the survey.
If not, the survey is reloaded and continues.
"""
raise NotImplementedError
def save(self) -> None:
"""
Store the information of the survey into the database.
"""
self.information.save(self.registration)
self.registration.save()
@classmethod
def get_algorithm_class(cls):
"""
Algorithm class associated to the survey.
The algorithm, extending WEISurveyAlgorithm, should associate a bus to each first year member.
The association is not permanent: that's only a suggestion.
"""
raise NotImplementedError
def select_bus(self, bus) -> None:
"""
Set the suggestion into the data of the membership.
:param bus: The bus suggested.
"""
self.information.selected_bus_pk = bus.pk
self.information.selected_bus_name = bus.name
self.information.valid = True

View File

@ -8,29 +8,50 @@ from ...models import Bus
class WEISurveyForm2020(forms.Form): class WEISurveyForm2020(forms.Form):
"""
Survey form for the year 2020.
For now, that's only a Bus selector.
TODO: Do a better survey (later)
"""
bus = forms.ModelChoiceField( bus = forms.ModelChoiceField(
Bus.objects, Bus.objects,
) )
def set_registration(self, registration): def set_registration(self, registration):
"""
Filter the bus selector with the buses of the current WEI.
"""
self.fields["bus"].queryset = Bus.objects.filter(wei=registration.wei) self.fields["bus"].queryset = Bus.objects.filter(wei=registration.wei)
class WEISurveyInformation2020(WEISurveyInformation): class WEISurveyInformation2020(WEISurveyInformation):
"""
We store the id of the selected bus. We store only the name, but is not used in the selection:
that's only for humans that try to read data.
"""
chosen_bus_pk = None chosen_bus_pk = None
chosen_bus_name = None chosen_bus_name = None
class WEISurvey2020(WEISurvey): class WEISurvey2020(WEISurvey):
year = 2020 """
Survey for the year 2020.
"""
@classmethod
def get_year(cls):
return 2020
def get_survey_information_class(self): @classmethod
def get_survey_information_class(cls):
return WEISurveyInformation2020 return WEISurveyInformation2020
def get_form_class(self): def get_form_class(self):
return WEISurveyForm2020 return WEISurveyForm2020
def update_form(self, form): def update_form(self, form):
"""
Filter the bus selector with the buses of the WEI.
"""
form.set_registration(self.registration) form.set_registration(self.registration)
def form_valid(self, form): def form_valid(self, form):
@ -39,13 +60,26 @@ class WEISurvey2020(WEISurvey):
self.information.chosen_bus_name = bus.name self.information.chosen_bus_name = bus.name
self.save() self.save()
@staticmethod @classmethod
def get_algorithm_class(): def get_algorithm_class(cls):
return WEISurveyAlgorithm2020 return WEISurveyAlgorithm2020
def is_complete(self) -> bool:
"""
The survey is complete once the bus is chosen.
"""
return self.information.chosen_bus_pk is not None
class WEISurveyAlgorithm2020(WEISurveyAlgorithm): class WEISurveyAlgorithm2020(WEISurveyAlgorithm):
def get_survey_class(self): """
The algorithm class for the year 2020.
For now, the algorithm is quite simple: the selected bus is the chosen bus.
TODO: Improve this algorithm.
"""
@classmethod
def get_survey_class(cls):
return WEISurvey2020 return WEISurvey2020
def run_algorithm(self): def run_algorithm(self):

View File

@ -0,0 +1,2 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later

View File

@ -4,7 +4,7 @@
from django.core.management import BaseCommand from django.core.management import BaseCommand
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from wei.forms import CurrentSurvey from ...forms import CurrentSurvey
class Command(BaseCommand): class Command(BaseCommand):

View File

@ -5,7 +5,8 @@ from django.urls import path
from .views import CurrentWEIDetailView, WEIListView, WEICreateView, WEIDetailView, WEIUpdateView,\ from .views import CurrentWEIDetailView, WEIListView, WEICreateView, WEIDetailView, WEIUpdateView,\
BusCreateView, BusManageView, BusUpdateView, BusTeamCreateView, BusTeamManageView, BusTeamUpdateView,\ BusCreateView, BusManageView, BusUpdateView, BusTeamCreateView, BusTeamManageView, BusTeamUpdateView,\
WEIRegister1AView, WEIRegister2AView, WEIUpdateRegistrationView, WEIValidateRegistrationView, WEISurveyView WEIRegister1AView, WEIRegister2AView, WEIUpdateRegistrationView, WEIValidateRegistrationView,\
WEISurveyView, WEISurveyEndView
app_name = 'wei' app_name = 'wei'
@ -28,4 +29,5 @@ urlpatterns = [
path('edit-registration/<int:pk>/', WEIUpdateRegistrationView.as_view(), name="wei_update_registration"), path('edit-registration/<int:pk>/', WEIUpdateRegistrationView.as_view(), name="wei_update_registration"),
path('validate/<int:pk>/', WEIValidateRegistrationView.as_view(), name="validate_registration"), path('validate/<int:pk>/', WEIValidateRegistrationView.as_view(), name="validate_registration"),
path('survey/<int:pk>/', WEISurveyView.as_view(), name="wei_survey"), path('survey/<int:pk>/', WEISurveyView.as_view(), name="wei_survey"),
path('survey/<int:pk>/end/', WEISurveyEndView.as_view(), name="wei_survey_end"),
] ]

View File

@ -6,8 +6,9 @@ from datetime import datetime, date
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.db.models import Q from django.db.models import Q
from django.shortcuts import redirect
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.views.generic import DetailView, UpdateView, CreateView, RedirectView from django.views.generic import DetailView, UpdateView, CreateView, RedirectView, TemplateView
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views.generic.edit import BaseFormView from django.views.generic.edit import BaseFormView
from django_tables2 import SingleTableView from django_tables2 import SingleTableView
@ -294,6 +295,7 @@ class WEIRegister1AView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView):
form.fields["user"].disabled = True form.fields["user"].disabled = True
del form.fields["first_year"] del form.fields["first_year"]
del form.fields["caution_check"] del form.fields["caution_check"]
del form.fields["information_json"]
return form return form
def form_valid(self, form): def form_valid(self, form):
@ -332,6 +334,7 @@ class WEIRegister2AView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView):
del form.fields["ml_events_registration"] del form.fields["ml_events_registration"]
del form.fields["ml_art_registration"] del form.fields["ml_art_registration"]
del form.fields["ml_sport_registration"] del form.fields["ml_sport_registration"]
del form.fields["information_json"]
return form return form
@ -398,6 +401,7 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Crea
def get_form(self, form_class=None): def get_form(self, form_class=None):
form = super().get_form(form_class) form = super().get_form(form_class)
registration = WEIRegistration.objects.get(pk=self.kwargs["pk"]) registration = WEIRegistration.objects.get(pk=self.kwargs["pk"])
form.fields["bus"].widget.attrs["api_url"] = "/api/wei/bus/?wei=" + str(registration.wei.pk)
if registration.first_year: if registration.first_year:
del form.fields["roles"] del form.fields["roles"]
survey = CurrentSurvey(registration) survey = CurrentSurvey(registration)
@ -457,20 +461,35 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Crea
class WEISurveyView(BaseFormView, DetailView): class WEISurveyView(BaseFormView, DetailView):
"""
Display the survey for the WEI for first
"""
model = WEIRegistration model = WEIRegistration
template_name = "wei/survey.html" template_name = "wei/survey.html"
survey = None survey = None
def setup(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
ret = super().setup(request, *args, **kwargs) obj = self.get_object()
return ret if not self.survey:
self.survey = CurrentSurvey(obj)
# If the survey is complete, then display the end page.
if self.survey.is_complete():
return redirect(reverse_lazy('wei:wei_survey_end', args=(self.survey.registration.pk,)))
# Non first year members don't have a survey
if not obj.first_year:
return redirect(reverse_lazy('wei:wei_survey_end', args=(self.survey.registration.pk,)))
return super().get(request, *args, **kwargs)
def get_form_class(self): def get_form_class(self):
if not self.survey: """
self.survey = CurrentSurvey(self.get_object()) Get the survey form. It may depend on the current state of the survey.
"""
return self.survey.get_form_class() return self.survey.get_form_class()
def get_form(self, form_class=None): def get_form(self, form_class=None):
"""
Update the form with the data of the survey.
"""
form = super().get_form(form_class) form = super().get_form(form_class)
self.survey.update_form(form) self.survey.update_form(form)
return form return form
@ -482,8 +501,21 @@ class WEISurveyView(BaseFormView, DetailView):
return context return context
def form_valid(self, form): def form_valid(self, form):
"""
Update the survey with the data of the form.
"""
self.survey.form_valid(form) self.survey.form_valid(form)
return super().form_valid(form) return super().form_valid(form)
def get_success_url(self): def get_success_url(self):
return reverse_lazy('wei:wei_survey', args=(self.get_object().pk,)) return reverse_lazy('wei:wei_survey', args=(self.get_object().pk,))
class WEISurveyEndView(TemplateView):
template_name = "wei/survey_end.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["club"] = WEIRegistration.objects.get(pk=self.kwargs["pk"]).wei
context["title"] = _("Survey WEI")
return context

View File

@ -11,7 +11,7 @@ $(document).ready(function () {
name_field = "name"; name_field = "name";
let input = target.val(); let input = target.val();
$.getJSON(api_url + "?format=json&search=^" + input + api_url_suffix, function(objects) { $.getJSON(api_url + (api_url.includes("?") ? "&" : "?") + "format=json&search=^" + input + api_url_suffix, function(objects) {
let html = ""; let html = "";
objects.results.forEach(function (obj) { objects.results.forEach(function (obj) {

View File

@ -0,0 +1,22 @@
{% extends "member/noteowner_detail.html" %}
{% load i18n %}
{% load crispy_forms_tags %}
{% block profile_info %}
{% include "wei/weiclub_info.html" %}
{% endblock %}
{% block profile_content %}
<div class="card">
<div class="card-header text-center">
<h4>{% trans "Survey WEI" %}</h4>
</div>
<div class="card-body">
<p>
{% blocktrans %}
The survey is now ended. Your answers have been saved.
{% endblocktrans %}
</p>
</div>
</div>
{% endblock %}

View File

@ -167,3 +167,15 @@
</form> </form>
</div> </div>
{% endblock %} {% endblock %}
{% block extrajavascript %}
<script>
function autocompleted(obj, prefix) {
console.log(prefix);
if (prefix === "id_bus") {
console.log(obj);
$("#id_team").attr('api_url', '/api/wei/team/?bus=' + obj.id);
}
}
</script>
{% endblock %}