start of work

This commit is contained in:
imperosol 2024-11-19 00:41:49 +01:00
parent 0270fb764e
commit cbbbdc4bbf
13 changed files with 173 additions and 152 deletions

View File

@ -529,13 +529,15 @@ class User(AbstractBaseUser):
return False
@cached_property
def can_create_subscription(self):
from club.models import Club
def can_create_subscription(self) -> bool:
from club.models import Membership
for club in Club.objects.filter(id__in=settings.SITH_CAN_CREATE_SUBSCRIPTIONS):
if club in self.clubs_with_rights:
return True
return False
return (
Membership.objects.board()
.ongoing()
.filter(club_id__in=settings.SITH_CAN_CREATE_SUBSCRIPTIONS)
.exists()
)
@cached_property
def is_launderette_manager(self):

View File

@ -1,3 +1,4 @@
import "htmx-ext-response-targets/response-targets";
import htmx from "htmx.org";
Object.assign(window, { htmx });

View File

@ -406,7 +406,7 @@ class Galaxy(models.Model):
cls.logger.debug(f"\t\t> Scaled distance: {value}")
return int(value)
def rule(self, picture_count_threshold=10) -> None:
def rule(self, picture_count_threshold=-1) -> None:
"""Main function of the Galaxy.
Iterate over all the rulable users to promote them to citizens.

6
package-lock.json generated
View File

@ -22,6 +22,7 @@
"d3-force-3d": "^3.0.5",
"easymde": "^2.18.0",
"glob": "^11.0.0",
"htmx-ext-response-targets": "^2.0.1",
"htmx.org": "^2.0.3",
"jquery": "^3.7.1",
"jquery-ui": "^1.14.0",
@ -4140,6 +4141,11 @@
"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": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-2.0.3.tgz",

View File

@ -14,7 +14,9 @@
"keywords": [],
"author": "",
"license": "GPL-3.0-only",
"sideEffects": [".css"],
"sideEffects": [
".css"
],
"imports": {
"#openapi": "./staticfiles/generated/openapi/index.ts",
"#core:*": "./core/static/bundled/*",
@ -47,6 +49,7 @@
"easymde": "^2.18.0",
"glob": "^11.0.0",
"htmx.org": "^2.0.3",
"htmx-ext-response-targets": "^2.0.1",
"jquery": "^3.7.1",
"jquery-ui": "^1.14.0",
"jquery.shorten": "^1.0.0",

View File

@ -1,9 +1,6 @@
from pydantic import TypeAdapter
from core.views.widgets.select import (
AutoCompleteSelect,
AutoCompleteSelectMultiple,
)
from core.views.widgets.select import AutoCompleteSelect, AutoCompleteSelectMultiple
from sas.models import Album
from sas.schemas import AlbumSchema

View File

@ -5,7 +5,7 @@ from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
from core.models import User
from core.views.forms import SelectDate, SelectDateTime
from core.views.forms import SelectDateTime
from core.views.widgets.select import AutoCompleteSelectUser
from subscription.models import Subscription
@ -21,37 +21,15 @@ class SelectionDateForm(forms.Form):
)
class SubscriptionForm(forms.ModelForm):
class Meta:
model = Subscription
fields = ["member", "subscription_type", "payment_method", "location"]
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
class SubscriptionNewUserForm(forms.Form):
def clean(self):
cleaned_data = super().clean()
if (
cleaned_data.get("member") is None
and "last_name" not in self.errors.as_data()
"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:
@ -70,19 +48,17 @@ class SubscriptionForm(forms.ModelForm):
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
class SubscriptionForm(forms.ModelForm):
"""Form to add a subscription to an existing user."""
template_name = "subscription/fragments/existing_user_form.html"
class Meta:
model = Subscription
fields = ["member", "subscription_type", "payment_method", "location"]
widgets = {"member": AutoCompleteSelectUser}
def clean_subscription_type(self):
raise ValidationError("ptdr non")

View File

@ -0,0 +1,12 @@
{#<form#}
{# hx-post="{{ url("subscription:subscription-fragment-existing-user") }}"#}
{# hx-swap="innerHTML"#}
{# hx-target="#existing-user-subscription-form-fields"#}
{# id="existing-user-subscription-form"#}
{#>#}
{# {% csrf_token %}#}
{# <div id="existing-user-subscription-form-fields">#}
{{ form.as_div }}
{# </div>#}
{# <input type="submit" value="{% trans %}Save{% endtrans %}">#}
{#</form>#}

View File

@ -4,59 +4,65 @@
{% trans %}New subscription{% endtrans %}
{% 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 defer src="{{ static("webpack/core/components/ajax-select-index.ts") }}"></script>
{% endblock %}
{% block additional_css %}
<link rel="stylesheet" href="{{ static("webpack/core/components/ajax-select-index.css") }}">
<link rel="stylesheet" href="{{ static("core/components/ajax-select.scss") }}">
{% endblock %}
{% block content %}
<h3>{% trans %}New subscription{% endtrans %}</h3>
<div id="user_info"></div>
<form action="" method="post" id="subscription_form">
{% csrf_token %}
{{ form.non_field_errors() }}
<p>{{ form.member.errors }}<label for="{{ form.member.name }}">{{ form.member.label }}</label> {{ form.member }}</p>
<div id="new_member">
<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>
<div id="subscription-form" x-data="{existing_member: true}" hx-ext="response-targets">
<form
hx-post="{{ url("subscription:subscription-fragment-existing-user") }}"
hx-swap="innerHTML"
hx-target-2*="#user_infos"
hx-target-3*="#user_infos"
hx-disabled-elt="find input[type='submit']"
id="existing-user-subscription-form"
>
{% csrf_token %}
<div id="existing-user-subscription-form-fields">
{{ existing_user_form }}
</div>
<input type="submit" value="{% trans %}Save{% endtrans %}">
</form>
</div>
{# <template x-if="!$refs.member_search?.widget?.items?.length">#}
{# <div id="new-member" @click="console.log($refs.member_search.widget.items)">#}
{# <p>#}
{# {{ existing_user_form.first_name.errors }}#}
{# <label for="{{ existing_user_form.first_name.name }}">{{ existing_user_form.first_name.label }}</label>#}
{# {{ existing_user_form.first_name }}#}
{# </p>#}
{# <p>#}
{# {{ existing_user_form.last_name.errors }}#}
{# <label for="{{ existing_user_form.last_name.name }}">{{ existing_user_form.last_name.label }}</label>#}
{# {{ existing_user_form.last_name }}#}
{# </p>#}
{# <p>#}
{# {{ existing_user_form.email.errors }}#}
{# <label for="{{ existing_user_form.email.name }}">{{ existing_user_form.email.label }}</label>#}
{# {{ existing_user_form.email }}#}
{# </p>#}
{# <p>#}
{# {{ existing_user_form.date_of_birth.errors }}#}
{# <label for="{{ existing_user_form.date_of_birth.name }}">{{ existing_user_form.date_of_birth.label}}</label>#}
{# {{ existing_user_form.date_of_birth }}#}
{# </p>#}
{# </div>#}
{# </template>#}
{% 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 %}
{#{% block script %}#}
{# {{ super() }}#}
{##}
{#{% endblock %}#}

View File

@ -15,10 +15,19 @@
from django.urls import path
from subscription.views import NewSubscription, SubscriptionsStatsView
from subscription.views import (
CreateSubscriptionExistingUserFragment,
NewSubscription,
SubscriptionsStatsView,
)
urlpatterns = [
# Subscription views
path("", NewSubscription.as_view(), name="subscription"),
path(
"existing-user/",
CreateSubscriptionExistingUserFragment.as_view(),
name="subscription-fragment-existing-user",
),
path("stats/", SubscriptionsStatsView.as_view(), name="stats"),
]

View File

@ -13,85 +13,96 @@
#
#
import secrets
from django import forms
from django.conf import settings
from django.contrib.auth.mixins import UserPassesTestMixin
from django.core.exceptions import PermissionDenied
from django.http import HttpResponse
from django.urls import reverse_lazy
from django.views.generic.edit import CreateView, FormView
from django.utils.timezone import localdate
from django.views.generic import TemplateView
from django.views.generic.edit import FormView
from subscription.forms import SelectionDateForm, SubscriptionForm
from subscription.forms import (
SelectionDateForm,
SubscriptionForm,
SubscriptionNewUserForm,
)
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"
def get_context_data(self, **kwargs):
return super().get_context_data(**kwargs) | {
"existing_user_form": SubscriptionForm(),
"new_user_form": SubscriptionNewUserForm(),
}
class CreateSubscriptionExistingUserFragment(CanCreateSubscriptionMixin, FormView):
"""Create a subscription for a user who already exists."""
form_class = SubscriptionForm
success_url = reverse_lazy("subscription:subscription-fragment-existing-user")
def dispatch(self, request, *arg, **kwargs):
if request.user.can_create_subscription:
return super().dispatch(request, *arg, **kwargs)
raise PermissionDenied
def render_to_response(self, context, **response_kwargs):
print(context["form"])
print(str(context["form"]))
print(context["form"].render())
return HttpResponse(context["form"].render(), **response_kwargs)
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 dispatch(self, request, *args, **kwargs):
# s = SubscriptionForm()
# print(s)
# return super().dispatch(request, *args, **kwargs)
def form_valid(self, form):
form.instance.subscription_start = Subscription.compute_start(
duration=settings.SITH_SUBSCRIPTIONS[form.instance.subscription_type][
"duration"
],
user=form.instance.member,
)
form.instance.subscription_end = Subscription.compute_end(
duration=settings.SITH_SUBSCRIPTIONS[form.instance.subscription_type][
"duration"
],
start=form.instance.subscription_start,
user=form.instance.member,
)
return super().form_valid(form)
# def form_valid(self, form):
# duration = settings.SITH_SUBSCRIPTIONS[form.instance.subscription_type][
# "duration"
# ]
# form.instance.subscription_start = Subscription.compute_start(
# duration=duration, user=form.instance.member
# )
# form.instance.subscription_end = Subscription.compute_end(
# duration=duration,
# start=form.instance.subscription_start,
# user=form.instance.member,
# )
# return super().form_valid(form)
class SubscriptionsStatsView(FormView):
template_name = "subscription/stats.jinja"
form_class = SelectionDateForm
success_url = reverse_lazy("subscriptions:stats")
def dispatch(self, request, *arg, **kwargs):
import datetime
self.start_date = datetime.datetime.today()
self.start_date = localdate()
self.end_date = self.start_date
res = super().dispatch(request, *arg, **kwargs)
if request.user.is_root or request.user.is_board_member:
return res
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"]
res = super().post(request, *args, **kwargs)
if request.user.is_root or request.user.is_board_member:
return res
raise PermissionDenied
return super().post(request, *args, **kwargs)
def get_initial(self):
init = {
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"),
}
return init
def get_context_data(self, **kwargs):
from subscription.models import Subscription
kwargs = super().get_context_data(**kwargs)
kwargs["subscriptions_total"] = Subscription.objects.filter(
subscription_end__gte=self.end_date, subscription_start__lte=self.start_date
@ -100,6 +111,3 @@ class SubscriptionsStatsView(FormView):
kwargs["payment_types"] = settings.SITH_COUNTER_PAYMENT_METHOD
kwargs["locations"] = settings.SITH_SUBSCRIPTION_LOCATIONS
return kwargs
def get_success_url(self, **kwargs):
return reverse_lazy("subscriptions:stats")

View File

@ -85,6 +85,7 @@ export default {
inject({
// biome-ignore lint/style/useNamingConvention: that's how it's called
Alpine: "alpinejs",
htmx: "htmx.org"
}),
viteStaticCopy({
targets: [