diff --git a/club/forms.py b/club/forms.py index 76687103..5bc00e7e 100644 --- a/club/forms.py +++ b/club/forms.py @@ -28,7 +28,14 @@ from django.db.models.functions import Lower from django.utils.functional import cached_property from django.utils.translation import gettext_lazy as _ -from club.models import Club, ClubRole, Mailing, MailingSubscription, Membership +from club.models import ( + Club, + ClubLink, + ClubRole, + Mailing, + MailingSubscription, + Membership, +) from core.models import User from core.views.forms import SelectDateTime from core.views.widgets.ajax_select import ( @@ -39,6 +46,26 @@ from counter.models import Counter, Selling from counter.schemas import SaleFilterSchema +class ClubLinkForm(forms.ModelForm): + error_css_class = "error" + required_css_class = "required" + + class Meta: + model = ClubLink + fields = ["url", "name", "link_type"] + widgets = { + "url": forms.URLInput( + {"pattern": "https://.*", "placeholder": "https://monlien.com"} + ), + "link_type": forms.HiddenInput(), + } + + +ClubLinkFormSet = forms.inlineformset_factory( + Club, ClubLink, ClubLinkForm, extra=0, can_delete_extra=False +) + + class ClubEditForm(forms.ModelForm): error_css_class = "error" required_css_class = "required" @@ -48,6 +75,20 @@ class ClubEditForm(forms.ModelForm): fields = ["address", "logo", "short_description"] widgets = {"short_description": forms.Textarea()} + def __init__(self, *args, prefix: str | None = None, instance=None, **kwargs): + super().__init__(*args, prefix=prefix, instance=instance, **kwargs) + self.link_formset = ClubLinkFormSet( + *args, instance=self.instance, prefix="link", **kwargs + ) + + def is_valid(self): + return super().is_valid() and self.link_formset.is_valid() + + def save(self, commit=True): # noqa: FBT002 + res = super().save(commit=commit) + self.link_formset.save(commit=commit) + return res + class ClubAdminEditForm(ClubEditForm): admin_fields = ["name", "parent", "is_active"] diff --git a/club/templates/club/edit_club.jinja b/club/templates/club/edit_club.jinja index d70e2140..d1d6aa2c 100644 --- a/club/templates/club/edit_club.jinja +++ b/club/templates/club/edit_club.jinja @@ -1,9 +1,60 @@ {% extends "core/base.jinja" %} +{% block additional_js %} + +{% endblock %} + {% block title %} {% trans name=object %}Edit {{ name }}{% endtrans %} {% endblock %} +{% macro link_form(form) %} +
+ {{ form.non_field_errors() }} +
+
+ {{ form.url.label_tag() }} + {{ form.url.errors }} + + {# we change the icon when the user change it and leave the input, + or when it is pasted from the clipboard #} + {{ form.url|add_attr("x-model.change=url,@paste.prevent=url = $event.clipboardData.getData('text')") }} + + +
+
{{ form.name.as_field_group() }}
+
+ {%- if form.DELETE -%} +
+ {{ form.DELETE.as_field_group() }} +
+ {%- else -%} +
+ + {%- endif -%} + {{ form.link_type|add_attr(":value=linkType.id") }} + {%- for field in form.hidden_fields() -%} + {%- if field != form.link_type -%} + {{ field }} + {%- endif -%} + {%- endfor -%} +
+{% endmacro %} + + {% block content %}

{% trans name=object %}Edit {{ name }}{% endtrans %}

@@ -17,7 +68,7 @@ and explicitly separate them from the non-admin ones, with some help text. Non-admin users will only see the regular form fields, - so they don't need thoses explanations #} + so they don't need those explanations #}

{% trans %}Club properties{% endtrans %}

{% trans trimmed %} @@ -25,7 +76,7 @@ Only admin users can see and edit them. {% endtrans %}

-
+
{% for field_name in form.admin_fields %} {% set field = form[field_name] %}
@@ -36,11 +87,13 @@ {# Remove the the admin fields from the form. The remaining non-admin fields will be rendered at once with a simple {{ form.as_p() }} #} - {% set _ = form.fields.pop(field_name) %} + {% do form.fields.pop(field_name) %} {% endfor %}
+ {% endif %} -

{% trans %}Club informations{% endtrans %}

+

{% trans %}Club informations{% endtrans %}

+ {% if form.admin_fields %}

{% trans trimmed %} The following form fields are linked to the basic description of a club. @@ -48,7 +101,45 @@ {% endtrans %}

{% endif %} - {{ form.as_p() }} -

+
+ {{ form.as_p() }} +
+ +

{% trans %}Club links{% endtrans %}

+
+ {{ form.link_formset.management_form }} +
+ {%- for f in form.link_formset.forms -%} + {{ link_form(f) }} + {%- endfor -%} +
+ +

+ {% trans trimmed %} + Note: if the icon of one of your links doesn't exist yet, + you can ask the info team to add it. + {% endtrans %} +

+
+ +
+
+ {% endblock content %} + +{% block script %} + +{% endblock %} diff --git a/club/tests/test_edit.py b/club/tests/test_edit.py index b4d9e2b1..c8a03b61 100644 --- a/club/tests/test_edit.py +++ b/club/tests/test_edit.py @@ -21,7 +21,13 @@ def test_club_board_member_cannot_edit_club_properties(client: Client): client.force_login(user) res = client.post( reverse("club:club_edit", kwargs={"club_id": club.id}), - {"name": "new name", "is_active": False, "address": "new address"}, + { + "name": "new name", + "is_active": False, + "address": "new address", + "link-TOTAL_FORMS": 0, + "link-INITIAL_FORMS": 0, + }, ) # The request should success, # but admin-only fields shouldn't be taken into account diff --git a/club/views.py b/club/views.py index cce60e8c..2e8f831f 100644 --- a/club/views.py +++ b/club/views.py @@ -33,6 +33,7 @@ from django.contrib.messages.views import SuccessMessageMixin from django.core.exceptions import NON_FIELD_ERRORS, PermissionDenied, ValidationError from django.core.paginator import InvalidPage, Paginator from django.db.models import F, Q, Sum +from django.db.models.functions import Length from django.http import Http404, StreamingHttpResponse from django.shortcuts import get_object_or_404, redirect from django.urls import reverse, reverse_lazy @@ -60,7 +61,7 @@ from club.forms import ( MailingForm, SellingsForm, ) -from club.models import Club, Mailing, MailingSubscription, Membership +from club.models import Club, LinkType, Mailing, MailingSubscription, Membership from com.models import Poster from com.views import ( PosterCreateBaseView, @@ -570,6 +571,11 @@ class ClubEditView(ClubTabsMixin, CanEditMixin, UpdateView): return ClubAdminEditForm return ClubEditForm + def get_context_data(self, **kwargs): + return super().get_context_data(**kwargs) | { + "link_types": list(LinkType.objects.order_by(Length("url_base").desc())) + } + class ClubCreateView(PermissionRequiredMixin, CreateView): """Create a club (for the Sith admin).""" diff --git a/core/templatetags/renderer.py b/core/templatetags/renderer.py index 927acf54..eef8bb3c 100644 --- a/core/templatetags/renderer.py +++ b/core/templatetags/renderer.py @@ -103,7 +103,7 @@ def add_attr(field: BoundField, attr: str): if "=" not in d: attrs["class"] = d else: - key, val = d.split("=") + key, val = d.split("=", maxsplit=1) attrs[key] = val return field.as_widget(attrs=attrs)