mirror of
https://github.com/ae-utbm/sith.git
synced 2026-03-29 14:59:45 +00:00
Compare commits
9 Commits
fix-link-s
...
club-list
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b5d8db0187 | ||
|
|
26178f10c5 | ||
|
|
842ae5615d | ||
|
|
5cd748e313 | ||
|
|
2944804074 | ||
| fcce34fde5 | |||
|
a213bc058d
|
|||
|
71bfdf68b7
|
|||
|
2a9bb46e97
|
@@ -6,7 +6,7 @@ from ninja_extra.pagination import PageNumberPaginationExtra
|
||||
from ninja_extra.schemas import PaginatedResponseSchema
|
||||
|
||||
from api.auth import ApiKeyAuth
|
||||
from api.permissions import CanAccessLookup, CanView, HasPerm
|
||||
from api.permissions import CanView, HasPerm
|
||||
from club.models import Club, Membership
|
||||
from club.schemas import (
|
||||
ClubSchema,
|
||||
@@ -22,13 +22,11 @@ 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.all())
|
||||
return filters.filter(Club.objects.order_by("name")).values()
|
||||
|
||||
@route.get(
|
||||
"/{int:club_id}",
|
||||
|
||||
@@ -315,3 +315,22 @@ 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
|
||||
|
||||
@@ -30,7 +30,7 @@ class ClubProfileSchema(ModelSchema):
|
||||
|
||||
class Meta:
|
||||
model = Club
|
||||
fields = ["id", "name", "logo"]
|
||||
fields = ["id", "name", "logo", "is_active", "short_description"]
|
||||
|
||||
url: str
|
||||
|
||||
|
||||
47
club/static/club/list.scss
Normal file
47
club/static/club/list.scss
Normal file
@@ -0,0 +1,47 @@
|
||||
#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%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,52 +1,75 @@
|
||||
{% if is_fragment %}
|
||||
{% extends "core/base_fragment.jinja" %}
|
||||
|
||||
{# Don't display tabs and errors #}
|
||||
{% block tabs %}
|
||||
{% endblock %}
|
||||
{% block errors %}
|
||||
{% endblock %}
|
||||
{% else %}
|
||||
{% extends "core/base.jinja" %}
|
||||
|
||||
{% block title -%}
|
||||
{% trans %}Club list{% endtrans %}
|
||||
{%- endblock %}
|
||||
|
||||
{% block additional_css %}
|
||||
<link rel="stylesheet" href="{{ static("club/list.scss") }}">
|
||||
{% endblock %}
|
||||
{% block description -%}
|
||||
{% trans %}The list of all clubs existing at UTBM.{% endtrans %}
|
||||
{%- 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 %})
|
||||
{% block title -%}
|
||||
{% trans %}Club list{% endtrans %}
|
||||
{%- endblock %}
|
||||
{% 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 %}
|
||||
{% from "core/macros.jinja" import paginate_htmx %}
|
||||
|
||||
{% block content %}
|
||||
{% if user.is_root %}
|
||||
<p><a href="{{ url('club:club_new') }}">{% trans %}New club{% endtrans %}</a></p>
|
||||
{% endif %}
|
||||
{% if club_list %}
|
||||
<main>
|
||||
<h3>{% trans %}Filters{% endtrans %}</h3>
|
||||
<form
|
||||
id="club-list-filters"
|
||||
hx-get="{{ url("club:club_list") }}"
|
||||
hx-target="#content"
|
||||
hx-swap="outerHtml"
|
||||
>
|
||||
<div class="row gap-4x">
|
||||
{{ form }}
|
||||
</div>
|
||||
<button type="submit" class="btn btn-blue margin-bottom">
|
||||
<i class="fa fa-magnifying-glass"></i>{% trans %}Search{% endtrans %}
|
||||
</button>
|
||||
</form>
|
||||
<h3>{% trans %}Club list{% endtrans %}</h3>
|
||||
<ul>
|
||||
{%- for club in club_list %}
|
||||
{{ display_club(club) }}
|
||||
{%- endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
{% trans %}There is no club in this website.{% endtrans %}
|
||||
{% 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 }}">
|
||||
{% else %}
|
||||
<i class="fa-regular fa-image fa-4x club-image"></i>
|
||||
{% 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>
|
||||
{% if is_paginated %}
|
||||
{{ paginate_htmx(request, page_obj, paginator) }}
|
||||
{% endif %}
|
||||
</main>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
{% extends "core/base.jinja" %}
|
||||
{% from 'core/macros.jinja' import user_profile_link, select_all_checkbox %}
|
||||
|
||||
{% block additional_js %}
|
||||
<script type="module" 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("club/members.scss") }}">
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
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
|
||||
@@ -25,3 +28,14 @@ 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.parametrize("nb_additional_clubs", [10, 30])
|
||||
@pytest.mark.parametrize("is_fragment", [True, False])
|
||||
@pytest.mark.django_db
|
||||
def test_club_list(client: Client, nb_additional_clubs: int, is_fragment):
|
||||
client.force_login(baker.make(User))
|
||||
baker.make(Club, _quantity=nb_additional_clubs)
|
||||
headers = {"HX-Request": True} if is_fragment else {}
|
||||
res = client.get(reverse("club:club_list"), headers=headers)
|
||||
assert res.status_code == 200
|
||||
|
||||
@@ -44,13 +44,19 @@ 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, UpdateView
|
||||
from django.views.generic.edit import (
|
||||
CreateView,
|
||||
DeleteView,
|
||||
FormMixin,
|
||||
UpdateView,
|
||||
)
|
||||
|
||||
from club.forms import (
|
||||
ClubAddMemberForm,
|
||||
ClubAdminEditForm,
|
||||
ClubEditForm,
|
||||
ClubOldMemberForm,
|
||||
ClubSearchForm,
|
||||
JoinClubForm,
|
||||
MailingForm,
|
||||
SellingsForm,
|
||||
@@ -66,7 +72,12 @@ from com.views import (
|
||||
from core.auth.mixins import CanEditMixin, PermissionOrClubBoardRequiredMixin
|
||||
from core.models import Page, PageRev
|
||||
from core.views import BasePageEditView, DetailFormView, UseFragmentsMixin
|
||||
from core.views.mixins import FragmentMixin, FragmentRenderer, TabedViewMixin
|
||||
from core.views.mixins import (
|
||||
AllowFragment,
|
||||
FragmentMixin,
|
||||
FragmentRenderer,
|
||||
TabedViewMixin,
|
||||
)
|
||||
from counter.models import Selling
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -180,15 +191,41 @@ class ClubTabsMixin(TabedViewMixin):
|
||||
return tab_list
|
||||
|
||||
|
||||
class ClubListView(ListView):
|
||||
"""List the Clubs."""
|
||||
class ClubListView(AllowFragment, 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.
|
||||
"""
|
||||
|
||||
model = Club
|
||||
template_name = "club/club_list.jinja"
|
||||
queryset = (
|
||||
Club.objects.filter(parent=None).order_by("name").prefetch_related("children")
|
||||
)
|
||||
context_object_name = "club_list"
|
||||
form_class = ClubSearchForm
|
||||
queryset = Club.objects.order_by("name")
|
||||
paginate_by = 1
|
||||
|
||||
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.filter(is_active=True)
|
||||
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):
|
||||
|
||||
@@ -1,106 +1,18 @@
|
||||
import { inheritHtmlElement, registerComponent } from "#core:utils/web-components.ts";
|
||||
|
||||
/**
|
||||
* Create an abstract class for ElementOnce types Web Components
|
||||
*
|
||||
* Those class aren't really abstract because that would be complicated with the
|
||||
* multiple inheritance involved
|
||||
* Instead, we just raise an unimplemented error
|
||||
**/
|
||||
function elementOnce<K extends keyof HTMLElementTagNameMap>(tagName: K) {
|
||||
return class ElementOnce extends inheritHtmlElement(tagName) {
|
||||
getElementQuerySelector(): string {
|
||||
throw new Error("Unimplemented");
|
||||
}
|
||||
|
||||
clearNode() {
|
||||
while (this.firstChild) {
|
||||
this.removeChild(this.lastChild);
|
||||
}
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.clearNode();
|
||||
if (document.querySelectorAll(this.getElementQuerySelector()).length === 0) {
|
||||
this.appendChild(this.node);
|
||||
}
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback(false);
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
// The MutationObserver can't see web components being removed
|
||||
// It also can't see if something is removed inside after the component gets deleted
|
||||
// We need to manually clear the containing node to trigger the observer
|
||||
this.clearNode();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Set of ElementOnce type components to refresh with the observer
|
||||
const registeredComponents: Set<string> = new Set();
|
||||
|
||||
/**
|
||||
* Helper to register ElementOnce types Web Components
|
||||
* It's a wrapper around registerComponent that registers that component on
|
||||
* a MutationObserver that activates a refresh on them when elements are removed
|
||||
**/
|
||||
function registerElementOnce(name: string, options?: ElementDefinitionOptions) {
|
||||
registeredComponents.add(name);
|
||||
return registerComponent(name, options);
|
||||
}
|
||||
|
||||
const startObserver = (observer: MutationObserver) => {
|
||||
observer.observe(document, {
|
||||
// We want to also listen for elements contained in the header (eg: link)
|
||||
subtree: true,
|
||||
childList: true,
|
||||
});
|
||||
};
|
||||
|
||||
// Refresh *-once components when changes happens
|
||||
const observer = new MutationObserver((mutations: MutationRecord[]) => {
|
||||
observer.disconnect();
|
||||
for (const mutation of mutations) {
|
||||
for (const node of mutation.removedNodes) {
|
||||
if (node.nodeType !== node.ELEMENT_NODE) {
|
||||
continue;
|
||||
}
|
||||
const refreshElement = (componentName: string, tagName: string) => {
|
||||
for (const element of document.getElementsByTagName(componentName)) {
|
||||
// We can't guess if an element is compatible before we get one
|
||||
// We exit the function completely if it's not compatible
|
||||
if (
|
||||
(element as any).inheritedTagName.toUpperCase() !== tagName.toUpperCase()
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
(element as any).refresh();
|
||||
}
|
||||
};
|
||||
for (const registered of registeredComponents) {
|
||||
refreshElement(registered, (node as HTMLElement).tagName);
|
||||
}
|
||||
}
|
||||
}
|
||||
startObserver(observer);
|
||||
});
|
||||
|
||||
startObserver(observer);
|
||||
|
||||
/**
|
||||
* Web component used to import css files only once
|
||||
* If called multiple times or the file was already imported, it does nothing
|
||||
**/
|
||||
@registerElementOnce("link-once")
|
||||
export class LinkOnce extends elementOnce("link") {
|
||||
getElementQuerySelector(): string {
|
||||
@registerComponent("link-once")
|
||||
export class LinkOnce extends inheritHtmlElement("link") {
|
||||
connectedCallback() {
|
||||
super.connectedCallback(false);
|
||||
// We get href from node.attributes instead of node.href to avoid getting the domain part
|
||||
return `link[href='${this.node.attributes.getNamedItem("href").nodeValue}']`;
|
||||
const href = this.node.attributes.getNamedItem("href").nodeValue;
|
||||
if (document.querySelectorAll(`link[href='${href}']`).length === 0) {
|
||||
this.appendChild(this.node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,10 +20,14 @@ export class LinkOnce extends elementOnce("link") {
|
||||
* Web component used to import javascript files only once
|
||||
* If called multiple times or the file was already imported, it does nothing
|
||||
**/
|
||||
@registerElementOnce("script-once")
|
||||
@registerComponent("script-once")
|
||||
export class ScriptOnce extends inheritHtmlElement("script") {
|
||||
getElementQuerySelector(): string {
|
||||
// We get href from node.attributes instead of node.src to avoid getting the domain part
|
||||
return `script[src='${this.node.attributes.getNamedItem("src").nodeValue}']`;
|
||||
connectedCallback() {
|
||||
super.connectedCallback(false);
|
||||
// We get src from node.attributes instead of node.src to avoid getting the domain part
|
||||
const src = this.node.attributes.getNamedItem("src").nodeValue;
|
||||
if (document.querySelectorAll(`script[src='${src}']`).length === 0) {
|
||||
this.appendChild(this.node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,6 @@ export function registerComponent(name: string, options?: ElementDefinitionOptio
|
||||
**/
|
||||
export function inheritHtmlElement<K extends keyof HTMLElementTagNameMap>(tagName: K) {
|
||||
return class Inherited extends HTMLElement {
|
||||
readonly inheritedTagName = tagName;
|
||||
protected node: HTMLElementTagNameMap[K];
|
||||
|
||||
connectedCallback(autoAddNode?: boolean) {
|
||||
|
||||
@@ -5,9 +5,8 @@
|
||||
<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('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>
|
||||
<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>
|
||||
</ul>
|
||||
</details>
|
||||
<details name="navbar" class="menu">
|
||||
|
||||
@@ -48,6 +48,6 @@
|
||||
>{% trans %}Delete{% endtrans %}</button></p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{{ paginate_htmx(page_obj, paginator) }}
|
||||
{{ paginate_htmx(request, page_obj, paginator) }}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -118,20 +118,21 @@
|
||||
</nav>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro paginate_jinja(current_page, paginator) %}
|
||||
{% macro paginate_jinja(request, current_page, paginator) %}
|
||||
{# Add pagination buttons for pages without Alpine.
|
||||
|
||||
This must be coupled with a view that handles pagination
|
||||
with the Django Paginator object.
|
||||
|
||||
Parameters:
|
||||
request (django.http.request.HttpRequest): the current django request
|
||||
current_page (django.core.paginator.Page): the current page object
|
||||
paginator (django.core.paginator.Paginator): the paginator object
|
||||
#}
|
||||
{{ paginate_server_side(current_page, paginator, False) }}
|
||||
{{ paginate_server_side(request, current_page, paginator, False) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro paginate_htmx(current_page, paginator) %}
|
||||
{% macro paginate_htmx(request, current_page, paginator) %}
|
||||
{# Add pagination buttons for pages without Alpine but supporting fragments.
|
||||
|
||||
This must be coupled with a view that handles pagination
|
||||
@@ -140,24 +141,25 @@
|
||||
The replaced fragment will be #content so make sure you are calling this macro inside your content block.
|
||||
|
||||
Parameters:
|
||||
request (django.http.request.HttpRequest): the current django request
|
||||
current_page (django.core.paginator.Page): the current page object
|
||||
paginator (django.core.paginator.Paginator): the paginator object
|
||||
#}
|
||||
{{ paginate_server_side(current_page, paginator, True) }}
|
||||
{{ paginate_server_side(request, current_page, paginator, True) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro paginate_server_side(current_page, paginator, use_htmx) %}
|
||||
{% macro paginate_server_side(request, current_page, paginator, use_htmx) %}
|
||||
<nav class="pagination">
|
||||
{% if current_page.has_previous() %}
|
||||
<a
|
||||
{% if use_htmx -%}
|
||||
hx-get="?{{ querystring(page=current_page.previous_page_number()) }}"
|
||||
hx-get="?{{ querystring(request, page=current_page.previous_page_number()) }}"
|
||||
hx-swap="innerHTML"
|
||||
hx-target="#content"
|
||||
hx-push-url="true"
|
||||
hx-trigger="click, keyup[key=='ArrowLeft'] from:body"
|
||||
{%- else -%}
|
||||
href="?{{ querystring(page=current_page.previous_page_number()) }}"
|
||||
href="?{{ querystring(request, page=current_page.previous_page_number()) }}"
|
||||
{%- endif -%}
|
||||
>
|
||||
<button>
|
||||
@@ -175,12 +177,12 @@
|
||||
{% else %}
|
||||
<a
|
||||
{% if use_htmx -%}
|
||||
hx-get="?{{ querystring(page=i) }}"
|
||||
hx-get="?{{ querystring(request, page=i) }}"
|
||||
hx-swap="innerHTML"
|
||||
hx-target="#content"
|
||||
hx-push-url="true"
|
||||
{%- else -%}
|
||||
href="?{{ querystring(page=i) }}"
|
||||
href="?{{ querystring(request, page=i) }}"
|
||||
{%- endif -%}
|
||||
>
|
||||
<button>{{ i }}</button>
|
||||
@@ -190,13 +192,13 @@
|
||||
{% if current_page.has_next() %}
|
||||
<a
|
||||
{% if use_htmx -%}
|
||||
hx-get="?{{querystring(page=current_page.next_page_number())}}"
|
||||
hx-get="?{{querystring(request, page=current_page.next_page_number())}}"
|
||||
hx-swap="innerHTML"
|
||||
hx-target="#content"
|
||||
hx-push-url="true"
|
||||
hx-trigger="click, keyup[key=='ArrowRight'] from:body"
|
||||
{%- else -%}
|
||||
href="?{{querystring(page=current_page.next_page_number())}}"
|
||||
href="?{{querystring(request, page=current_page.next_page_number())}}"
|
||||
{%- endif -%}
|
||||
><button>
|
||||
<i class="fa fa-caret-right"></i>
|
||||
@@ -247,15 +249,8 @@
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{% macro querystring() %}
|
||||
{%- for key, values in request.GET.lists() -%}
|
||||
{%- if key not in kwargs -%}
|
||||
{%- for value in values -%}
|
||||
{{ key }}={{ value }}&
|
||||
{%- endfor -%}
|
||||
{%- endif -%}
|
||||
{%- endfor -%}
|
||||
{%- for key, value in kwargs.items() -%}
|
||||
{{ key }}={{ value }}&
|
||||
{%- endfor -%}
|
||||
{% macro querystring(request) %}
|
||||
{%- set qs = request.GET.copy() -%}
|
||||
{%- do qs.update(kwargs) -%}
|
||||
{{- qs | urlencode -}}
|
||||
{% endmacro %}
|
||||
@@ -1,7 +1,14 @@
|
||||
{% extends "core/base.jinja" %}
|
||||
|
||||
{%- block additional_js -%}
|
||||
<script type="module" src="{{ static("bundled/core/components/ajax-select-index.ts") }}"></script>
|
||||
{%- endblock -%}
|
||||
|
||||
{%- block additional_css -%}
|
||||
<link rel="stylesheet" href="{{ static('user/user_preferences.scss') }}">
|
||||
{# importing ajax-select-index is necessary for it to be applied after HTMX reload #}
|
||||
<link rel="stylesheet" href="{{ static("bundled/core/components/ajax-select-index.css") }}">
|
||||
<link rel="stylesheet" href="{{ static("core/components/ajax-select.scss") }}">
|
||||
{%- endblock -%}
|
||||
|
||||
{% block title %}
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
</table>
|
||||
<br>
|
||||
{% if is_paginated %}
|
||||
{{ paginate_jinja(page_obj, paginator) }}
|
||||
{{ paginate_jinja(request, page_obj, paginator) }}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% trans %}There is no cash register summary in this website.{% endtrans %}
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
{%- endfor %}
|
||||
</table>
|
||||
{% if is_paginated %}
|
||||
{{ paginate_jinja(page_obj, paginator) }}
|
||||
{{ paginate_jinja(request, page_obj, paginator) }}
|
||||
{% endif %}
|
||||
{%- endblock %}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
</section>
|
||||
{%- endfor %}
|
||||
{% if is_paginated %}
|
||||
{{ paginate_jinja(page_obj, paginator) }}
|
||||
{{ paginate_jinja(request, page_obj, paginator) }}
|
||||
{% endif %}
|
||||
{%- endblock %}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
</p>
|
||||
|
||||
{{ display_search_bar(request) }}
|
||||
{{ paginate_jinja(msgs, msgs.paginator) }}
|
||||
{{ paginate_jinja(request, msgs, msgs.paginator) }}
|
||||
|
||||
<main class="message-list">
|
||||
{% for m in msgs %}
|
||||
@@ -44,7 +44,9 @@
|
||||
|
||||
<p><a class="ib button" href="{{ url('forum:new_message', topic_id=topic.id) }}">{% trans %}Reply{% endtrans %}</a></p>
|
||||
|
||||
{{ paginate_jinja(msgs, msgs.paginator) }}
|
||||
{% if is_paginated %}
|
||||
{{ paginate_jinja(request, msgs, msgs.paginator) }}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@@ -310,16 +310,36 @@ 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 "inactive"
|
||||
msgstr "inactif"
|
||||
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"
|
||||
|
||||
#: 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 "There is no club in this website."
|
||||
msgstr "Il n'y a pas de club dans ce site web."
|
||||
msgid "inactive"
|
||||
msgstr "inactif"
|
||||
|
||||
#: club/templates/club/club_members.jinja
|
||||
msgid "Club members"
|
||||
@@ -1881,10 +1901,6 @@ 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"
|
||||
|
||||
@@ -28,5 +28,7 @@
|
||||
</table>
|
||||
|
||||
<br>
|
||||
{{ paginate_jinja(page_obj, paginator) }}
|
||||
{% if is_paginated %}
|
||||
{{ paginate_jinja(request, page_obj, paginator) }}
|
||||
{% endif %}
|
||||
{% endblock content %}
|
||||
@@ -6,8 +6,14 @@
|
||||
{% 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 type="module" src="{{ static('bundled/core/components/tabs-index.ts') }}"></script>
|
||||
<script type="module" src="{{ static("bundled/core/components/ajax-select-index.ts") }}"></script>
|
||||
<script
|
||||
type="module"
|
||||
src="{{ static("bundled/subscription/creation-form-existing-user-index.ts") }}"
|
||||
@@ -15,6 +21,8 @@
|
||||
{% endblock %}
|
||||
{% block additional_css %}
|
||||
<link rel="stylesheet" href="{{ static("core/components/tabs.scss") }}">
|
||||
<link rel="stylesheet" href="{{ static("bundled/core/components/ajax-select-index.css") }}">
|
||||
<link rel="stylesheet" href="{{ static("core/components/ajax-select.scss") }}">
|
||||
<link rel="stylesheet" href="{{ static("subscription/css/subscription.scss") }}">
|
||||
{% endblock %}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user