Add some forms

This commit is contained in:
Yohann D'ANELLO 2020-05-04 20:21:53 +02:00
parent 97e5a0dfdd
commit 5ebf258eab
17 changed files with 285 additions and 26 deletions

View File

@ -1,4 +1,5 @@
from django.contrib.auth.forms import UserCreationForm from django.contrib.auth.forms import UserCreationForm
from django import forms
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from member.models import TFJMUser from member.models import TFJMUser
@ -37,3 +38,9 @@ class SignUpForm(UserCreationForm):
'responsible_email', 'responsible_email',
'description', 'description',
) )
class TFJMUserForm(forms.ModelForm):
class Meta:
model = TFJMUser
fields = '__all__'

View File

@ -1,13 +1,15 @@
from django.urls import path from django.urls import path
from django.views.generic import RedirectView from django.views.generic import RedirectView
from .views import CreateUserView, ProfileListView, OrphanedProfileListView, OrganizersListView from .views import CreateUserView, MyAccountView, UserDetailView,\
ProfileListView, OrphanedProfileListView, OrganizersListView
app_name = "member" app_name = "member"
urlpatterns = [ urlpatterns = [
path('signup/', CreateUserView.as_view(), name="signup"), path('signup/', CreateUserView.as_view(), name="signup"),
path("my-account/", RedirectView.as_view(pattern_name="index"), name="my_account"), path("my-account/", MyAccountView.as_view(), name="my_account"),
path("information/<int:pk>/", UserDetailView.as_view(), name="information"),
path("add-team/", RedirectView.as_view(pattern_name="index"), name="add_team"), path("add-team/", RedirectView.as_view(pattern_name="index"), name="add_team"),
path("join-team/", RedirectView.as_view(pattern_name="index"), name="join_team"), path("join-team/", RedirectView.as_view(pattern_name="index"), name="join_team"),
path("my-team/", RedirectView.as_view(pattern_name="index"), name="my_team"), path("my-team/", RedirectView.as_view(pattern_name="index"), name="my_team"),

View File

@ -4,11 +4,11 @@ from django.db.models import Q
from django.http import FileResponse from django.http import FileResponse
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views import View from django.views import View
from django.views.generic import CreateView from django.views.generic import CreateView, UpdateView, DetailView
from django_tables2 import SingleTableView from django_tables2 import SingleTableView
from tournament.views import AdminMixin from tournament.views import AdminMixin
from .forms import SignUpForm from .forms import SignUpForm, TFJMUserForm
from .models import TFJMUser, Document from .models import TFJMUser, Document
from .tables import UserTable from .tables import UserTable
@ -19,6 +19,35 @@ class CreateUserView(CreateView):
template_name = "registration/signup.html" template_name = "registration/signup.html"
class MyAccountView(LoginRequiredMixin, UpdateView):
model = TFJMUser
form_class = TFJMUserForm
template_name = "member/my_account.html"
def get_object(self, queryset=None):
return self.request.user
class UserDetailView(LoginRequiredMixin, DetailView):
model = TFJMUser
form_class = TFJMUserForm
context_object_name = "user"
def dispatch(self, request, *args, **kwargs):
if not request.user.admin \
and (self.object.team is not None and request.user not in self.object.team.tournament.organizers)\
and self.request.user != self.object:
raise PermissionDenied
return super().dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["title"] = str(self.object)
return context
class DocumentView(LoginRequiredMixin, View): class DocumentView(LoginRequiredMixin, View):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
doc = Document.objects.get(file=self.kwargs["file"]) doc = Document.objects.get(file=self.kwargs["file"])
@ -29,7 +58,7 @@ class DocumentView(LoginRequiredMixin, View):
return FileResponse(doc.file, content_type="application/pdf") return FileResponse(doc.file, content_type="application/pdf")
class ProfileListView(LoginRequiredMixin, AdminMixin, SingleTableView): class ProfileListView(AdminMixin, SingleTableView):
model = TFJMUser model = TFJMUser
queryset = TFJMUser.objects.order_by("role", "last_name", "first_name") queryset = TFJMUser.objects.order_by("role", "last_name", "first_name")
table_class = UserTable table_class = UserTable
@ -37,7 +66,7 @@ class ProfileListView(LoginRequiredMixin, AdminMixin, SingleTableView):
extra_context = dict(title=_("All profiles")) extra_context = dict(title=_("All profiles"))
class OrphanedProfileListView(LoginRequiredMixin, AdminMixin, SingleTableView): class OrphanedProfileListView(AdminMixin, SingleTableView):
model = TFJMUser model = TFJMUser
queryset = TFJMUser.objects.filter((Q(role="2coach") | Q(role="3participant")) & Q(team__isnull=True))\ queryset = TFJMUser.objects.filter((Q(role="2coach") | Q(role="3participant")) & Q(team__isnull=True))\
.order_by("role", "last_name", "first_name") .order_by("role", "last_name", "first_name")
@ -46,7 +75,7 @@ class OrphanedProfileListView(LoginRequiredMixin, AdminMixin, SingleTableView):
extra_context = dict(title=_("Orphaned profiles")) extra_context = dict(title=_("Orphaned profiles"))
class OrganizersListView(LoginRequiredMixin, AdminMixin, SingleTableView): class OrganizersListView(AdminMixin, SingleTableView):
model = TFJMUser model = TFJMUser
queryset = TFJMUser.objects.filter(Q(role="0admin") | Q(role="1volunteer"))\ queryset = TFJMUser.objects.filter(Q(role="0admin") | Q(role="1volunteer"))\
.order_by("role", "last_name", "first_name") .order_by("role", "last_name", "first_name")

44
apps/tournament/forms.py Normal file
View File

@ -0,0 +1,44 @@
from django import forms
from django.utils.translation import gettext_lazy as _
from member.models import TFJMUser
from tfjm.inputs import DatePickerInput, DateTimePickerInput, AmountInput
from tournament.models import Tournament, Team
class TournamentForm(forms.ModelForm):
class Meta:
model = Tournament
fields = '__all__'
widgets = {
"price": AmountInput(),
"date_start": DatePickerInput(),
"date_end": DatePickerInput(),
"date_inscription": DateTimePickerInput(),
"date_solutions": DateTimePickerInput(),
"date_syntheses": DateTimePickerInput(),
}
class OrganizerForm(forms.ModelForm):
class Meta:
model = TFJMUser
fields = ('last_name', 'first_name', 'email', 'is_superuser',)
def save(self, commit=True):
user = self.instance
user.role = '0admin' if user.is_superuser else '1organizer'
super().save(commit)
class TeamForm(forms.ModelForm):
class Meta:
model = Team
fields = ('name', 'trigram', 'tournament',)
class JoinTeam(forms.Form):
access_code = forms.CharField(
label=_("Access code"),
max_length=6,
)

View File

@ -2,6 +2,7 @@ import os
from datetime import date from datetime import date
from django.db import models from django.db import models
from django.urls import reverse_lazy
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
@ -145,10 +146,20 @@ class Team(models.Model):
def encadrants(self): def encadrants(self):
return self.users.all().filter(role="2coach") return self.users.all().filter(role="2coach")
@property
def linked_encadrants(self):
return ['<a href="{url}">'.format(url=reverse_lazy("member:information", args=(user.pk,))) + str(user) + '</a>'
for user in self.encadrants]
@property @property
def participants(self): def participants(self):
return self.users.all().filter(role="3participant") return self.users.all().filter(role="3participant")
@property
def linked_participants(self):
return ['<a href="{url}">'.format(url=reverse_lazy("member:information", args=(user.pk,))) + str(user) + '</a>'
for user in self.participants]
class Meta: class Meta:
verbose_name = _("team") verbose_name = _("team")
verbose_name_plural = _("teams") verbose_name_plural = _("teams")

View File

@ -1,16 +1,19 @@
from django.urls import path from django.urls import path
from django.views.generic import RedirectView from django.views.generic import RedirectView
from .views import TournamentListView, TournamentDetailView, TeamDetailView, SolutionsView, SolutionsOrgaListView from .views import TournamentListView, TournamentCreateView, TournamentDetailView, TournamentUpdateView,\
TeamDetailView, TeamUpdateView, AddOrganizerView, SolutionsView, SolutionsOrgaListView
app_name = "tournament" app_name = "tournament"
urlpatterns = [ urlpatterns = [
path('list/', TournamentListView.as_view(), name="list"), path('list/', TournamentListView.as_view(), name="list"),
path("add/", RedirectView.as_view(pattern_name="index"), name="add"), path("add/", TournamentCreateView.as_view(), name="add"),
path('<int:pk>/', TournamentDetailView.as_view(), name="detail"), path('<int:pk>/', TournamentDetailView.as_view(), name="detail"),
path('<int:pk>/update/', TournamentUpdateView.as_view(), name="update"),
path('team/<int:pk>/', TeamDetailView.as_view(), name="team_detail"), path('team/<int:pk>/', TeamDetailView.as_view(), name="team_detail"),
path("add-organizer/", RedirectView.as_view(pattern_name="index"), name="add_organizer"), path('team/<int:pk>/update/', TeamUpdateView.as_view(), name="team_update"),
path("add-organizer/", AddOrganizerView.as_view(), name="add_organizer"),
path("solutions/", SolutionsView.as_view(), name="solutions"), path("solutions/", SolutionsView.as_view(), name="solutions"),
path("all-solutions/", SolutionsOrgaListView.as_view(), name="all_solutions"), path("all-solutions/", SolutionsOrgaListView.as_view(), name="all_solutions"),
path("syntheses/", RedirectView.as_view(pattern_name="index"), name="syntheses"), path("syntheses/", RedirectView.as_view(pattern_name="index"), name="syntheses"),

View File

@ -5,23 +5,26 @@ from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.db.models import Q from django.db.models import Q
from django.http import HttpResponse from django.http import HttpResponse
from django.shortcuts import redirect
from django.urls import reverse_lazy
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views.generic import DetailView from django.views.generic import DetailView, CreateView, UpdateView
from django_tables2.views import SingleTableView from django_tables2.views import SingleTableView
from member.models import TFJMUser, Solution from member.models import TFJMUser, Solution
from .forms import TournamentForm, OrganizerForm, TeamForm
from .models import Tournament, Team from .models import Tournament, Team
from .tables import TournamentTable, TeamTable, SolutionTable from .tables import TournamentTable, TeamTable, SolutionTable
class AdminMixin(object): class AdminMixin(LoginRequiredMixin):
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
if not request.user.admin: if not request.user.admin:
raise PermissionDenied raise PermissionDenied
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
class TeamMixin(object): class TeamMixin(LoginRequiredMixin):
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
if not request.user.team: if not request.user.team:
raise PermissionDenied raise PermissionDenied
@ -47,6 +50,15 @@ class TournamentListView(SingleTableView):
return context return context
class TournamentCreateView(AdminMixin, CreateView):
model = Tournament
form_class = TournamentForm
extra_context = dict(title=_("Add tournament"),)
def get_success_url(self):
return reverse_lazy('tournament:detail', args=(self.object.pk,))
class TournamentDetailView(DetailView): class TournamentDetailView(DetailView):
model = Tournament model = Tournament
@ -71,6 +83,15 @@ class TournamentDetailView(DetailView):
return context return context
class TournamentUpdateView(AdminMixin, UpdateView):
model = Tournament
form_class = TournamentForm
extra_context = dict(title=_("Update tournament"),)
def get_success_url(self):
return reverse_lazy('tournament:detail', args=(self.object.pk,))
class TeamDetailView(LoginRequiredMixin, DetailView): class TeamDetailView(LoginRequiredMixin, DetailView):
model = Team model = Team
@ -97,6 +118,9 @@ class TeamDetailView(LoginRequiredMixin, DetailView):
.format(_("Solutions for team {team}.zip") .format(_("Solutions for team {team}.zip")
.format(team=str(team)).replace(" ", "%20")) .format(team=str(team)).replace(" ", "%20"))
return resp return resp
elif "delete" in request.POST:
team.delete()
return redirect('tournament:detail', pk=team.tournament.pk)
return self.get(request, *args, **kwargs) return self.get(request, *args, **kwargs)
@ -108,7 +132,25 @@ class TeamDetailView(LoginRequiredMixin, DetailView):
return context return context
class SolutionsView(LoginRequiredMixin, TeamMixin, SingleTableView): class TeamUpdateView(LoginRequiredMixin, UpdateView):
model = Team
form_class = TeamForm
extra_context = dict(title=_("Udpate team"),)
def dispatch(self, request, *args, **kwargs):
if not request.user.admin and self.request.user not in self.get_object().tournament.organizers:
raise PermissionDenied
return super().dispatch(request, *args, **kwargs)
class AddOrganizerView(AdminMixin, CreateView):
model = TFJMUser
form_class = OrganizerForm
extra_context = dict(title=_("Add organizer"),)
template_name = "tournament/add_organizer.html"
class SolutionsView(TeamMixin, SingleTableView):
model = Solution model = Solution
table_class = SolutionTable table_class = SolutionTable
template_name = "tournament/solutions_list.html" template_name = "tournament/solutions_list.html"
@ -149,7 +191,7 @@ class SolutionsView(LoginRequiredMixin, TeamMixin, SingleTableView):
return qs.order_by('team__tournament__date_start', 'team__tournament__name', 'team__trigram', 'problem',) return qs.order_by('team__tournament__date_start', 'team__tournament__name', 'team__trigram', 'problem',)
class SolutionsOrgaListView(LoginRequiredMixin, AdminMixin, SingleTableView): class SolutionsOrgaListView(AdminMixin, SingleTableView):
model = Solution model = Solution
table_class = SolutionTable table_class = SolutionTable
template_name = "tournament/solutions_orga_list.html" template_name = "tournament/solutions_orga_list.html"

View File

@ -442,7 +442,7 @@ msgstr "Liste des tournois"
#: apps/tournament/views.py:56 #: apps/tournament/views.py:56
msgid "Tournament of {name}" msgid "Tournament of {name}"
msgstr "Tournoi de {user}" msgstr "Tournoi de {name}"
#: apps/tournament/views.py:97 apps/tournament/views.py:131 #: apps/tournament/views.py:97 apps/tournament/views.py:131
#, python-brace-format #, python-brace-format

View File

@ -0,0 +1,14 @@
{% extends "base.html" %}
{% load i18n crispy_forms_filters %}
{% block content %}
<form method="post">
{{ form|crispy }}
<input type="submit" class="btn btn-primary btn-block" value="{% trans "Submit" %}">
</form>
<hr>
<a class="btn btn-secondary btn-block" href="{% url "password_change" %}">{% trans "Update my password" %}</a>
{% endblock %}

View File

@ -0,0 +1,69 @@
{% extends "base.html" %}
{% load getconfig i18n django_tables2 static %}
{% block content %}
<div class="card bg-light shadow">
<div class="card-header text-center">
<h4>{{ user }}</h4>
</div>
<div class="card-body">
<dl class="row">
<dt class="col-xl-6 text-right">{% trans 'role'|capfirst %}</dt>
<dd class="col-xl-6">{{ user.get_role_display }}</dd>
<dt class="col-xl-6 text-right">{% trans 'team'|capfirst %}</dt>
<dd class="col-xl-6"><a href="{% url "tournament:team_detail" pk=user.team.pk %}">{{ user.team }}</a></dd>
<dt class="col-xl-6 text-right">{% trans 'birth date'|capfirst %}</dt>
<dd class="col-xl-6">{{ user.birth_date }}</dd>
<dt class="col-xl-6 text-right">{% trans 'gender'|capfirst %}</dt>
<dd class="col-xl-6">{{ user.get_gender_display }}</dd>
<dt class="col-xl-6 text-right">{% trans 'address'|capfirst %}</dt>
<dd class="col-xl-6">{{ user.address }}, {{ user.postal_code }}, {{ user.city }}{% if user.country != "France" %}, {{ user.country }}{% endif %}</dd>
<dt class="col-xl-6 text-right">{% trans 'email'|capfirst %}</dt>
<dd class="col-xl-6"><a href="mailto:{{ user.email }}">{{ user.email }}</a></dd>
<dt class="col-xl-6 text-right">{% trans 'phone number'|capfirst %}</dt>
<dd class="col-xl-6">{{ user.phone_number }}</dd>
{% if user.role == '3participant' %}
<dt class="col-xl-6 text-right">{% trans 'school'|capfirst %}</dt>
<dd class="col-xl-6">{{ user.school }}</dd>
<dt class="col-xl-6 text-right">{% trans 'class'|capfirst %}</dt>
<dd class="col-xl-6">{{ user.get_student_class_display }}</dd>
{% if user.responsible_name %}
<dt class="col-xl-6 text-right">{% trans 'responsible name'|capfirst %}</dt>
<dd class="col-xl-6">{{ user.responsible_name }}</dd>
{% endif %}
{% if user.responsible_phone %}
<dt class="col-xl-6 text-right">{% trans 'responsible phone'|capfirst %}</dt>
<dd class="col-xl-6">{{ user.responsible_phone }}</dd>
{% endif %}
{% if user.responsible_email %}
<dt class="col-xl-6 text-right">{% trans 'responsible email'|capfirst %}</dt>
<dd class="col-xl-6"><a href="{{ user.responsible_email }}">{{ user.responsible_email }}</a></dd>
{% endif %}
{% endif %}
{% if user.role == '2coach' %}
<dt class="col-xl-6 text-right">{% trans 'description'|capfirst %}</dt>
<dd class="col-xl-6">{{ user.description }}</dd>
{% endif %}
</dl>
</div>
</div>
<hr>
<h4>{% trans "Documents" %}</h4>
{# TODO Display documents #}
{% endblock %}

View File

@ -0,0 +1,11 @@
{% extends "base.html" %}
{% load i18n crispy_forms_filters %}
{% block content %}
<form method="post">
{% csrf_token %}
{{ form|crispy }}
<input type="submit" class="btn btn-primary btn-block" value="{% trans "Submit" %}">
</form>
{% endblock %}

View File

@ -19,18 +19,21 @@
<dd class="col-xl-6">{{ team.tournament }}</dd> <dd class="col-xl-6">{{ team.tournament }}</dd>
<dt class="col-xl-6 text-right">{% trans 'coachs'|capfirst %}</dt> <dt class="col-xl-6 text-right">{% trans 'coachs'|capfirst %}</dt>
<dd class="col-xl-6">{{ team.encadrants.all|join:", " }}</dd> <dd class="col-xl-6">{% autoescape off %}{{ team.linked_encadrants|join:", " }}{% endautoescape %}</dd>
<dt class="col-xl-6 text-right">{% trans 'participants'|capfirst %}</dt> <dt class="col-xl-6 text-right">{% trans 'participants'|capfirst %}</dt>
<dd class="col-xl-6">{{ team.participants.all|join:", " }}</dd> <dd class="col-xl-6">{% autoescape off %}{{ team.linked_participants|join:", " }}{% endautoescape %}</dd>
</dl> </dl>
</div> </div>
{% if user.admin or user in team.tournament.organizers.all %} {% if user.admin or user in team.tournament.organizers.all %}
<div class="card-footer text-center"> <div class="card-footer text-center">
<button class="btn btn-secondary">{% trans "Edit team" %}</button> <a href="{% url "tournament:team_update" pk=team.pk %}"><button class="btn btn-secondary">{% trans "Edit team" %}</button></a>
{% if user.admin and team.invalid %} {% if user.admin and team.invalid %}
<button class="btn btn-danger">{% trans "Delete team" %}</button> <form method="post">
{% csrf_token %}
<button name="delete" class="btn btn-danger">{% trans "Delete team" %}</button>
</form>
{% endif %} {% endif %}
</div> </div>
{% endif %} {% endif %}

View File

@ -0,0 +1,11 @@
{% extends "base.html" %}
{% load i18n crispy_forms_filters %}
{% block content %}
<form method="post">
{% csrf_token %}
{{ form|crispy }}
<input type="submit" class="btn btn-primary btn-block" value="{% trans "Submit" %}">
</form>
{% endblock %}

View File

@ -39,15 +39,15 @@
{% if user.is_authenticated and user.admin %} {% if user.is_authenticated and user.admin %}
<div class="alert alert-info"> <div class="alert alert-info">
<a href="mailto:contact@tfm.org?subject=TFJM²%20{{ "TFJM_YEAR"|get_env }}&bcc={{ team_users_emails|join:"," }}">{% trans "Send a mail to all people in this tournament" %}</a><br> <a href="mailto:contact@tfjm.org?subject=TFJM²%20{{ "TFJM_YEAR"|get_env }}&bcc={{ team_users_emails|join:"," }}">{% trans "Send a mail to all people in this tournament" %}</a><br>
<a href="mailto:contact@tfm.org?subject=TFJM²%20{{ "TFJM_YEAR"|get_env }}&bcc={{ valid_team_users_emails|join:"," }}">{% trans "Send a mail to all people in this tournament that are in a valid team" %}</a> <a href="mailto:contact@tfjm.org?subject=TFJM²%20{{ "TFJM_YEAR"|get_env }}&bcc={{ valid_team_users_emails|join:"," }}">{% trans "Send a mail to all people in this tournament that are in a valid team" %}</a>
</div> </div>
{% endif %} {% endif %}
</div> </div>
{% if user.admin or user in tournament.organizers.all %} {% if user.admin or user in tournament.organizers.all %}
<div class="card-footer text-center"> <div class="card-footer text-center">
<button class="btn btn-secondary">{% trans "Edit tournament" %}</button> <a href="{% url "tournament:update" pk=tournament.pk %}"><button class="btn btn-secondary">{% trans "Edit tournament" %}</button></a>
</div> </div>
{% endif %} {% endif %}
</div> </div>

View File

@ -0,0 +1,11 @@
{% extends "base.html" %}
{% load i18n crispy_forms_filters %}
{% block content %}
<form method="post">
{% csrf_token %}
{{ form|crispy }}
<input type="submit" class="btn btn-primary btn-block" value="{% trans "Submit" %}">
</form>
{% endblock %}

View File

@ -5,8 +5,8 @@
{% block content %} {% block content %}
{% if user.is_authenticated and user.admin %} {% if user.is_authenticated and user.admin %}
<div class="alert alert-info"> <div class="alert alert-info">
<a href="mailto:contact@tfm.org?subject=TFJM²%20{{ "TFJM_YEAR"|get_env }}&bcc={{ team_users_emails|join:"," }}">{% trans "Send a mail to all people that are in a team" %}</a><br> <a href="mailto:contact@tfjm.org?subject=TFJM²%20{{ "TFJM_YEAR"|get_env }}&bcc={{ team_users_emails|join:"," }}">{% trans "Send a mail to all people that are in a team" %}</a><br>
<a href="mailto:contact@tfm.org?subject=TFJM²%20{{ "TFJM_YEAR"|get_env }}&bcc={{ valid_team_users_emails|join:"," }}">{% trans "Send a mail to all people that are in a valid team" %}</a> <a href="mailto:contact@tfjm.org?subject=TFJM²%20{{ "TFJM_YEAR"|get_env }}&bcc={{ valid_team_users_emails|join:"," }}">{% trans "Send a mail to all people that are in a valid team" %}</a>
</div> </div>
{% endif %} {% endif %}
{% render_table table %} {% render_table table %}

View File

@ -89,6 +89,8 @@ TEMPLATES = [
}, },
] ]
FORM_RENDERER = 'django.forms.renderers.TemplatesSetting'
WSGI_APPLICATION = 'tfjm.wsgi.application' WSGI_APPLICATION = 'tfjm.wsgi.application'
@ -150,7 +152,7 @@ LANGUAGES = [
('fr', _('French')), ('fr', _('French')),
] ]
TIME_ZONE = 'UTC' TIME_ZONE = 'Europe/Paris'
USE_I18N = True USE_I18N = True