mirror of
https://github.com/ae-utbm/sith.git
synced 2026-03-28 06:19:44 +00:00
Don't use JS for the club list page
This commit is contained in:
@@ -315,3 +315,22 @@ class JoinClubForm(ClubMemberForm):
|
|||||||
_("You are already a member of this club"), code="invalid"
|
_("You are already a member of this club"), code="invalid"
|
||||||
)
|
)
|
||||||
return super().clean()
|
return super().clean()
|
||||||
|
|
||||||
|
|
||||||
|
class ClubSearchForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = Club
|
||||||
|
fields = ["name"]
|
||||||
|
widgets = {"name": forms.SearchInput(attrs={"autocomplete": "off"})}
|
||||||
|
|
||||||
|
club_status = forms.NullBooleanField(
|
||||||
|
label=_("Club status"),
|
||||||
|
widget=forms.RadioSelect(
|
||||||
|
choices=[(True, _("Active")), (False, _("Inactive")), ("", _("All clubs"))],
|
||||||
|
),
|
||||||
|
initial=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.fields["name"].required = False
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
import { History, updateQueryString } from "#core:utils/history";
|
|
||||||
import {
|
|
||||||
type ClubProfileSchema,
|
|
||||||
type ClubSearchClubData,
|
|
||||||
clubSearchClubProfile,
|
|
||||||
type Options,
|
|
||||||
} from "#openapi";
|
|
||||||
|
|
||||||
type ClubStatus = "active" | "inactive" | "both";
|
|
||||||
const PAGE_SIZE = 50;
|
|
||||||
|
|
||||||
document.addEventListener("alpine:init", () => {
|
|
||||||
Alpine.data("clubList", () => ({
|
|
||||||
clubName: "",
|
|
||||||
clubStatus: "active" as ClubStatus,
|
|
||||||
currentPage: 1,
|
|
||||||
nbPages: 1,
|
|
||||||
clubs: [] as ClubProfileSchema[],
|
|
||||||
loading: false,
|
|
||||||
|
|
||||||
async init() {
|
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
|
||||||
this.clubName = urlParams.get("clubName") || "";
|
|
||||||
this.clubStatus = urlParams.get("clubStatus") || "active";
|
|
||||||
this.currentPage = urlParams.get("currentPage") || 1;
|
|
||||||
for (const param of ["clubName", "clubStatus", "currentPage"]) {
|
|
||||||
this.$watch(param, async (value: number | string) => {
|
|
||||||
updateQueryString(param, value.toString(), History.Replace);
|
|
||||||
await this.loadClubs();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
await this.loadClubs();
|
|
||||||
},
|
|
||||||
async loadClubs() {
|
|
||||||
this.loading = true;
|
|
||||||
const searchParams: Options<ClubSearchClubData> = {
|
|
||||||
query: { page: this.currentPage },
|
|
||||||
};
|
|
||||||
if (this.clubName) {
|
|
||||||
searchParams.query.search = this.clubName;
|
|
||||||
}
|
|
||||||
if (this.clubStatus === "active") {
|
|
||||||
searchParams.query.is_active = true;
|
|
||||||
} else if (this.clubStatus === "inactive") {
|
|
||||||
searchParams.query.is_active = false;
|
|
||||||
}
|
|
||||||
const res = await clubSearchClubProfile(searchParams);
|
|
||||||
this.nbPages = Math.ceil(res.data.count / PAGE_SIZE);
|
|
||||||
this.clubs = res.data.results;
|
|
||||||
this.loading = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
getParagraphs(s: string) {
|
|
||||||
if (!s) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
.split("\n")
|
|
||||||
.map((s) => s.trim())
|
|
||||||
.filter((s) => s !== "");
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{% extends "core/base.jinja" %}
|
{% extends "core/base.jinja" %}
|
||||||
{% from "core/macros.jinja" import paginate_alpine %}
|
{% from "core/macros.jinja" import paginate_jinja %}
|
||||||
|
|
||||||
{% block title -%}
|
{% block title -%}
|
||||||
{% trans %}Club list{% endtrans %}
|
{% trans %}Club list{% endtrans %}
|
||||||
@@ -9,44 +9,18 @@
|
|||||||
{% trans %}The list of all clubs existing at UTBM.{% endtrans %}
|
{% trans %}The list of all clubs existing at UTBM.{% endtrans %}
|
||||||
{%- endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{% block additional_js %}
|
|
||||||
<script type="module" src="{{ static("bundled/club/club-list-index.ts") }}"></script>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block additional_css %}
|
{% block additional_css %}
|
||||||
<link rel="stylesheet" href="{{ static("club/list.scss") }}">
|
<link rel="stylesheet" href="{{ static("club/list.scss") }}">
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<main x-data="clubList">
|
<main>
|
||||||
<h3>{% trans %}Filters{% endtrans %}</h3>
|
<h3>{% trans %}Filters{% endtrans %}</h3>
|
||||||
<form id="club-list-filters">
|
<form id="club-list-filters" method="GET">
|
||||||
<div class="row gap-4x">
|
<div class="row gap-4x">
|
||||||
<fieldset>
|
{{ form }}
|
||||||
<label for="club-name-input">{% trans %}Name{% endtrans %}</label>
|
|
||||||
<input
|
|
||||||
id="club-name-input"
|
|
||||||
type="text"
|
|
||||||
name="club-name"
|
|
||||||
x-model.debounce.500ms="clubName"
|
|
||||||
/>
|
|
||||||
</fieldset>
|
|
||||||
<fieldset class="grow">
|
|
||||||
<legend>{% trans %}Club state{% endtrans %}</legend>
|
|
||||||
<div class="row">
|
|
||||||
<input type="radio" id="filter-active-clubs" x-model="clubStatus" value="active">
|
|
||||||
<label for="filter-active-clubs">{% trans %}Active{% endtrans %}</label>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<input type="radio" id="filter-inactive-clubs" x-model="clubStatus" value="inactive">
|
|
||||||
<label for="filter-inactive-clubs">{% trans %}Inactive{% endtrans %}</label>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<input type="radio" id="filter-all-clubs" x-model="clubStatus" value="both">
|
|
||||||
<label for="filter-all-clubs">{% trans %}All clubs{% endtrans %}</label>
|
|
||||||
</div>
|
|
||||||
</fieldset>
|
|
||||||
</div>
|
</div>
|
||||||
|
<input type="submit" class="btn btn-blue margin-bottom" value="{% trans %}Search{% endtrans %}">
|
||||||
</form>
|
</form>
|
||||||
<h3>{% trans %}Club list{% endtrans %}</h3>
|
<h3>{% trans %}Club list{% endtrans %}</h3>
|
||||||
{% if user.has_perm("club.add_club") %}
|
{% if user.has_perm("club.add_club") %}
|
||||||
@@ -55,29 +29,29 @@
|
|||||||
<i class="fa fa-plus"></i> {% trans %}New club{% endtrans %}
|
<i class="fa fa-plus"></i> {% trans %}New club{% endtrans %}
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<section class="aria-busy-grow" :aria-busy="loading" id="club-list">
|
<section class="aria-busy-grow" id="club-list">
|
||||||
<template x-for="club of clubs" :key="club.id">
|
{% for club in object_list %}
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<a :href="club.url">
|
{% set club_url = club.get_absolute_url() %}
|
||||||
<template x-if="club.logo">
|
<a href="{{ club_url }}">
|
||||||
<img class="club-image" :src="club.logo" :alt="`logo ${club.name}`">
|
{% if club.logo %}
|
||||||
</template>
|
<img class="club-image" src="{{ club.logo.url }}" alt="logo {{ club.name }}">
|
||||||
<template x-if="!club.logo">
|
{% else %}
|
||||||
<i class="fa-regular fa-image fa-4x club-image"></i>
|
<i class="fa-regular fa-image fa-4x club-image"></i>
|
||||||
</template>
|
{% endif %}
|
||||||
</a>
|
</a>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<a :href="club.url">
|
<a href="{{ club_url }}">
|
||||||
<h4 x-text="`${club.name} ${club.is_active ? '' : '({% trans %}inactive{% endtrans %})'}`"></h4>
|
<h4>
|
||||||
|
{{ club.name }} {% if not club.is_active %}({% trans %}inactive{% endtrans %}){% endif %}
|
||||||
|
</h4>
|
||||||
</a>
|
</a>
|
||||||
<template x-for="paragraph of getParagraphs(club.short_description)">
|
{{ club.short_description|markdown }}
|
||||||
<p x-text="paragraph"></p>
|
|
||||||
</template>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
{% endfor %}
|
||||||
</section>
|
</section>
|
||||||
{{ paginate_alpine("currentPage", "nbPages") }}
|
{{ paginate_jinja(page_obj, paginator) }}
|
||||||
</main>
|
</main>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|||||||
@@ -42,15 +42,21 @@ from django.utils.functional import cached_property
|
|||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from django.utils.translation import gettext
|
from django.utils.translation import gettext
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.views.generic import DetailView, TemplateView, View
|
from django.views.generic import DetailView, ListView, View
|
||||||
from django.views.generic.detail import SingleObjectMixin
|
from django.views.generic.detail import SingleObjectMixin
|
||||||
from django.views.generic.edit import CreateView, DeleteView, UpdateView
|
from django.views.generic.edit import (
|
||||||
|
CreateView,
|
||||||
|
DeleteView,
|
||||||
|
FormMixin,
|
||||||
|
UpdateView,
|
||||||
|
)
|
||||||
|
|
||||||
from club.forms import (
|
from club.forms import (
|
||||||
ClubAddMemberForm,
|
ClubAddMemberForm,
|
||||||
ClubAdminEditForm,
|
ClubAdminEditForm,
|
||||||
ClubEditForm,
|
ClubEditForm,
|
||||||
ClubOldMemberForm,
|
ClubOldMemberForm,
|
||||||
|
ClubSearchForm,
|
||||||
JoinClubForm,
|
JoinClubForm,
|
||||||
MailingForm,
|
MailingForm,
|
||||||
SellingsForm,
|
SellingsForm,
|
||||||
@@ -180,10 +186,41 @@ class ClubTabsMixin(TabedViewMixin):
|
|||||||
return tab_list
|
return tab_list
|
||||||
|
|
||||||
|
|
||||||
class ClubListView(TemplateView):
|
class ClubListView(FormMixin, ListView):
|
||||||
"""List the Clubs."""
|
"""List the clubs of the AE, with a form to perform basic search.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
This view is fully public, because we want to advertise as much as possible
|
||||||
|
the cultural life of the AE.
|
||||||
|
In accordance with that matter, searching and listing the clubs is done
|
||||||
|
entirely server-side (no AlpineJS involved) ;
|
||||||
|
this is done this way in order to be sure the page is the most accessible
|
||||||
|
and SEO-friendly possible, even if it makes the UX slightly less smooth.
|
||||||
|
"""
|
||||||
|
|
||||||
template_name = "club/club_list.jinja"
|
template_name = "club/club_list.jinja"
|
||||||
|
form_class = ClubSearchForm
|
||||||
|
queryset = Club.objects.order_by("name")
|
||||||
|
paginate_by = 20
|
||||||
|
|
||||||
|
def get_form_kwargs(self):
|
||||||
|
res = super().get_form_kwargs()
|
||||||
|
if self.request.GET:
|
||||||
|
res |= {"data": self.request.GET, "initial": self.request.GET}
|
||||||
|
return res
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
form: ClubSearchForm = self.get_form()
|
||||||
|
qs = self.queryset
|
||||||
|
if not form.is_bound:
|
||||||
|
return qs
|
||||||
|
if not form.is_valid():
|
||||||
|
return qs.none()
|
||||||
|
if name := form.cleaned_data.get("name"):
|
||||||
|
qs = qs.filter(name__icontains=name)
|
||||||
|
if (is_active := form.cleaned_data.get("club_status")) is not None:
|
||||||
|
qs = qs.filter(is_active=is_active)
|
||||||
|
return qs
|
||||||
|
|
||||||
|
|
||||||
class ClubView(ClubTabsMixin, DetailView):
|
class ClubView(ClubTabsMixin, DetailView):
|
||||||
|
|||||||
Reference in New Issue
Block a user