2 Commits

15 changed files with 76 additions and 231 deletions

View File

@@ -6,10 +6,9 @@ from ninja_extra.pagination import PageNumberPaginationExtra
from ninja_extra.schemas import PaginatedResponseSchema
from api.auth import ApiKeyAuth
from api.permissions import CanView, HasPerm
from api.permissions import CanAccessLookup, CanView, HasPerm
from club.models import Club, Membership
from club.schemas import (
ClubProfileSchema,
ClubSchema,
ClubSearchFilterSchema,
SimpleClubSchema,
@@ -23,21 +22,13 @@ class ClubController(ControllerBase):
@route.get(
"/search",
response=PaginatedResponseSchema[SimpleClubSchema],
auth=[ApiKeyAuth(), SessionAuth()],
permissions=[CanAccessLookup],
url_name="search_club",
)
@paginate(PageNumberPaginationExtra, page_size=50)
def search_club(self, filters: Query[ClubSearchFilterSchema]):
return filters.filter(Club.objects.order_by("name")).values()
@route.get(
"/search-profile",
response=PaginatedResponseSchema[ClubProfileSchema],
url_name="search_club_profile",
)
@paginate(PageNumberPaginationExtra, page_size=50)
def search_club_profile(self, filters: Query[ClubSearchFilterSchema]):
"""Same as /api/club/search, but with more returned data"""
return filters.filter(Club.objects.order_by("name"))
return filters.filter(Club.objects.all())
@route.get(
"/{int:club_id}",

View File

@@ -315,22 +315,3 @@ class JoinClubForm(ClubMemberForm):
_("You are already a member of this club"), code="invalid"
)
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

View File

@@ -30,7 +30,7 @@ class ClubProfileSchema(ModelSchema):
class Meta:
model = Club
fields = ["id", "name", "logo", "is_active", "short_description"]
fields = ["id", "name", "logo"]
url: str

View File

@@ -1,47 +0,0 @@
#club-list {
display: flex;
flex-direction: column;
gap: 2em;
padding: 2em;
.card {
display: block;
background-color: unset;
.club-image {
float: left;
margin-right: 2rem;
margin-bottom: .5rem;
width: 150px;
height: 150px;
border-radius: 10%;
background-color: rgba(173, 173, 173, 0.2);
@media screen and (max-width: 500px) {
width: 100px;
height: 100px;
}
}
i.club-image {
display: flex;
flex-direction: column;
justify-content: center;
color: black;
}
.content {
display: block;
text-align: justify;
h4 {
margin-top: 0;
margin-right: .5rem;
}
p {
font-size: 100%;
}
}
}
}

View File

@@ -1,5 +1,4 @@
{% extends "core/base.jinja" %}
{% from "core/macros.jinja" import paginate_jinja with context %}
{% block title -%}
{% trans %}Club list{% endtrans %}
@@ -9,50 +8,45 @@
{% trans %}The list of all clubs existing at UTBM.{% endtrans %}
{%- endblock %}
{% block additional_css %}
<link rel="stylesheet" href="{{ static("club/list.scss") }}">
{% endblock %}
{% macro display_club(club) -%}
{% if club.is_active or user.is_root %}
<li><a href="{{ url('club:club_view', club_id=club.id) }}">{{ club.name }}</a>
{% if not club.is_active %}
({% trans %}inactive{% endtrans %})
{% endif %}
{% if club.president %} - <a href="{{ url('core:user_profile', user_id=club.president.user.id) }}">{{ club.president.user }}</a>{% endif %}
{% if club.short_description %}<p>{{ club.short_description|markdown }}</p>{% endif %}
{% endif %}
{%- if club.children.all()|length != 0 %}
<ul>
{%- for c in club.children.order_by('name').prefetch_related("children") %}
{{ display_club(c) }}
{%- endfor %}
</ul>
{%- endif -%}
</li>
{%- endmacro %}
{% block content %}
<main>
<h3>{% trans %}Filters{% endtrans %}</h3>
<form id="club-list-filters" method="GET">
<div class="row gap-4x">
{{ form }}
</div>
<input type="submit" class="btn btn-blue margin-bottom" value="{% trans %}Search{% endtrans %}">
</form>
{% if user.is_root %}
<p><a href="{{ url('club:club_new') }}">{% trans %}New club{% endtrans %}</a></p>
{% endif %}
{% if club_list %}
<h3>{% trans %}Club list{% endtrans %}</h3>
{% if user.has_perm("club.add_club") %}
<br>
<a href="{{ url('club:club_new') }}" class="btn btn-blue">
<i class="fa fa-plus"></i> {% trans %}New club{% endtrans %}
</a>
{% endif %}
<section class="aria-busy-grow" id="club-list">
{% for club in object_list %}
<div class="card">
{% set club_url = club.get_absolute_url() %}
<a href="{{ club_url }}">
{% if club.logo %}
<img class="club-image" src="{{ club.logo.url }}" alt="logo {{ club.name }}">
<ul>
{%- for club in club_list %}
{{ display_club(club) }}
{%- endfor %}
</ul>
{% else %}
<i class="fa-regular fa-image fa-4x club-image"></i>
{% trans %}There is no club in this website.{% endtrans %}
{% endif %}
</a>
<div class="content">
<a href="{{ club_url }}">
<h4>
{{ club.name }} {% if not club.is_active %}({% trans %}inactive{% endtrans %}){% endif %}
</h4>
</a>
{{ club.short_description|markdown }}
</div>
</div>
{% endfor %}
</section>
{{ paginate_jinja(page_obj, paginator) }}
</main>
{% endblock %}

View File

@@ -1,15 +1,12 @@
from datetime import timedelta
import pytest
from django.test import Client
from django.urls import reverse
from django.utils.timezone import localdate
from model_bakery import baker
from model_bakery.recipe import Recipe
from club.models import Club, Membership
from core.baker_recipes import subscriber_user
from core.models import User
@pytest.mark.django_db
@@ -28,10 +25,3 @@ def test_club_queryset_having_board_member():
club_ids = Club.objects.having_board_member(user).values_list("id", flat=True)
assert set(club_ids) == {clubs[1].id, clubs[2].id}
@pytest.mark.django_db
def test_club_list(client: Client):
client.force_login(baker.make(User))
res = client.get(reverse("club:club_list"))
assert res.status_code == 200

View File

@@ -59,14 +59,6 @@ class TestClubSearch(TestCase):
ids = {d["id"] for d in response.json()["results"]}
assert ids == {c.id for c in [self.clubs[0], self.clubs[1], self.clubs[3]]}
def test_club_search_profile(self):
self.client.force_login(self.user)
url = reverse("api:search_club_profile")
response = self.client.get(url, {"search": "AE"})
assert response.status_code == 200
ids = {d["id"] for d in response.json()["results"]}
assert ids == {c.id for c in [self.clubs[0], self.clubs[1], self.clubs[3]]}
@pytest.mark.django_db
class TestFetchClub:

View File

@@ -44,19 +44,13 @@ from django.utils.translation import gettext
from django.utils.translation import gettext_lazy as _
from django.views.generic import DetailView, ListView, View
from django.views.generic.detail import SingleObjectMixin
from django.views.generic.edit import (
CreateView,
DeleteView,
FormMixin,
UpdateView,
)
from django.views.generic.edit import CreateView, DeleteView, UpdateView
from club.forms import (
ClubAddMemberForm,
ClubAdminEditForm,
ClubEditForm,
ClubOldMemberForm,
ClubSearchForm,
JoinClubForm,
MailingForm,
SellingsForm,
@@ -186,41 +180,15 @@ class ClubTabsMixin(TabedViewMixin):
return tab_list
class ClubListView(FormMixin, ListView):
"""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.
"""
class ClubListView(ListView):
"""List the Clubs."""
model = Club
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
queryset = (
Club.objects.filter(parent=None).order_by("name").prefetch_related("children")
)
context_object_name = "club_list"
class ClubView(ClubTabsMixin, DetailView):

View File

@@ -5,8 +5,9 @@
<details name="navbar" class="menu">
<summary class="head">{% trans %}Associations & Clubs{% endtrans %}</summary>
<ul class="content">
<li><a href="{{ url("core:page", page_name="ae") }}">{% trans %}AE{% endtrans %}</a></li>
<li><a href="{{ url("club:club_list") }}">{% trans %}AE's clubs{% endtrans %}</a></li>
<li><a href="{{ url('core:page', page_name='ae') }}">{% trans %}AE{% endtrans %}</a></li>
<li><a href="{{ url('core:page', page_name='clubs') }}">{% trans %}AE's clubs{% endtrans %}</a></li>
<li><a href="{{ url('core:page', page_name='utbm-associations') }}">{% trans %}Others UTBM's Associations{% endtrans %}</a></li>
</ul>
</details>
<details name="navbar" class="menu">

View File

@@ -124,15 +124,12 @@
This must be coupled with a view that handles pagination
with the Django Paginator object.
Warning:
You must include this macro `with context` as it uses the `querystring` macro
Parameters:
current_page (django.core.paginator.Page): the current page object
paginator (django.core.paginator.Paginator): the paginator object
Warnings:
This macro must be imported with context :
```jinja
{% from "core/macros.jinja" import paginate_jinja with context %}
```
#}
{{ paginate_server_side(current_page, paginator, False) }}
{% endmacro %}
@@ -145,15 +142,12 @@
The replaced fragment will be #content so make sure you are calling this macro inside your content block.
Warning:
You must include this macro `with context` as it uses the `querystring` macro
Parameters:
current_page (django.core.paginator.Page): the current page object
paginator (django.core.paginator.Paginator): the paginator object
Warnings:
This macro must be imported with context :
```jinja
{% from "core/macros.jinja" import paginate_htmx with context %}
```
#}
{{ paginate_server_side(current_page, paginator, True) }}
{% endmacro %}
@@ -260,14 +254,5 @@
{% macro querystring() %}
{%- for key, values in request.GET.lists() -%}
{%- if key not in kwargs -%}
{%- for value in values -%}
{{ key }}={{ value }}&amp;
{%- endfor -%}
{%- endif -%}
{%- endfor -%}
{%- for key, value in kwargs.items() -%}
{{ key }}={{ value }}&amp;
{%- endfor -%}
{{- urlencode(dict(dict(request.GET.lists()) | items | list + kwargs | items | list), doseq=True) -}}
{% endmacro %}

View File

@@ -1,5 +1,6 @@
{% extends "core/base.jinja" %}
{% from 'core/macros.jinja' import user_profile_link, paginate_jinja with context %}
{% from 'core/macros.jinja' import user_profile_link %}
{% from 'core/macros.jinja' import paginate_jinja with context %}
{% block title %}
{% trans %}Cash register summary list{% endtrans %}

View File

@@ -44,7 +44,9 @@
<p><a class="ib button" href="{{ url('forum:new_message', topic_id=topic.id) }}">{% trans %}Reply{% endtrans %}</a></p>
{% if is_paginated %}
{{ paginate_jinja(msgs, msgs.paginator) }}
{% endif %}
</div>
{% endblock %}

View File

@@ -310,36 +310,16 @@ msgid "The list of all clubs existing at UTBM."
msgstr "La liste de tous les clubs existants à l'UTBM"
#: club/templates/club/club_list.jinja
msgid "Filters"
msgstr "Filtres"
#: club/templates/club/club_list.jinja
msgid "Name"
msgstr "Nom"
#: club/templates/club/club_list.jinja
msgid "Club state"
msgstr "Etat du club"
#: club/templates/club/club_list.jinja
msgid "Active"
msgstr "Actif"
#: club/templates/club/club_list.jinja
msgid "Inactive"
msgstr "Inactif"
#: club/templates/club/club_list.jinja
msgid "All clubs"
msgstr "Tous les clubs"
msgid "inactive"
msgstr "inactif"
#: club/templates/club/club_list.jinja core/templates/core/user_tools.jinja
msgid "New club"
msgstr "Nouveau club"
#: club/templates/club/club_list.jinja
msgid "inactive"
msgstr "inactif"
msgid "There is no club in this website."
msgstr "Il n'y a pas de club dans ce site web."
#: club/templates/club/club_members.jinja
msgid "Club members"
@@ -1901,6 +1881,10 @@ msgstr "L'AE"
msgid "AE's clubs"
msgstr "Les clubs de L'AE"
#: core/templates/core/base/navbar.jinja
msgid "Others UTBM's Associations"
msgstr "Les autres associations de l'UTBM"
#: core/templates/core/base/navbar.jinja
msgid "Big event"
msgstr "Grandes Activités"

View File

@@ -28,5 +28,7 @@
</table>
<br>
{% if is_paginated %}
{{ paginate_jinja(page_obj, paginator) }}
{% endif %}
{% endblock content %}

View File

@@ -190,6 +190,7 @@ TEMPLATES = [
"get_sith": "com.views.sith",
"get_language": "django.utils.translation.get_language",
"timedelta": "datetime.timedelta",
"urlencode": "urllib.parse.urlencode",
},
"bytecode_cache": {
"name": "default",