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.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 note.models import NoteUser, Note
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["caution_check"]
del wei_form.fields["first_year"]
del wei_form.fields["information_json"]
context["wei_form"] = wei_form
context["wei_registration_form"] = WEISignupForm()

View File

@ -3,11 +3,11 @@
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(WEIRegistration)
admin.site.register(WEIMembership)
admin.site.register(WEIRole)
admin.site.register(Bus)
admin.site.register(BusTeam)

View File

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

View File

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

View File

@ -3,9 +3,10 @@
from django import forms
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 wei.models import WEIClub, WEIRegistration, Bus, BusTeam, WEIMembership, WEIRole
from ..models import WEIClub, WEIRegistration, Bus, BusTeam, WEIMembership, WEIRole
class WEIForm(forms.ModelForm):
@ -25,7 +26,7 @@ class WEIForm(forms.ModelForm):
class WEIRegistrationForm(forms.ModelForm):
class Meta:
model = WEIRegistration
exclude = ('wei', 'information_json', )
exclude = ('wei', )
widgets = {
"user": Autocomplete(
User,
@ -42,6 +43,12 @@ class WEIRegistrationForm(forms.ModelForm):
class WEIMembershipForm(forms.ModelForm):
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:
model = WEIMembership
fields = ('roles', 'bus', 'team',)

View File

@ -1,46 +1,18 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# 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
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:
"""
Abstract data of the survey.
"""
valid = False
selected_bus_pk = None
selected_bus_name = None
@ -48,22 +20,142 @@ class WEISurveyInformation:
def __init__(self, registration):
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:
return None
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.save()
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
def get_registrations(self):
return WEIRegistration.objects.filter(wei__year=self.get_survey_class().year, first_year=True).all()
@classmethod
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
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):
"""
Survey form for the year 2020.
For now, that's only a Bus selector.
TODO: Do a better survey (later)
"""
bus = forms.ModelChoiceField(
Bus.objects,
)
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)
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_name = None
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
def get_form_class(self):
return WEISurveyForm2020
def update_form(self, form):
"""
Filter the bus selector with the buses of the WEI.
"""
form.set_registration(self.registration)
def form_valid(self, form):
@ -39,13 +60,26 @@ class WEISurvey2020(WEISurvey):
self.information.chosen_bus_name = bus.name
self.save()
@staticmethod
def get_algorithm_class():
@classmethod
def get_algorithm_class(cls):
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):
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
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.utils.translation import gettext_lazy as _
from wei.forms import CurrentSurvey
from ...forms import CurrentSurvey
class Command(BaseCommand):

View File

@ -5,7 +5,8 @@ from django.urls import path
from .views import CurrentWEIDetailView, WEIListView, WEICreateView, WEIDetailView, WEIUpdateView,\
BusCreateView, BusManageView, BusUpdateView, BusTeamCreateView, BusTeamManageView, BusTeamUpdateView,\
WEIRegister1AView, WEIRegister2AView, WEIUpdateRegistrationView, WEIValidateRegistrationView, WEISurveyView
WEIRegister1AView, WEIRegister2AView, WEIUpdateRegistrationView, WEIValidateRegistrationView,\
WEISurveyView, WEISurveyEndView
app_name = 'wei'
@ -28,4 +29,5 @@ urlpatterns = [
path('edit-registration/<int:pk>/', WEIUpdateRegistrationView.as_view(), name="wei_update_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>/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.models import User
from django.db.models import Q
from django.shortcuts import redirect
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.views.generic.edit import BaseFormView
from django_tables2 import SingleTableView
@ -294,6 +295,7 @@ class WEIRegister1AView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView):
form.fields["user"].disabled = True
del form.fields["first_year"]
del form.fields["caution_check"]
del form.fields["information_json"]
return 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_art_registration"]
del form.fields["ml_sport_registration"]
del form.fields["information_json"]
return form
@ -398,6 +401,7 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Crea
def get_form(self, form_class=None):
form = super().get_form(form_class)
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:
del form.fields["roles"]
survey = CurrentSurvey(registration)
@ -457,20 +461,35 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Crea
class WEISurveyView(BaseFormView, DetailView):
"""
Display the survey for the WEI for first
"""
model = WEIRegistration
template_name = "wei/survey.html"
survey = None
def setup(self, request, *args, **kwargs):
ret = super().setup(request, *args, **kwargs)
return ret
def get(self, request, *args, **kwargs):
obj = self.get_object()
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):
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()
def get_form(self, form_class=None):
"""
Update the form with the data of the survey.
"""
form = super().get_form(form_class)
self.survey.update_form(form)
return form
@ -482,8 +501,21 @@ class WEISurveyView(BaseFormView, DetailView):
return context
def form_valid(self, form):
"""
Update the survey with the data of the form.
"""
self.survey.form_valid(form)
return super().form_valid(form)
def get_success_url(self):
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";
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 = "";
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>
</div>
{% 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 %}