mirror of
https://github.com/ae-utbm/sith.git
synced 2025-09-13 11:35:44 +00:00
require birthday when creating subscriptions for users that didn't give it previously
This commit is contained in:
11
core/api.py
11
core/api.py
@@ -25,6 +25,7 @@ from core.schemas import (
|
||||
UserFamilySchema,
|
||||
UserFilterSchema,
|
||||
UserProfileSchema,
|
||||
UserSchema,
|
||||
)
|
||||
from core.templatetags.renderer import markdown
|
||||
|
||||
@@ -69,16 +70,22 @@ class MailingListController(ControllerBase):
|
||||
return data
|
||||
|
||||
|
||||
@api_controller("/user", permissions=[CanAccessLookup])
|
||||
@api_controller("/user")
|
||||
class UserController(ControllerBase):
|
||||
@route.get("", response=list[UserProfileSchema])
|
||||
@route.get("", response=list[UserProfileSchema], permissions=[CanAccessLookup])
|
||||
def fetch_profiles(self, pks: Query[set[int]]):
|
||||
return User.objects.filter(pk__in=pks)
|
||||
|
||||
@route.get("/{int:user_id}", response=UserSchema, permissions=[CanView])
|
||||
def fetch_user(self, user_id: int):
|
||||
"""Fetch a single user"""
|
||||
return self.get_object_or_exception(User, id=user_id)
|
||||
|
||||
@route.get(
|
||||
"/search",
|
||||
response=PaginatedResponseSchema[UserProfileSchema],
|
||||
url_name="search_users",
|
||||
permissions=[CanAccessLookup],
|
||||
)
|
||||
@paginate(PageNumberPaginationExtra, page_size=20)
|
||||
def search_users(self, filters: Query[UserFilterSchema]):
|
||||
|
@@ -34,6 +34,22 @@ class SimpleUserSchema(ModelSchema):
|
||||
fields = ["id", "nick_name", "first_name", "last_name"]
|
||||
|
||||
|
||||
class UserSchema(ModelSchema):
|
||||
class Meta:
|
||||
model = User
|
||||
fields = [
|
||||
"id",
|
||||
"nick_name",
|
||||
"first_name",
|
||||
"last_name",
|
||||
"date_of_birth",
|
||||
"email",
|
||||
"role",
|
||||
"quote",
|
||||
"promo",
|
||||
]
|
||||
|
||||
|
||||
class UserProfileSchema(ModelSchema):
|
||||
"""The necessary information to show a user profile"""
|
||||
|
||||
|
@@ -6,7 +6,7 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-09-01 18:18+0200\n"
|
||||
"POT-Creation-Date: 2025-09-02 15:56+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"
|
||||
@@ -5135,6 +5135,10 @@ msgstr "Tee-shirt AE"
|
||||
msgid "A user with that email address already exists"
|
||||
msgstr "Un utilisateur avec cette adresse email existe déjà"
|
||||
|
||||
#: subscription/forms.py
|
||||
msgid "This user didn't fill its birthdate yet."
|
||||
msgstr "Cet utilisateur n'a pas encore renseigné sa date de naissance"
|
||||
|
||||
#: subscription/models.py
|
||||
msgid "Bad subscription type"
|
||||
msgstr "Mauvais type de cotisation"
|
||||
@@ -5174,7 +5178,7 @@ msgid ""
|
||||
"%(user)s received its new %(type)s subscription. It will be active until "
|
||||
"%(end)s included."
|
||||
msgstr ""
|
||||
"%(user)s a reçu sa nouvelle cotisaton %(type)s. Elle sert active jusqu'au "
|
||||
"%(user)s a reçu sa nouvelle cotisaton %(type)s. Elle sera active jusqu'au "
|
||||
"%(end)s inclu."
|
||||
|
||||
#: subscription/templates/subscription/fragments/creation_success.jinja
|
||||
|
@@ -23,8 +23,8 @@ class SelectionDateForm(forms.Form):
|
||||
|
||||
|
||||
class SubscriptionForm(forms.ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
initial = kwargs.pop("initial", {})
|
||||
def __init__(self, *args, initial=None, **kwargs):
|
||||
initial = initial or {}
|
||||
if "subscription_type" not in initial:
|
||||
initial["subscription_type"] = "deux-semestres"
|
||||
if "payment_method" not in initial:
|
||||
@@ -131,8 +131,55 @@ class SubscriptionExistingUserForm(SubscriptionForm):
|
||||
"""Form to add a subscription to an existing user."""
|
||||
|
||||
template_name = "subscription/forms/create_existing_user.html"
|
||||
required_css_class = "required"
|
||||
|
||||
birthdate = forms.fields_for_model(
|
||||
User,
|
||||
["date_of_birth"],
|
||||
widgets={"date_of_birth": SelectDate(attrs={"hidden": True})},
|
||||
help_texts={"date_of_birth": _("This user didn't fill its birthdate yet.")},
|
||||
)["date_of_birth"]
|
||||
|
||||
class Meta:
|
||||
model = Subscription
|
||||
fields = ["member", "subscription_type", "payment_method", "location"]
|
||||
widgets = {"member": AutoCompleteSelectUser}
|
||||
|
||||
field_order = [
|
||||
"member",
|
||||
"birthdate",
|
||||
"subscription_type",
|
||||
"payment_method",
|
||||
"location",
|
||||
]
|
||||
|
||||
def __init__(self, *args, initial=None, **kwargs):
|
||||
super().__init__(*args, initial=initial, **kwargs)
|
||||
self.fields["birthdate"].required = True
|
||||
if not initial:
|
||||
return
|
||||
member = initial.get("member")
|
||||
if member:
|
||||
member = User.objects.filter(id=member).first()
|
||||
if member and member.date_of_birth:
|
||||
# if there is an initial member with a birthdate,
|
||||
# there is no need to ask this to the user
|
||||
self.fields["birthdate"].initial = member.date_of_birth
|
||||
elif member:
|
||||
# if there is an initial member without a birthdate,
|
||||
# then the field must be displayed
|
||||
self.fields["birthdate"].widget.attrs.update({"hidden": False})
|
||||
# if there is no initial member, it means that it will be
|
||||
# dynamically selected using the AutoCompleteSelectUser widget.
|
||||
# JS will take care of un-hiding the field if necessary
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if self.errors:
|
||||
return super().save(*args, **kwargs)
|
||||
if (
|
||||
self.cleaned_data["birthdate"] is not None
|
||||
and self.instance.member.date_of_birth != self.cleaned_data["birthdate"]
|
||||
):
|
||||
self.instance.member.date_of_birth = self.cleaned_data["birthdate"]
|
||||
self.instance.member.save()
|
||||
return super().save(*args, **kwargs)
|
||||
|
@@ -1,3 +1,5 @@
|
||||
import { userFetchUser } from "#openapi";
|
||||
|
||||
document.addEventListener("alpine:init", () => {
|
||||
Alpine.data("existing_user_subscription_form", () => ({
|
||||
loading: false,
|
||||
@@ -12,13 +14,24 @@ document.addEventListener("alpine:init", () => {
|
||||
},
|
||||
|
||||
async loadProfile(userId: number) {
|
||||
const birthdayInput = document.getElementById("id_birthdate") as HTMLInputElement;
|
||||
if (!Number.isInteger(userId)) {
|
||||
this.profileFragment = "";
|
||||
birthdayInput.hidden = true;
|
||||
return;
|
||||
}
|
||||
this.loading = true;
|
||||
const response = await fetch(`/user/${userId}/mini/`);
|
||||
this.profileFragment = await response.text();
|
||||
const [miniProfile, userInfos] = await Promise.all([
|
||||
fetch(`/user/${userId}/mini/`),
|
||||
// biome-ignore lint/style/useNamingConvention: api is snake_case
|
||||
userFetchUser({ path: { user_id: userId } }),
|
||||
]);
|
||||
this.profileFragment = await miniProfile.text();
|
||||
// If the user has no birthdate yet, show the form input
|
||||
// to fill this info.
|
||||
// Else keep the input hidden and change its value to the user birthdate
|
||||
birthdayInput.value = userInfos.data.date_of_birth;
|
||||
birthdayInput.hidden = userInfos.data.date_of_birth !== null;
|
||||
this.loading = false;
|
||||
},
|
||||
}));
|
||||
|
@@ -23,6 +23,11 @@
|
||||
* then display the user profile right in the middle of the remaining space. */
|
||||
fieldset {
|
||||
flex: 0 1 auto;
|
||||
|
||||
p:has(input[hidden]) {
|
||||
// when the input is hidden, hide the whole label+input+help text group
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
#subscription-form-user-mini-profile {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
"""Tests focused on testing subscription creation"""
|
||||
|
||||
from datetime import timedelta
|
||||
from datetime import date, timedelta
|
||||
from typing import Callable
|
||||
|
||||
import pytest
|
||||
@@ -31,6 +31,26 @@ def test_form_existing_user_valid(
|
||||
):
|
||||
"""Test `SubscriptionExistingUserForm`"""
|
||||
user = user_factory()
|
||||
user.date_of_birth = date(year=1967, month=3, day=14)
|
||||
user.save()
|
||||
data = {
|
||||
"member": user,
|
||||
"birthdate": user.date_of_birth,
|
||||
"subscription_type": "deux-semestres",
|
||||
"location": settings.SITH_SUBSCRIPTION_LOCATIONS[0][0],
|
||||
"payment_method": settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0][0],
|
||||
}
|
||||
form = SubscriptionExistingUserForm(data)
|
||||
assert form.is_valid()
|
||||
form.save()
|
||||
user.refresh_from_db()
|
||||
assert user.is_subscribed
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_form_existing_user_with_birthdate(settings: SettingsWrapper):
|
||||
"""Test `SubscriptionExistingUserForm`"""
|
||||
user = baker.make(User, date_of_birth=None)
|
||||
data = {
|
||||
"member": user,
|
||||
"subscription_type": "deux-semestres",
|
||||
@@ -38,11 +58,15 @@ def test_form_existing_user_valid(
|
||||
"payment_method": settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0][0],
|
||||
}
|
||||
form = SubscriptionExistingUserForm(data)
|
||||
assert not form.is_valid()
|
||||
|
||||
data |= {"birthdate": date(year=1967, month=3, day=14)}
|
||||
form = SubscriptionExistingUserForm(data)
|
||||
assert form.is_valid()
|
||||
form.save()
|
||||
user.refresh_from_db()
|
||||
assert user.is_subscribed
|
||||
assert user.date_of_birth == date(year=1967, month=3, day=14)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@@ -132,6 +156,14 @@ def test_page_access(
|
||||
assert res.status_code == status_code
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_page_access_with_get_data(client: Client):
|
||||
user = old_subscriber_user.make()
|
||||
client.force_login(baker.make(User, is_superuser=True))
|
||||
res = client.get(reverse("subscription:subscription", query={"member": user.id}))
|
||||
assert res.status_code == 200
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_submit_form_existing_user(client: Client, settings: SettingsWrapper):
|
||||
client.force_login(
|
||||
@@ -140,11 +172,12 @@ def test_submit_form_existing_user(client: Client, settings: SettingsWrapper):
|
||||
user_permissions=Permission.objects.filter(codename="add_subscription"),
|
||||
)
|
||||
)
|
||||
user = old_subscriber_user.make()
|
||||
user = old_subscriber_user.make(date_of_birth=date(year=1967, month=3, day=14))
|
||||
response = client.post(
|
||||
reverse("subscription:fragment-existing-user"),
|
||||
{
|
||||
"member": user.id,
|
||||
"birthdate": user.date_of_birth,
|
||||
"subscription_type": "deux-semestres",
|
||||
"location": settings.SITH_SUBSCRIPTION_LOCATIONS[0][0],
|
||||
"payment_method": settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0][0],
|
||||
|
Reference in New Issue
Block a user