mirror of
https://github.com/ae-utbm/sith.git
synced 2024-11-28 20:14:23 +00:00
Split SubscriptionForm
into SubscriptionNewUserForm
and SubscriptionExistingUserForm
This commit is contained in:
parent
75406f7b58
commit
a41c381efd
@ -529,13 +529,15 @@ class User(AbstractBaseUser):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def can_create_subscription(self):
|
def can_create_subscription(self) -> bool:
|
||||||
from club.models import Club
|
from club.models import Membership
|
||||||
|
|
||||||
for club in Club.objects.filter(id__in=settings.SITH_CAN_CREATE_SUBSCRIPTIONS):
|
return (
|
||||||
if club in self.clubs_with_rights:
|
Membership.objects.board()
|
||||||
return True
|
.ongoing()
|
||||||
return False
|
.filter(club_id__in=settings.SITH_CAN_CREATE_SUBSCRIPTIONS)
|
||||||
|
.exists()
|
||||||
|
)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def is_launderette_manager(self):
|
def is_launderette_manager(self):
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
import htmx from "htmx.org";
|
import htmx from "htmx.org";
|
||||||
|
|
||||||
|
import "htmx-ext-response-targets/response-targets";
|
||||||
|
|
||||||
Object.assign(window, { htmx });
|
Object.assign(window, { htmx });
|
||||||
|
6
package-lock.json
generated
6
package-lock.json
generated
@ -22,6 +22,7 @@
|
|||||||
"d3-force-3d": "^3.0.5",
|
"d3-force-3d": "^3.0.5",
|
||||||
"easymde": "^2.18.0",
|
"easymde": "^2.18.0",
|
||||||
"glob": "^11.0.0",
|
"glob": "^11.0.0",
|
||||||
|
"htmx-ext-response-targets": "^2.0.1",
|
||||||
"htmx.org": "^2.0.3",
|
"htmx.org": "^2.0.3",
|
||||||
"jquery": "^3.7.1",
|
"jquery": "^3.7.1",
|
||||||
"jquery-ui": "^1.14.0",
|
"jquery-ui": "^1.14.0",
|
||||||
@ -4140,6 +4141,11 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/htmx-ext-response-targets": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/htmx-ext-response-targets/-/htmx-ext-response-targets-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-uCMw098+0xcrs7UW/s8l8hqj5wfOaVnVV7286cS+TNMNguo8fQpi/PEaZuT4VUysIiRcjj4pcTkuaP6Q9iJ3XA=="
|
||||||
|
},
|
||||||
"node_modules/htmx.org": {
|
"node_modules/htmx.org": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-2.0.3.tgz",
|
||||||
|
@ -47,6 +47,7 @@
|
|||||||
"easymde": "^2.18.0",
|
"easymde": "^2.18.0",
|
||||||
"glob": "^11.0.0",
|
"glob": "^11.0.0",
|
||||||
"htmx.org": "^2.0.3",
|
"htmx.org": "^2.0.3",
|
||||||
|
"htmx-ext-response-targets": "^2.0.1",
|
||||||
"jquery": "^3.7.1",
|
"jquery": "^3.7.1",
|
||||||
"jquery-ui": "^1.14.0",
|
"jquery-ui": "^1.14.0",
|
||||||
"jquery.shorten": "^1.0.0",
|
"jquery.shorten": "^1.0.0",
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
from pydantic import TypeAdapter
|
from pydantic import TypeAdapter
|
||||||
|
|
||||||
from core.views.widgets.select import (
|
from core.views.widgets.select import AutoCompleteSelect, AutoCompleteSelectMultiple
|
||||||
AutoCompleteSelect,
|
|
||||||
AutoCompleteSelectMultiple,
|
|
||||||
)
|
|
||||||
from sas.models import Album
|
from sas.models import Album
|
||||||
from sas.schemas import AlbumSchema
|
from sas.schemas import AlbumSchema
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import random
|
import secrets
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
@ -22,67 +23,90 @@ class SelectionDateForm(forms.Form):
|
|||||||
|
|
||||||
|
|
||||||
class SubscriptionForm(forms.ModelForm):
|
class SubscriptionForm(forms.ModelForm):
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
if self.errors:
|
||||||
|
# let django deal with the error messages
|
||||||
|
return super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
duration, user = self.instance.semester_duration, self.instance.member
|
||||||
|
self.instance.subscription_start = self.instance.compute_start(
|
||||||
|
duration=duration, user=user
|
||||||
|
)
|
||||||
|
self.instance.subscription_end = self.instance.compute_end(
|
||||||
|
duration=duration, start=self.instance.subscription_start, user=user
|
||||||
|
)
|
||||||
|
return super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class SubscriptionNewUserForm(SubscriptionForm):
|
||||||
|
"""Form to create subscriptions with the user they belong to.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
```py
|
||||||
|
assert not User.objects.filter(email=request.POST.get("email")).exists()
|
||||||
|
form = SubscriptionNewUserForm(request.POST)
|
||||||
|
if form.is_valid():
|
||||||
|
form.save()
|
||||||
|
|
||||||
|
# now the user exists and is subscribed
|
||||||
|
user = User.objects.get(email=request.POST.get("email"))
|
||||||
|
assert user.is_subscribed
|
||||||
|
"""
|
||||||
|
|
||||||
|
__user_fields = forms.fields_for_model(
|
||||||
|
User,
|
||||||
|
["first_name", "last_name", "email", "date_of_birth"],
|
||||||
|
widgets={"date_of_birth": SelectDate},
|
||||||
|
)
|
||||||
|
first_name = __user_fields["first_name"]
|
||||||
|
last_name = __user_fields["last_name"]
|
||||||
|
email = __user_fields["email"]
|
||||||
|
date_of_birth = __user_fields["date_of_birth"]
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Subscription
|
||||||
|
fields = ["subscription_type", "payment_method", "location"]
|
||||||
|
|
||||||
|
field_order = [
|
||||||
|
"first_name",
|
||||||
|
"last_name",
|
||||||
|
"email",
|
||||||
|
"date_of_birth",
|
||||||
|
"subscription_type",
|
||||||
|
"payment_method",
|
||||||
|
"location",
|
||||||
|
]
|
||||||
|
|
||||||
|
def clean_email(self):
|
||||||
|
email = self.cleaned_data["email"]
|
||||||
|
if User.objects.filter(email=email).exists():
|
||||||
|
raise ValidationError(_("A user with that email address already exists"))
|
||||||
|
return email
|
||||||
|
|
||||||
|
def clean(self) -> dict[str, Any]:
|
||||||
|
member = User(
|
||||||
|
first_name=self.cleaned_data.get("first_name"),
|
||||||
|
last_name=self.cleaned_data.get("last_name"),
|
||||||
|
email=self.cleaned_data.get("email"),
|
||||||
|
date_of_birth=self.cleaned_data.get("date_of_birth"),
|
||||||
|
)
|
||||||
|
member.generate_username()
|
||||||
|
member.set_password(secrets.token_urlsafe(nbytes=10))
|
||||||
|
self.instance.member = member
|
||||||
|
return super().clean()
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
if self.errors:
|
||||||
|
# let django deal with the error messages
|
||||||
|
return super().save(*args, **kwargs)
|
||||||
|
self.instance.member.save()
|
||||||
|
return super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class SubscriptionExistingUserForm(SubscriptionForm):
|
||||||
|
"""Form to add a subscription to an existing user."""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Subscription
|
model = Subscription
|
||||||
fields = ["member", "subscription_type", "payment_method", "location"]
|
fields = ["member", "subscription_type", "payment_method", "location"]
|
||||||
widgets = {"member": AutoCompleteSelectUser}
|
widgets = {"member": AutoCompleteSelectUser}
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
self.fields["member"].required = False
|
|
||||||
self.fields |= forms.fields_for_model(
|
|
||||||
User,
|
|
||||||
fields=["first_name", "last_name", "email", "date_of_birth"],
|
|
||||||
widgets={"date_of_birth": SelectDate},
|
|
||||||
)
|
|
||||||
|
|
||||||
def clean_member(self):
|
|
||||||
subscriber = self.cleaned_data.get("member")
|
|
||||||
if subscriber:
|
|
||||||
subscriber = User.objects.filter(id=subscriber.id).first()
|
|
||||||
return subscriber
|
|
||||||
|
|
||||||
def clean(self):
|
|
||||||
cleaned_data = super().clean()
|
|
||||||
if (
|
|
||||||
cleaned_data.get("member") is None
|
|
||||||
and "last_name" not in self.errors.as_data()
|
|
||||||
and "first_name" not in self.errors.as_data()
|
|
||||||
and "email" not in self.errors.as_data()
|
|
||||||
and "date_of_birth" not in self.errors.as_data()
|
|
||||||
):
|
|
||||||
self.errors.pop("member", None)
|
|
||||||
if self.errors:
|
|
||||||
return cleaned_data
|
|
||||||
if User.objects.filter(email=cleaned_data.get("email")).first() is not None:
|
|
||||||
self.add_error(
|
|
||||||
"email",
|
|
||||||
ValidationError(_("A user with that email address already exists")),
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
u = User(
|
|
||||||
last_name=self.cleaned_data.get("last_name"),
|
|
||||||
first_name=self.cleaned_data.get("first_name"),
|
|
||||||
email=self.cleaned_data.get("email"),
|
|
||||||
date_of_birth=self.cleaned_data.get("date_of_birth"),
|
|
||||||
)
|
|
||||||
u.generate_username()
|
|
||||||
u.set_password(str(random.randrange(1000000, 10000000)))
|
|
||||||
u.save()
|
|
||||||
cleaned_data["member"] = u
|
|
||||||
elif cleaned_data.get("member") is not None:
|
|
||||||
self.errors.pop("last_name", None)
|
|
||||||
self.errors.pop("first_name", None)
|
|
||||||
self.errors.pop("email", None)
|
|
||||||
self.errors.pop("date_of_birth", None)
|
|
||||||
if cleaned_data.get("member") is None:
|
|
||||||
# This should be handled here,
|
|
||||||
# but it is done in the Subscription model's clean method
|
|
||||||
# TODO investigate why!
|
|
||||||
raise ValidationError(
|
|
||||||
_(
|
|
||||||
"You must either choose an existing "
|
|
||||||
"user or create a new one properly"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return cleaned_data
|
|
||||||
|
@ -93,22 +93,23 @@ class Subscription(models.Model):
|
|||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
today = localdate()
|
today = localdate()
|
||||||
active_subscriptions = Subscription.objects.exclude(pk=self.pk).filter(
|
threshold = timedelta(weeks=settings.SITH_SUBSCRIPTION_END)
|
||||||
subscription_start__gte=today, subscription_end__lte=today
|
# a user may subscribe if :
|
||||||
|
# - he/she is not currently subscribed
|
||||||
|
# - its current subscription ends in less than a few weeks
|
||||||
|
overlapping_subscriptions = Subscription.objects.exclude(pk=self.pk).filter(
|
||||||
|
member=self.member,
|
||||||
|
subscription_start__lte=today,
|
||||||
|
subscription_end__gte=today + threshold,
|
||||||
)
|
)
|
||||||
for s in active_subscriptions:
|
if overlapping_subscriptions.exists():
|
||||||
if (
|
raise ValidationError(
|
||||||
s.is_valid_now()
|
_("You can not subscribe many time for the same period")
|
||||||
and s.subscription_end - timedelta(weeks=settings.SITH_SUBSCRIPTION_END)
|
)
|
||||||
> date.today()
|
|
||||||
):
|
|
||||||
raise ValidationError(
|
|
||||||
_("You can not subscribe many time for the same period")
|
|
||||||
)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def compute_start(
|
def compute_start(
|
||||||
d: date | None = None, duration: int = 1, user: User | None = None
|
d: date | None = None, duration: int | float = 1, user: User | None = None
|
||||||
) -> date:
|
) -> date:
|
||||||
"""Computes the start date of the subscription.
|
"""Computes the start date of the subscription.
|
||||||
|
|
||||||
@ -132,7 +133,7 @@ class Subscription(models.Model):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def compute_end(
|
def compute_end(
|
||||||
duration: int, start: date | None = None, user: User | None = None
|
duration: int | float, start: date | None = None, user: User | None = None
|
||||||
) -> date:
|
) -> date:
|
||||||
"""Compute the end date of the subscription.
|
"""Compute the end date of the subscription.
|
||||||
|
|
||||||
@ -163,3 +164,19 @@ class Subscription(models.Model):
|
|||||||
|
|
||||||
def is_valid_now(self):
|
def is_valid_now(self):
|
||||||
return self.subscription_start <= date.today() <= self.subscription_end
|
return self.subscription_start <= date.today() <= self.subscription_end
|
||||||
|
|
||||||
|
@property
|
||||||
|
def semester_duration(self) -> float:
|
||||||
|
"""Duration of this subscription, in number of semester.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
The `Subscription` object doesn't have to actually exist
|
||||||
|
in the database to access this property
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
```py
|
||||||
|
subscription = Subscription(subscription_type="deux-semestres")
|
||||||
|
assert subscription.semester_duration == 2.0
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
return settings.SITH_SUBSCRIPTIONS[self.subscription_type]["duration"]
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
<form
|
||||||
|
hx-post="{{ post_url }}"
|
||||||
|
hx-target="this"
|
||||||
|
hx-disabled-elt="find input[type='submit']"
|
||||||
|
hx-swap="outerHTML"
|
||||||
|
>
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form.as_div() }}
|
||||||
|
<input type="submit" value="{% trans %}Save{% endtrans %}">
|
||||||
|
</form>
|
@ -0,0 +1,28 @@
|
|||||||
|
<div class="alert alert-green">
|
||||||
|
<div class="alert-main">
|
||||||
|
<h3 class="alert-title">
|
||||||
|
{% trans user=subscription.member %}Subscription created for {{ user }}{% endtrans %}
|
||||||
|
</h3>
|
||||||
|
<p>
|
||||||
|
|
||||||
|
{% trans trimmed user=subscription.member.get_short_name(), type=subscription.subscription_type, end=subscription.subscription_end %}
|
||||||
|
{{ user }} received its new {{ type }} subscription.
|
||||||
|
It will be active until {{ end }} included.
|
||||||
|
{% endtrans %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="alert-aside">
|
||||||
|
<a class="btn btn-blue" href="{{ subscription.member.get_absolute_url() }}">
|
||||||
|
{% trans %}Go to user profile{% endtrans %}
|
||||||
|
</a>
|
||||||
|
<br>
|
||||||
|
<a class="btn btn-grey" href="{{ url("subscription:subscription") }}">
|
||||||
|
{# We don't know if this fragment is displayed after creating a subscription
|
||||||
|
for a previously existing user or for a newly created one.
|
||||||
|
Thus, we don't know which form should be used to create another subscription
|
||||||
|
in this place.
|
||||||
|
Therefore, we reload the entire page. It just works. #}
|
||||||
|
{% trans %}Create another subscription{% endtrans %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -1,62 +1,38 @@
|
|||||||
{% extends "core/base.jinja" %}
|
{% extends "core/base.jinja" %}
|
||||||
|
|
||||||
|
{% from "core/macros.jinja" import tabs %}
|
||||||
|
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{% trans %}New subscription{% endtrans %}
|
{% trans %}New subscription{% endtrans %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{# The following statics are bundled with our autocomplete select.
|
||||||
|
However, if one tries to swap a form by another, then the urls in script-once
|
||||||
|
and link-once disappear.
|
||||||
|
So we give them here.
|
||||||
|
If the aforementioned bug is resolved, you can remove this. #}
|
||||||
|
{% block additional_js %}
|
||||||
|
<script type="module" defer src="{{ static("bundled/core/components/ajax-select-index.ts") }}"></script>
|
||||||
|
{% endblock %}
|
||||||
|
{% block additional_css %}
|
||||||
|
<link rel="stylesheet" href="{{ static("bundled/core/components/ajax-select-index.css") }}">
|
||||||
|
<link rel="stylesheet" href="{{ static("core/components/ajax-select.scss") }}">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% macro form_fragment(form_object, post_url) %}
|
||||||
|
{# Include the form fragment inside a with block,
|
||||||
|
in order to inject the right form in the right place #}
|
||||||
|
{% with form=form_object, post_url=post_url %}
|
||||||
|
{% include "subscription/fragments/creation_form.jinja" %}
|
||||||
|
{% endwith %}
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h3>{% trans %}New subscription{% endtrans %}</h3>
|
<h3>{% trans %}New subscription{% endtrans %}</h3>
|
||||||
<div id="user_info"></div>
|
<div id="subscription-form" hx-ext="response-targets">
|
||||||
<form action="" method="post" id="subscription_form">
|
{{ tabs([
|
||||||
{% csrf_token %}
|
(_("Existing member"), form_fragment(existing_user_form, existing_user_post_url)),
|
||||||
{{ form.non_field_errors() }}
|
(_("New member"), form_fragment(new_user_form, new_user_post_url)),
|
||||||
<p>{{ form.member.errors }}<label for="{{ form.member.name }}">{{ form.member.label }}</label> {{ form.member }}</p>
|
]) }}
|
||||||
<div id="new_member">
|
</div>
|
||||||
<p>{{ form.first_name.errors }}<label for="{{ form.first_name.name }}">{{ form.first_name.label }}</label> {{ form.first_name }}</p>
|
|
||||||
<p>{{ form.last_name.errors }}<label for="{{ form.last_name.name }}">{{ form.last_name.label }}</label> {{ form.last_name }}</p>
|
|
||||||
<p>{{ form.email.errors }}<label for="{{ form.email.name }}">{{ form.email.label }}</label> {{ form.email }}</p>
|
|
||||||
<p>{{ form.date_of_birth.errors }}<label for="{{ form.date_of_birth.name }}">{{ form.date_of_birth.label}}</label> {{ form.date_of_birth }}</p>
|
|
||||||
</div>
|
|
||||||
<p>{{ form.subscription_type.errors }}<label for="{{ form.subscription_type.name }}">{{ form.subscription_type.label }}</label> {{ form.subscription_type }}</p>
|
|
||||||
<p>{{ form.payment_method.errors }}<label for="{{ form.payment_method.name }}">{{ form.payment_method.label }}</label> {{
|
|
||||||
form.payment_method }}</p>
|
|
||||||
<p>{% trans %}Eboutic is reserved to specific users. In doubt, don't use it.{% endtrans %}</p>
|
|
||||||
<p>{{ form.location.errors }}<label for="{{ form.location.name }}">{{ form.location.label }}</label> {{ form.location }}</p>
|
|
||||||
<p><input type="submit" value="{% trans %}Save{% endtrans %}" /></p>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block script %}
|
|
||||||
{{ super() }}
|
|
||||||
<script type="text/javascript" charset="utf-8">
|
|
||||||
$( function() {
|
|
||||||
select = $("#id_member");
|
|
||||||
member_block = $("#subscription_form #new_member");
|
|
||||||
user_info = $("#user_info");
|
|
||||||
function display_new_member() {
|
|
||||||
if (select.val()) {
|
|
||||||
member_block.hide();
|
|
||||||
member_block.children().each(function() {
|
|
||||||
$(this).children().each(function() {
|
|
||||||
$(this).removeAttr('required');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
user_info.load("/user/"+select.val()+"/mini");
|
|
||||||
user_info.show();
|
|
||||||
} else {
|
|
||||||
member_block.show();
|
|
||||||
member_block.children().each(function() {
|
|
||||||
$(this).children().each(function() {
|
|
||||||
$(this).prop('required', true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
user_info.empty();
|
|
||||||
user_info.hide();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
select.on("change", display_new_member);
|
|
||||||
display_new_member();
|
|
||||||
} );
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -15,10 +15,31 @@
|
|||||||
|
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from subscription.views import NewSubscription, SubscriptionsStatsView
|
from subscription.views import (
|
||||||
|
CreateSubscriptionExistingUserFragment,
|
||||||
|
CreateSubscriptionNewUserFragment,
|
||||||
|
NewSubscription,
|
||||||
|
SubscriptionCreatedFragment,
|
||||||
|
SubscriptionsStatsView,
|
||||||
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# Subscription views
|
# Subscription views
|
||||||
path("", NewSubscription.as_view(), name="subscription"),
|
path("", NewSubscription.as_view(), name="subscription"),
|
||||||
|
path(
|
||||||
|
"fragment/existing-user/",
|
||||||
|
CreateSubscriptionExistingUserFragment.as_view(),
|
||||||
|
name="fragment-existing-user",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"fragment/new-user/",
|
||||||
|
CreateSubscriptionNewUserFragment.as_view(),
|
||||||
|
name="fragment-new-user",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"fragment/<int:subscription_id>/creation-success",
|
||||||
|
SubscriptionCreatedFragment.as_view(),
|
||||||
|
name="creation-success",
|
||||||
|
),
|
||||||
path("stats/", SubscriptionsStatsView.as_view(), name="stats"),
|
path("stats/", SubscriptionsStatsView.as_view(), name="stats"),
|
||||||
]
|
]
|
||||||
|
@ -13,85 +13,94 @@
|
|||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
||||||
import secrets
|
|
||||||
|
|
||||||
from django import forms
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.contrib.auth.mixins import UserPassesTestMixin
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse, reverse_lazy
|
||||||
from django.views.generic.edit import CreateView, FormView
|
from django.utils.timezone import localdate
|
||||||
|
from django.views.generic import CreateView, DetailView, TemplateView
|
||||||
|
from django.views.generic.edit import FormView
|
||||||
|
|
||||||
from subscription.forms import SelectionDateForm, SubscriptionForm
|
from subscription.forms import (
|
||||||
|
SelectionDateForm,
|
||||||
|
SubscriptionExistingUserForm,
|
||||||
|
SubscriptionNewUserForm,
|
||||||
|
)
|
||||||
from subscription.models import Subscription
|
from subscription.models import Subscription
|
||||||
|
|
||||||
|
|
||||||
class NewSubscription(CreateView):
|
class CanCreateSubscriptionMixin(UserPassesTestMixin):
|
||||||
|
def test_func(self):
|
||||||
|
return self.request.user.can_create_subscription
|
||||||
|
|
||||||
|
|
||||||
|
class NewSubscription(CanCreateSubscriptionMixin, TemplateView):
|
||||||
template_name = "subscription/subscription.jinja"
|
template_name = "subscription/subscription.jinja"
|
||||||
form_class = SubscriptionForm
|
|
||||||
|
|
||||||
def dispatch(self, request, *arg, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
if request.user.can_create_subscription:
|
return super().get_context_data(**kwargs) | {
|
||||||
return super().dispatch(request, *arg, **kwargs)
|
"existing_user_form": SubscriptionExistingUserForm(),
|
||||||
raise PermissionDenied
|
"new_user_form": SubscriptionNewUserForm(),
|
||||||
|
"existing_user_post_url": reverse("subscription:fragment-existing-user"),
|
||||||
|
"new_user_post_url": reverse("subscription:fragment-new-user"),
|
||||||
|
}
|
||||||
|
|
||||||
def get_initial(self):
|
|
||||||
if "member" in self.request.GET:
|
|
||||||
return {
|
|
||||||
"member": self.request.GET["member"],
|
|
||||||
"subscription_type": "deux-semestres",
|
|
||||||
}
|
|
||||||
return {"subscription_type": "deux-semestres"}
|
|
||||||
|
|
||||||
def form_valid(self, form):
|
class CreateSubscriptionFragment(CanCreateSubscriptionMixin, CreateView):
|
||||||
form.instance.subscription_start = Subscription.compute_start(
|
template_name = "subscription/fragments/creation_form.jinja"
|
||||||
duration=settings.SITH_SUBSCRIPTIONS[form.instance.subscription_type][
|
|
||||||
"duration"
|
def get_success_url(self):
|
||||||
],
|
return reverse(
|
||||||
user=form.instance.member,
|
"subscription:creation-success", kwargs={"subscription_id": self.object.id}
|
||||||
)
|
)
|
||||||
form.instance.subscription_end = Subscription.compute_end(
|
|
||||||
duration=settings.SITH_SUBSCRIPTIONS[form.instance.subscription_type][
|
|
||||||
"duration"
|
class CreateSubscriptionExistingUserFragment(CreateSubscriptionFragment):
|
||||||
],
|
"""Create a subscription for a user who already exists."""
|
||||||
start=form.instance.subscription_start,
|
|
||||||
user=form.instance.member,
|
form_class = SubscriptionExistingUserForm
|
||||||
)
|
extra_context = {"post_url": reverse_lazy("subscription:fragment-existing-user")}
|
||||||
return super().form_valid(form)
|
|
||||||
|
|
||||||
|
class CreateSubscriptionNewUserFragment(CreateSubscriptionFragment):
|
||||||
|
"""Create a subscription for a user who already exists."""
|
||||||
|
|
||||||
|
form_class = SubscriptionNewUserForm
|
||||||
|
extra_context = {"post_url": reverse_lazy("subscription:fragment-new-user")}
|
||||||
|
|
||||||
|
|
||||||
|
class SubscriptionCreatedFragment(CanCreateSubscriptionMixin, DetailView):
|
||||||
|
template_name = "subscription/fragments/creation_success.jinja"
|
||||||
|
model = Subscription
|
||||||
|
pk_url_kwarg = "subscription_id"
|
||||||
|
context_object_name = "subscription"
|
||||||
|
|
||||||
|
|
||||||
class SubscriptionsStatsView(FormView):
|
class SubscriptionsStatsView(FormView):
|
||||||
template_name = "subscription/stats.jinja"
|
template_name = "subscription/stats.jinja"
|
||||||
form_class = SelectionDateForm
|
form_class = SelectionDateForm
|
||||||
|
success_url = reverse_lazy("subscriptions:stats")
|
||||||
|
|
||||||
def dispatch(self, request, *arg, **kwargs):
|
def dispatch(self, request, *arg, **kwargs):
|
||||||
import datetime
|
self.start_date = localdate()
|
||||||
|
|
||||||
self.start_date = datetime.datetime.today()
|
|
||||||
self.end_date = self.start_date
|
self.end_date = self.start_date
|
||||||
res = super().dispatch(request, *arg, **kwargs)
|
|
||||||
if request.user.is_root or request.user.is_board_member:
|
if request.user.is_root or request.user.is_board_member:
|
||||||
return res
|
return super().dispatch(request, *arg, **kwargs)
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
self.form = self.get_form()
|
self.form = self.get_form()
|
||||||
self.start_date = self.form["start_date"]
|
self.start_date = self.form["start_date"]
|
||||||
self.end_date = self.form["end_date"]
|
self.end_date = self.form["end_date"]
|
||||||
res = super().post(request, *args, **kwargs)
|
return super().post(request, *args, **kwargs)
|
||||||
if request.user.is_root or request.user.is_board_member:
|
|
||||||
return res
|
|
||||||
raise PermissionDenied
|
|
||||||
|
|
||||||
def get_initial(self):
|
def get_initial(self):
|
||||||
init = {
|
return {
|
||||||
"start_date": self.start_date.strftime("%Y-%m-%d %H:%M:%S"),
|
"start_date": self.start_date.strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
"end_date": self.end_date.strftime("%Y-%m-%d %H:%M:%S"),
|
"end_date": self.end_date.strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
}
|
}
|
||||||
return init
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
from subscription.models import Subscription
|
|
||||||
|
|
||||||
kwargs = super().get_context_data(**kwargs)
|
kwargs = super().get_context_data(**kwargs)
|
||||||
kwargs["subscriptions_total"] = Subscription.objects.filter(
|
kwargs["subscriptions_total"] = Subscription.objects.filter(
|
||||||
subscription_end__gte=self.end_date, subscription_start__lte=self.start_date
|
subscription_end__gte=self.end_date, subscription_start__lte=self.start_date
|
||||||
@ -100,6 +109,3 @@ class SubscriptionsStatsView(FormView):
|
|||||||
kwargs["payment_types"] = settings.SITH_COUNTER_PAYMENT_METHOD
|
kwargs["payment_types"] = settings.SITH_COUNTER_PAYMENT_METHOD
|
||||||
kwargs["locations"] = settings.SITH_SUBSCRIPTION_LOCATIONS
|
kwargs["locations"] = settings.SITH_SUBSCRIPTION_LOCATIONS
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
def get_success_url(self, **kwargs):
|
|
||||||
return reverse_lazy("subscriptions:stats")
|
|
||||||
|
@ -85,6 +85,7 @@ export default defineConfig((config: UserConfig) => {
|
|||||||
inject({
|
inject({
|
||||||
// biome-ignore lint/style/useNamingConvention: that's how it's called
|
// biome-ignore lint/style/useNamingConvention: that's how it's called
|
||||||
Alpine: "alpinejs",
|
Alpine: "alpinejs",
|
||||||
|
htmx: "htmx.org",
|
||||||
}),
|
}),
|
||||||
viteStaticCopy({
|
viteStaticCopy({
|
||||||
targets: [
|
targets: [
|
||||||
|
Loading…
Reference in New Issue
Block a user