Author SHA1 Message Date
imperosol 7fdb1a914f remove date selection from subscription stats view
Ca n'a jamais marché depuis 2017
2026-09-22 11:37:38 +02:00
imperosol 130afc471c better subscription generation in populate_more 2026-09-22 11:04:18 +02:00
thomas girod e937189b80 Merge pull request #1493 from ae-utbm/padding-refilling
add a space between inputs and submit in refill form
2026-09-21 10:34:03 +02:00
thomas girod 9b8ca76e33 Merge pull request #1494 from ae-utbm/fix-htmx-events
fix: `htmx:after:swap` was still using old syntax
2026-09-21 10:33:40 +02:00
imperosol aedfbf948e fix: htmx:after:swap was still using old syntax 2026-09-20 23:07:09 +02:00
imperosol 8175fc415c add a space between inputs and submit in refill form 2026-09-20 22:37:31 +02:00
22 changed files with 215 additions and 422 deletions
+5 -13
View File
@@ -28,7 +28,6 @@ from typing import ClassVar, NamedTuple
from django.conf import settings
from django.contrib.auth.models import Permission
from django.contrib.sites.models import Site
from django.core.files.base import ContentFile
from django.core.management import call_command
from django.core.management.base import BaseCommand
from django.db import connection
@@ -121,22 +120,15 @@ class Command(BaseCommand):
)
self.profiles_root = SithFile.objects.create(name="profiles", owner=root)
home_root = SithFile.objects.create(name="users", owner=root)
# Page needed for club creation
p = Page(name=settings.SITH_CLUB_ROOT_PAGE)
p.save(force_lock=True)
club_root = SithFile.objects.create(name="clubs", owner=root)
sas = SithFile.objects.create(
name="SAS", owner=root, id=settings.SITH_SAS_ROOT_DIR_ID
)
s = SithFile.objects.create(
name="CGU",
is_folder=False,
file=ContentFile(
content="Conditions générales d'utilisation", name="cgu.txt"
),
owner=root,
)
s.view_groups.add(settings.SITH_GROUP_PUBLIC_ID)
# Page needed for club creation
p = Page(name=settings.SITH_CLUB_ROOT_PAGE)
p.save(force_lock=True)
clubs = self._create_clubs()
self.reset_index("club")
+14 -2
View File
@@ -138,10 +138,22 @@ class Command(BaseCommand):
)
def create_subscriptions(self, users: list[User]):
subscription_types = [
"un-semestre",
"deux-semestres",
"cursus-tronc-commun",
"cursus-branche",
]
def prepare_subscription(_user: User, start_date: date) -> Subscription:
payment_method = random.choice(settings.SITH_SUBSCRIPTION_PAYMENT_METHOD)[0]
duration = random.randint(1, 4)
s = Subscription(member=_user, payment_method=payment_method)
subscription_type = random.choice(subscription_types)
s = Subscription(
member=_user,
payment_method=payment_method,
subscription_type=subscription_type,
)
duration = settings.SITH_SUBSCRIPTIONS[subscription_type]["duration"]
s.subscription_start = s.compute_start(d=start_date, duration=duration)
s.subscription_end = s.compute_end(duration)
return s
@@ -1,15 +0,0 @@
# Generated by Django 5.2.17 on 2026-09-15 11:02
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [("core", "0050_alter_sithfile_moderator")]
operations = [
migrations.AddField(
model_name="user",
name="cgu_approved",
field=models.BooleanField(default=False, verbose_name="ToS approved"),
),
]
-1
View File
@@ -291,7 +291,6 @@ class User(AbstractUser):
),
blank=True,
)
cgu_approved = models.BooleanField(_("ToS approved"), default=False)
godfathers = models.ManyToManyField("User", related_name="godchildren", blank=True)
objects = CustomUserManager()
+46 -11
View File
@@ -37,7 +37,8 @@ body {
margin: 0;
}
form {
>div,
>form {
box-sizing: border-box;
display: flex;
flex-direction: column;
@@ -48,33 +49,67 @@ body {
max-width: 500px;
margin-top: 20px;
input[type="submit"] {
>p,
>div {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%;
max-width: 300px;
margin-top: 1em;
margin: 0;
>label {
width: 100%;
@media (min-width: 500px) {
width: 300px;
}
}
}
.errorlist {
>input,
>p>input,
>div>input {
box-sizing: border-box;
width: 100%;
max-width: 500px;
@media (min-width: 500px) {
max-width: 300px;
}
}
>.errorlist {
color: red;
text-align: center;
margin: 10px 0 0 0;
list-style-type: none;
}
div, fieldset {
max-width: 300px;
>.required>.helptext {
text-align: center;
font-style: italic;
}
.captcha {
box-sizing: border-box;
fieldset {
margin-bottom: unset
>.required:last-of-type {
box-sizing: border-box;
max-width: 300px;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-between;
>label {
width: 100%;
}
>img {
width: 70px;
object-fit: contain;
}
>input {
width: 200px;
}
}
}
}
-39
View File
@@ -1,39 +0,0 @@
{% extends "core/base.jinja" %}
{%- block additional_css -%}
<link rel="stylesheet" href="{{ static('user/login.scss') }}">
{%- endblock -%}
{% block title %}
{% trans %}Login{% endtrans %}
{% endblock %}
{% block info_boxes %}
{% endblock %}
{% block nav %}
{% endblock %}
{% block content %}
<h1 class="title">{% trans %}Terms of Service{% endtrans %}</h1>
<form method="post" id="login-form">
{% csrf_token %}
<div class="alert alert-yellow">
{% trans trimmed %}
To continue using our services,
please read and approve the AE website's terms of service
{% endtrans %}
</div>
<div class="form-group">
{{ form.cgu_approved.errors }}
{{ form.cgu_approved }}
{{ form.cgu_approved.label_tag() }}
</div>
<input type="hidden" name="next" value="{{ next }}">
<input type="submit" class="btn btn-blue">
</form>
{% endblock %}
+19 -8
View File
@@ -35,17 +35,28 @@
{% csrf_token %}
{{ form }}
<div>
<label for="{{ form.username.name }}">{{ form.username.label }}</label>
{{ form.username }}
{{ form.username.errors }}
</div>
<div>
<label for="{{ form.password.name }}">{{ form.password.label }}</label>
{{ form.password }}
{{ form.password.errors }}
</div>
<input type="hidden" name="next" value="{{ next }}">
<input type="submit" class="btn btn-blue" value="{% trans %}Login{% endtrans %}">
<input type="submit" value="{% trans %}Login{% endtrans %}">
<div>
<a href="{{ url("core:password_reset") }}">{% trans %}Lost password?{% endtrans %}</a>
</div>
<div>
<a href="{{ url("core:register") }}">{% trans %}Create account{% endtrans %}</a>
</div>
{# Assumes you setup the password_reset view in your URLconf #}
<p>
<a href="{{ url('core:password_reset') }}">{% trans %}Lost password?{% endtrans %}</a>
&nbsp;&nbsp;
<a href="{{ url('core:register') }}">{% trans %}Create account{% endtrans %}</a>
</p>
</form>
{% endblock %}
+2 -12
View File
@@ -18,17 +18,7 @@
<form action="{{ url('core:register') }}" method="post">
{% csrf_token %}
{% render_honeypot_field %}
{% for field in form %}
{% if field.name not in ["cgu_approved", "captcha"] %}
<div>{{ field.as_field_group() }}</div>
{% endif %}
{% endfor %}
<div class="captcha">{{ form.captcha.as_field_group() }}</div>
<div class="form-group">
{{ form.cgu_approved.errors }}
{{ form.cgu_approved }}
{{ form.cgu_approved.label_tag() }}
</div>
<input type="submit" class="btn btn-blue" value="{% trans %}Register{% endtrans %}" />
{{ form.as_p() }}
<input type="submit" value="{% trans %}Register{% endtrans %}" />
</form>
{% endblock %}
+2 -37
View File
@@ -55,7 +55,6 @@ class TestUserRegistration:
"password2": "plop",
"captcha_0": "dummy-value",
"captcha_1": "PASSED",
"cgu_approved": True,
}
@pytest.fixture()
@@ -93,10 +92,6 @@ class TestUserRegistration:
({"first_name": ""}, "Ce champ est obligatoire."),
({"last_name": ""}, "Ce champ est obligatoire."),
({"captcha_1": "WRONG_CAPTCHA"}, "CAPTCHA invalide"),
(
{"cgu_approved": ""},
"Vous devez approuver les conditions générales d'utilisation",
),
],
)
def test_register_user_form_fail(
@@ -155,7 +150,7 @@ class TestUserRegistration:
class TestUserLogin:
@pytest.fixture()
def user(self) -> User:
return baker.make(User, password=make_password("plop"), cgu_approved=True)
return baker.make(User, password=make_password("plop"))
@pytest.mark.parametrize(
"identifier_getter",
@@ -196,40 +191,10 @@ class TestUserLogin:
reverse("core:login"),
{"username": identifier_getter(user), "password": "plop"},
)
assertRedirects(response, settings.LOGIN_REDIRECT_URL)
assertRedirects(response, reverse("core:index"))
assert response.wsgi_request.user == user
@pytest.mark.django_db
class TestCGU:
def test_cgu_approval(self, client: Client):
user = baker.make(User, password=make_password("plop"), cgu_approved=False)
user_url = user.get_absolute_url()
res = client.post(
reverse("core:login"),
{"username": user.username, "password": "plop", "next": user_url},
)
assertRedirects(res, reverse("core:approve_cgu", query={"next": user_url}))
res = client.post(
reverse("core:approve_cgu"), {"cgu_approved": True, "next": user_url}
)
assertRedirects(res, user_url)
user.refresh_from_db()
assert user.cgu_approved
def test_access_cgu_when_already_approved(self, client: Client):
url = reverse("core:approve_cgu")
res = client.get(url)
assertRedirects(res, reverse("core:login"))
client.force_login(baker.make(User, cgu_approved=True))
res = client.get(url)
assertRedirects(res, settings.LOGIN_REDIRECT_URL)
res = client.post(url, {"cgu_approved": True})
assertRedirects(res, settings.LOGIN_REDIRECT_URL)
@pytest.mark.parametrize(
("md", "html"),
[
-2
View File
@@ -31,7 +31,6 @@ from core.converters import (
TwoDigitMonthConverter,
)
from core.views import (
CGUApprovalView,
FileDeleteView,
FileEditPropView,
FileEditView,
@@ -126,7 +125,6 @@ urlpatterns = [
name="password_reset_complete",
),
path("register/", UserCreationView.as_view(), name="register"),
path("cgu/", CGUApprovalView.as_view(), name="approve_cgu"),
# Group handling
path("group/", GroupListView.as_view(), name="group_list"),
path("group/new/", GroupCreateView.as_view(), name="group_new"),
+5 -41
View File
@@ -30,7 +30,9 @@ from django import forms
from django.conf import settings
from django.contrib.auth.forms import AuthenticationForm, UserCreationForm
from django.contrib.auth.models import Permission
from django.contrib.staticfiles.storage import staticfiles_storage
from django.contrib.staticfiles.management.commands.collectstatic import (
staticfiles_storage,
)
from django.core.exceptions import ValidationError
from django.db import transaction
from django.forms import (
@@ -40,9 +42,6 @@ from django.forms import (
TextInput,
Widget,
)
from django.urls import reverse
from django.utils.functional import lazy
from django.utils.safestring import mark_safe
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from phonenumber_field.widgets import RegionalPhoneNumberWidget
@@ -109,34 +108,6 @@ class FutureDateTimeField(forms.DateTimeField):
return {"min": widget.format_value(now())}
class CGUApprovalField(forms.BooleanField):
cgu_file_id = settings.SITH_CGU_FILE_ID
default_error_messages = {"required": _("You must approve the terms of service.")}
__label = None
def __init__(self, *, label_suffix: str | None = "", **kwargs):
# Because the core app of the sith is so huge,
# and because we require a url from the latter,
# putting the reverse into the __init__ will result in it
# being evaluated at server startup time (even with reverse_lazy).
# This will result in a circular import.
# Thus, we must keep the label in its own property and force it to be lazy.
kwargs["label"] = lazy(self.get_label, str)
kwargs["required"] = True
super().__init__(label_suffix=label_suffix, **kwargs)
def get_label(self):
if not self.__label:
self.__label = mark_safe(
_(
"I have read and I approve the "
'<a href="%(url)s" target="_blank">Terms of Service</a>'
)
% {"url": reverse("core:page", kwargs={"page_name": self.cgu_file_id})}
)
return self.__label
# Forms
@@ -175,15 +146,8 @@ class RegisteringForm(UserCreationForm):
class Meta:
model = User
fields = ("first_name", "last_name", "email", "cgu_approved")
field_classes = {"email": AntiSpamEmailField, "cgu_approved": CGUApprovalField}
class CGUApprovalForm(forms.ModelForm):
class Meta:
model = User
fields = ["cgu_approved"]
field_classes = {"cgu_approved": CGUApprovalField}
fields = ("first_name", "last_name", "email")
field_classes = {"email": AntiSpamEmailField}
class UserProfileForm(forms.ModelForm):
+3 -33
View File
@@ -27,12 +27,12 @@ from datetime import timedelta
# This file contains all the views that concern the user model
from operator import itemgetter
from smtplib import SMTPException
from typing import TYPE_CHECKING, Any
from typing import TYPE_CHECKING
from django.contrib import messages
from django.contrib.auth import login, views
from django.contrib.auth.decorators import login_required
from django.contrib.auth.forms import SetPasswordForm
from django.contrib.auth.forms import PasswordChangeForm, SetPasswordForm
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.contrib.messages.views import SuccessMessageMixin
from django.core.exceptions import PermissionDenied
@@ -60,7 +60,6 @@ from honeypot.decorators import check_honeypot
from core.auth.mixins import CanEditMixin, CanEditPropMixin, CanViewMixin
from core.models import Gift, Preferences, User
from core.views.forms import (
CGUApprovalForm,
GiftForm,
LoginForm,
RegisteringForm,
@@ -72,7 +71,6 @@ from core.views.forms import (
from core.views.mixins import FragmentMixin, TabedViewMixin, UseFragmentsMixin
from counter.models import Refilling, Selling
from eboutic.models import Invoice
from sith import settings
from trombi.views import UserTrombiForm
if TYPE_CHECKING:
@@ -84,16 +82,9 @@ class SithLoginView(views.LoginView):
template_name = "core/login.jinja"
authentication_form = LoginForm
form_class = PasswordChangeForm
redirect_authenticated_user = True
def get_success_url(self) -> str:
redirect_to = self.get_redirect_url()
default_url = self.get_default_redirect_url()
if not self.request.user.cgu_approved:
query = {"next": redirect_to} if redirect_to else {}
return reverse("core:approve_cgu", query=query)
return redirect_to or default_url
class SithPasswordChangeView(views.PasswordChangeView):
"""Allows a user to change its password."""
@@ -197,27 +188,6 @@ class UserCreationView(FormView):
return super().form_valid(form)
class CGUApprovalView(views.RedirectURLMixin, UpdateView):
form_class = CGUApprovalForm
next_page = settings.LOGIN_REDIRECT_URL
template_name = "core/cgu_approve.jinja"
def dispatch(self, request, *args, **kwargs):
if self.request.user.is_anonymous:
return redirect("core:login")
if self.request.user.cgu_approved:
return redirect(self.get_success_url())
return super().dispatch(request, *args, **kwargs)
def get_object(self, *args, **kwargs):
return self.request.user
def get_context_data(self, **kwargs) -> dict[str, Any]:
return super().get_context_data(**kwargs) | {
self.redirect_field_name: self.get_redirect_url()
}
class UserMeRedirect(LoginRequiredMixin, RedirectView):
def get_redirect_url(self, *args, **kwargs):
if remaining := kwargs.get("remaining_path"):
@@ -123,14 +123,14 @@ document.addEventListener("alpine:init", () => {
onRefillingSuccess(event: CustomEvent) {
if (
event.type !== "htmx:after-swap" ||
event.detail.failed ||
event.detail.elt.querySelector(".errorlist")
event.type !== "htmx:after:swap" ||
event.detail.ctx.response.status !== 200 ||
event.detail.ctx.target.querySelector(".errorlist")
) {
return;
}
this.customerBalance += Number.parseFloat(
(event.detail.target.querySelector("#id_amount") as HTMLInputElement).value,
(event.detail.ctx.target.querySelector("#id_amount") as HTMLInputElement).value,
);
document.getElementById("selling-accordion")?.setAttribute("open", "");
this.codeField?.widget.focus();
@@ -62,8 +62,7 @@
}
form {
margin-top: .5rem;
margin-bottom: .5rem;
margin: 0;
}
}
@@ -186,7 +186,7 @@
{% if refilling_fragment %}
<div
class="accordion-content"
@htmx:after-swap="onRefillingSuccess"
@htmx:after:swap="onRefillingSuccess"
>
{{ refilling_fragment }}
</div>
@@ -4,6 +4,8 @@
hx-swap="outerHTML"
>
{% csrf_token %}
<div class="margin-bottom">
{{ form.as_p() }}
<input type="submit" value="{% trans %}Go{% endtrans %}"/>
</div>
<input type="submit" class="btn btn-blue" value="{% trans %}Go{% endtrans %}"/>
</form>
@@ -60,7 +60,7 @@
</p>
<br>
{% if settings.SITH_EBOUTIC_CB_ENABLED %}
<div @htmx:after-request="fill">
<div @htmx:after:request="fill">
{{ billing_infos_form }}
</div>
{% endif %}
+72 -119
View File
@@ -6,7 +6,7 @@
msgid ""
msgstr ""
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-09-21 13:16+0200\n"
"POT-Creation-Date: 2026-09-09 07:21+0200\n"
"PO-Revision-Date: 2016-07-18\n"
"Last-Translator: Maréchal <thomas.girod@utbm.fr\n"
"Language-Team: AE info <ae.info@utbm.fr>\n"
@@ -736,9 +736,8 @@ msgstr "Méthode de paiement"
#: counter/templates/counter/fragments/create_student_card.jinja
#: counter/templates/counter/last_ops.jinja
#: election/templates/election/election_detail.jinja
#: forum/templates/forum/macros.jinja
#: pedagogy/templates/pedagogy/fragments/ue_details/comments.jinja
#: pedagogy/templates/pedagogy/guide.jinja sas/templates/sas/album.jinja
#: forum/templates/forum/macros.jinja pedagogy/templates/pedagogy/guide.jinja
#: pedagogy/templates/pedagogy/ue_detail.jinja sas/templates/sas/album.jinja
#: sas/templates/sas/moderation.jinja sas/templates/sas/picture.jinja
#: trombi/templates/trombi/detail.jinja
#: trombi/templates/trombi/edit_profile.jinja
@@ -932,9 +931,8 @@ msgstr "Outils"
#: counter/templates/counter/cash_summary_list.jinja
#: counter/templates/counter/counter_list.jinja
#: election/templates/election/election_detail.jinja
#: forum/templates/forum/macros.jinja
#: pedagogy/templates/pedagogy/fragments/ue_details/comments.jinja
#: pedagogy/templates/pedagogy/guide.jinja sas/templates/sas/album.jinja
#: forum/templates/forum/macros.jinja pedagogy/templates/pedagogy/guide.jinja
#: pedagogy/templates/pedagogy/ue_detail.jinja sas/templates/sas/album.jinja
#: trombi/templates/trombi/detail.jinja
#: trombi/templates/trombi/edit_profile.jinja
msgid "Edit"
@@ -1837,10 +1835,6 @@ msgstr ""
"Même si ce profil est caché, les utilisateurs sur cette liste pourront "
"toujours le voir."
#: core/models.py
msgid "ToS approved"
msgstr "CGU approuvées"
#: core/models.py
msgid "A user with that username already exists"
msgstr "Un utilisateur de ce nom d'utilisateur existe déjà"
@@ -2115,8 +2109,7 @@ msgstr "R&D"
msgid "Site created by the IT Department of the AE"
msgstr "Site réalisé par le Pôle Informatique de l'AE"
#: core/templates/core/base/header.jinja core/templates/core/cgu_approve.jinja
#: core/templates/core/login.jinja
#: core/templates/core/base/header.jinja core/templates/core/login.jinja
#: core/templates/core/password_reset_complete.jinja
msgid "Login"
msgstr "Connexion"
@@ -2214,18 +2207,6 @@ msgstr "FAQ"
msgid "Wiki"
msgstr "Wiki"
#: core/templates/core/cgu_approve.jinja
msgid "Terms of Service"
msgstr "Conditions générales d'utilisation"
#: core/templates/core/cgu_approve.jinja
msgid ""
"To continue using our services, please read and approve the AE website's "
"terms of service"
msgstr ""
"Pour continuer à utiliser nos services, veuillez lire et approuver les "
"conditions générales d'utilisation du site AE."
#: core/templates/core/create.jinja
#, python-format
msgid "Create %(name)s"
@@ -2252,7 +2233,6 @@ msgstr "Confirmation"
#: core/templates/core/file_delete_confirm.jinja
#: counter/templates/counter/counter_click.jinja
#: counter/templates/counter/fragments/delete_student_card.jinja
#: pedagogy/templates/pedagogy/fragments/comment_report.jinja
#: sas/templates/sas/ask_picture_removal.jinja
msgid "Cancel"
msgstr "Annuler"
@@ -2279,7 +2259,6 @@ msgstr "Propriétés"
#: core/templates/core/file_delete_confirm.jinja
#: counter/templates/counter/fragments/delete_student_card.jinja
#: pedagogy/templates/pedagogy/fragments/ue_details/comments.jinja
#, python-format
msgid "Are you sure you want to delete \"%(obj)s\"?"
msgstr "Êtes-vous sûr de vouloir supprimer \"%(obj)s\" ?"
@@ -3154,19 +3133,6 @@ msgstr "Appliquer les droits récursivement"
msgid "Ensure this timestamp is set in the future"
msgstr "Assurez-vous que cet horodatage est dans le futur"
#: core/views/forms.py
msgid "You must approve the terms of service."
msgstr "Vous devez approuver les conditions générales d'utilisation"
#: core/views/forms.py
#, python-format
msgid ""
"I have read and I approve the <a href=\"%(url)s\" target=\"_blank\">Terms of "
"Service</a>"
msgstr ""
"J'ai lu et j'approuve les <a href=\"%(url)s\" target=\"_blank\">Conditions "
"Générales d'utilisation</a>"
#: core/views/forms.py
msgid "Username, email, or account number"
msgstr "Nom d'utilisateur, email, ou numéro de compte AE"
@@ -3812,8 +3778,8 @@ msgid "Emptied"
msgstr "Coffre vidé"
#: counter/templates/counter/cash_summary_list.jinja counter/views/cash.py
#: pedagogy/templates/pedagogy/fragments/ue_comment_form.jinja
#: pedagogy/templates/pedagogy/moderation.jinja
#: pedagogy/templates/pedagogy/ue_detail.jinja
#: trombi/templates/trombi/comment.jinja
#: trombi/templates/trombi/user_tools.jinja
msgid "Comment"
@@ -5143,80 +5109,6 @@ msgstr "signaler"
msgid "reporter"
msgstr "signalant"
#: pedagogy/templates/pedagogy/fragments/comment_report.jinja
msgid "Report"
msgstr "Signaler"
#: pedagogy/templates/pedagogy/fragments/ue_comment_form.jinja
msgid "Leave comment"
msgstr "Laisser un commentaire"
#: pedagogy/templates/pedagogy/fragments/ue_details/comments.jinja
#: trombi/templates/trombi/export.jinja
msgid "Comments"
msgstr "Commentaires"
#: pedagogy/templates/pedagogy/fragments/ue_details/comments.jinja
#: pedagogy/templates/pedagogy/fragments/ue_details/grade.jinja
msgid "Global grade"
msgstr "Note globale"
#: pedagogy/templates/pedagogy/fragments/ue_details/comments.jinja
#: pedagogy/templates/pedagogy/fragments/ue_details/grade.jinja
msgid "Utility"
msgstr "Utilité"
#: pedagogy/templates/pedagogy/fragments/ue_details/comments.jinja
#: pedagogy/templates/pedagogy/fragments/ue_details/grade.jinja
msgid "Interest"
msgstr "Intérêt"
#: pedagogy/templates/pedagogy/fragments/ue_details/comments.jinja
#: pedagogy/templates/pedagogy/fragments/ue_details/grade.jinja
msgid "Teaching"
msgstr "Enseignement"
#: pedagogy/templates/pedagogy/fragments/ue_details/comments.jinja
#: pedagogy/templates/pedagogy/fragments/ue_details/grade.jinja
msgid "Work load"
msgstr "Charge de travail"
#: pedagogy/templates/pedagogy/fragments/ue_details/comments.jinja
msgid "This comment has been reported"
msgstr "Ce commentaire a été signalé"
#: pedagogy/templates/pedagogy/fragments/ue_details/comments.jinja
msgid "Report this comment"
msgstr "Signaler ce commentaire"
#: pedagogy/templates/pedagogy/fragments/ue_details/form.jinja
msgid ""
"You already posted a comment on this UE. If you want to comment again, "
"please modify or delete your previous comment."
msgstr ""
"Vous avez déjà commenté cette UE. Si vous voulez de nouveau commenter, "
"veuillez modifier ou supprimer votre commentaire précédent."
#: pedagogy/templates/pedagogy/fragments/ue_details/grade.jinja
msgid "Objectives"
msgstr "Objectifs"
#: pedagogy/templates/pedagogy/fragments/ue_details/grade.jinja
msgid "Program"
msgstr "Programme"
#: pedagogy/templates/pedagogy/fragments/ue_details/grade.jinja
msgid "Earned skills"
msgstr "Compétences acquises"
#: pedagogy/templates/pedagogy/fragments/ue_details/grade.jinja
msgid "Key concepts"
msgstr "Concepts clefs"
#: pedagogy/templates/pedagogy/fragments/ue_details/grade.jinja
msgid "UE manager: "
msgstr "Gestionnaire d'UE : "
#: pedagogy/templates/pedagogy/guide.jinja
msgid "A guide of courses available at UTBM."
msgstr "Un guide de tous les cours disponibles à l'UTBM."
@@ -5225,10 +5117,6 @@ msgstr "Un guide de tous les cours disponibles à l'UTBM."
msgid "Search UE"
msgstr "Recherche d'UE"
#: pedagogy/templates/pedagogy/guide.jinja
msgid "Hide closed UEs"
msgstr "Cacher les UEs fermées"
#: pedagogy/templates/pedagogy/guide.jinja
#, python-format
msgid "%(display_name)s"
@@ -5301,6 +5189,71 @@ msgstr "TE : "
msgid "THE: "
msgstr "THE : "
#: pedagogy/templates/pedagogy/ue_detail.jinja
msgid "Global grade"
msgstr "Note globale"
#: pedagogy/templates/pedagogy/ue_detail.jinja
msgid "Utility"
msgstr "Utilité"
#: pedagogy/templates/pedagogy/ue_detail.jinja
msgid "Interest"
msgstr "Intérêt"
#: pedagogy/templates/pedagogy/ue_detail.jinja
msgid "Teaching"
msgstr "Enseignement"
#: pedagogy/templates/pedagogy/ue_detail.jinja
msgid "Work load"
msgstr "Charge de travail"
#: pedagogy/templates/pedagogy/ue_detail.jinja
msgid "Objectives"
msgstr "Objectifs"
#: pedagogy/templates/pedagogy/ue_detail.jinja
msgid "Program"
msgstr "Programme"
#: pedagogy/templates/pedagogy/ue_detail.jinja
msgid "Earned skills"
msgstr "Compétences acquises"
#: pedagogy/templates/pedagogy/ue_detail.jinja
msgid "Key concepts"
msgstr "Concepts clefs"
#: pedagogy/templates/pedagogy/ue_detail.jinja
msgid "UE manager: "
msgstr "Gestionnaire d'UE : "
#: pedagogy/templates/pedagogy/ue_detail.jinja pedagogy/tests/tests.py
msgid ""
"You already posted a comment on this UE. If you want to comment again, "
"please modify or delete your previous comment."
msgstr ""
"Vous avez déjà commenté cette UE. Si vous voulez de nouveau commenter, "
"veuillez modifier ou supprimer votre commentaire précédent."
#: pedagogy/templates/pedagogy/ue_detail.jinja
msgid "Leave comment"
msgstr "Laisser un commentaire"
#: pedagogy/templates/pedagogy/ue_detail.jinja
#: trombi/templates/trombi/export.jinja
msgid "Comments"
msgstr "Commentaires"
#: pedagogy/templates/pedagogy/ue_detail.jinja
msgid "This comment has been reported"
msgstr "Ce commentaire a été signalé"
#: pedagogy/templates/pedagogy/ue_detail.jinja
msgid "Report this comment"
msgstr "Signaler ce commentaire"
#: pedagogy/templates/pedagogy/ue_edit.jinja
msgid "Edit UE"
msgstr "Éditer l'UE"
-2
View File
@@ -417,8 +417,6 @@ SITH_FORUM_PAGE_LENGTH = 30
SITH_SAS_ROOT_DIR_ID = env.int("SITH_SAS_ROOT_DIR_ID", default=4)
SITH_SAS_IMAGES_PER_PAGE = 60
SITH_CGU_FILE_ID = env.int("SITH_CGU_FILE_ID", default=5)
SITH_PROFILE_DEPARTMENTS = [
("TC", _("TC")),
("IMSI", _("IMSI")),
+1 -12
View File
@@ -8,22 +8,11 @@ from django.utils.translation import gettext_lazy as _
from core.models import User
from core.utils import get_last_promo
from core.views.forms import SelectDate, SelectDateTime
from core.views.forms import SelectDate
from core.views.widgets.ajax_select import AutoCompleteSelectUser
from subscription.models import Subscription
class SelectionDateForm(forms.Form):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["start_date"] = forms.DateTimeField(
label=_("Start date"), widget=SelectDateTime, required=True
)
self.fields["end_date"] = forms.DateTimeField(
label=_("End date"), widget=SelectDateTime, required=True
)
class SubscriptionForm(forms.ModelForm):
allowed_payment_methods = ["CARD", "CASH", "AE_ACCOUNT"]
@@ -11,35 +11,23 @@
{% block content %}
<p>
<form>
{{ form.start_date.label }}<br>
{{ form.start_date }}<br><br>
{{ form.end_date.label }}<br>
{{ form.end_date }}<br>
<p><input type="submit" value="{% trans %}Go{% endtrans %}" /></p>
</form>
</p>
<canvas id="statsChart" width="400" height="200"></canvas>
<p>
{% trans %}Total subscriptions{% endtrans %} : {{ subscriptions_total.count() }}<br><br>
{% trans %}Subscriptions by type{% endtrans %}<br><br>
{% for location in locations %}
{{ location[1] }} : <i class="nb">{{ subscriptions_total.filter(location=location[0]).count() }}</i><br>
{% endfor %}
<p>
<br>
<table>
<tr>
<thead>
<th>{% trans %}Subscription type{% endtrans %}</th>
{% for location in locations %}
<th>{{ location[1] }}</th>
{% endfor %}
<th id="graphLabel">{% trans %}Total{% endtrans %}</th>
</thead>
{% for type in subscriptions_types %}
<tr>
<td><i class="types" >{{ subscriptions_types[type]['name'] }}</i></td>
@@ -53,9 +41,8 @@
{% endfor %}
</td>
{% endfor %}
<td class="total"><i class="nb">{{subscriptions_total_type.count()}}</i>
<td class="total"><i class="nb">{{subscriptions_total_type.count()}}</i></td>
</tr>
{% endfor %}
</table>
{% endblock %}
+4 -21
View File
@@ -17,16 +17,14 @@ from django.conf import settings
from django.contrib.auth.forms import PasswordResetForm
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.core.exceptions import PermissionDenied
from django.urls import reverse, reverse_lazy
from django.urls import reverse
from django.utils.timezone import localdate
from django.utils.translation import gettext_lazy as _
from django.views.generic import CreateView, DetailView, TemplateView
from django.views.generic.edit import FormView
from core.views import FragmentMixin, UseFragmentsMixin
from core.views.group import PermissionGroupsUpdateView
from subscription.forms import (
SelectionDateForm,
SubscriptionExistingUserForm,
SubscriptionNewUserForm,
)
@@ -93,34 +91,19 @@ class SubscriptionPermissionView(PermissionGroupsUpdateView):
extra_context = {"object_name": _("the groups that can create subscriptions")}
class SubscriptionsStatsView(FormView):
class SubscriptionsStatsView(TemplateView):
template_name = "subscription/stats.jinja"
form_class = SelectionDateForm
success_url = reverse_lazy("subscriptions:stats")
def dispatch(self, request, *arg, **kwargs):
self.start_date = localdate()
self.end_date = self.start_date
if request.user.is_root or request.user.is_board_member:
return super().dispatch(request, *arg, **kwargs)
raise PermissionDenied
def post(self, request, *args, **kwargs):
self.form = self.get_form()
self.start_date = self.form["start_date"]
self.end_date = self.form["end_date"]
return super().post(request, *args, **kwargs)
def get_initial(self):
return {
"start_date": self.start_date.strftime("%Y-%m-%d %H:%M:%S"),
"end_date": self.end_date.strftime("%Y-%m-%d %H:%M:%S"),
}
def get_context_data(self, **kwargs):
kwargs = super().get_context_data(**kwargs)
today = localdate()
kwargs["subscriptions_total"] = Subscription.objects.filter(
subscription_end__gte=self.end_date, subscription_start__lte=self.start_date
subscription_end__gte=today, subscription_start__lte=today
)
kwargs["subscriptions_types"] = settings.SITH_SUBSCRIPTIONS
kwargs["payment_types"] = settings.SITH_SUBSCRIPTION_PAYMENT_METHOD