mirror of
https://github.com/ae-utbm/sith.git
synced 2026-05-13 20:48:06 +00:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a4a5757835 | |||
| 3a1f4388fd | |||
| 3ff61e3835 | |||
| 2de1f9f937 | |||
| f5eac164ec | |||
| 2b0c36c085 | |||
| 74a7f4ffc9 | |||
| b9b0c00b74 | |||
| 59847b3973 | |||
| b7275bd843 |
+16
-1
@@ -16,7 +16,7 @@ from django.contrib import admin
|
|||||||
from django.forms.models import ModelForm
|
from django.forms.models import ModelForm
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
|
|
||||||
from club.models import Club, ClubRole, Membership
|
from club.models import Club, ClubLink, ClubRole, LinkType, Membership
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Club)
|
@admin.register(Club)
|
||||||
@@ -67,3 +67,18 @@ class MembershipAdmin(admin.ModelAdmin):
|
|||||||
"club__name",
|
"club__name",
|
||||||
)
|
)
|
||||||
autocomplete_fields = ("user",)
|
autocomplete_fields = ("user",)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(LinkType)
|
||||||
|
class LinkTypeAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ("name", "url_base", "icon")
|
||||||
|
search_fields = ("name",)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(ClubLink)
|
||||||
|
class ClubLinkAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ("link_type", "club", "url")
|
||||||
|
list_select_related = ("link_type", "club")
|
||||||
|
autocomplete_fields = ("link_type", "club")
|
||||||
|
search_fields = ("link_type__name", "url")
|
||||||
|
list_filter = ("link_type", ("club", admin.RelatedOnlyFieldListFilter))
|
||||||
|
|||||||
+1
-1
@@ -41,7 +41,7 @@ class ClubController(ControllerBase):
|
|||||||
queryset=Membership.objects.ongoing().select_related("user", "role"),
|
queryset=Membership.objects.ongoing().select_related("user", "role"),
|
||||||
)
|
)
|
||||||
return self.get_object_or_exception(
|
return self.get_object_or_exception(
|
||||||
Club.objects.prefetch_related(prefetch), id=club_id
|
Club.objects.prefetch_related(prefetch, "links"), id=club_id
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+42
-1
@@ -28,7 +28,14 @@ from django.db.models.functions import Lower
|
|||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
from django.utils.translation import gettext_lazy as _
|
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.models import User
|
||||||
from core.views.forms import SelectDateTime
|
from core.views.forms import SelectDateTime
|
||||||
from core.views.widgets.ajax_select import (
|
from core.views.widgets.ajax_select import (
|
||||||
@@ -39,6 +46,26 @@ from counter.models import Counter, Selling
|
|||||||
from counter.schemas import SaleFilterSchema
|
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):
|
class ClubEditForm(forms.ModelForm):
|
||||||
error_css_class = "error"
|
error_css_class = "error"
|
||||||
required_css_class = "required"
|
required_css_class = "required"
|
||||||
@@ -48,6 +75,20 @@ class ClubEditForm(forms.ModelForm):
|
|||||||
fields = ["address", "logo", "short_description"]
|
fields = ["address", "logo", "short_description"]
|
||||||
widgets = {"short_description": forms.Textarea()}
|
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):
|
class ClubAdminEditForm(ClubEditForm):
|
||||||
admin_fields = ["name", "parent", "is_active"]
|
admin_fields = ["name", "parent", "is_active"]
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
# Generated by Django 5.2.12 on 2026-04-27 07:39
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [("club", "0016_clubrole_alter_membership_role")]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="LinkType",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.AutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"name",
|
||||||
|
models.CharField(max_length=40, unique=True, verbose_name="name"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"url_base",
|
||||||
|
models.URLField(
|
||||||
|
help_text=(
|
||||||
|
"L'url de base que tous les "
|
||||||
|
"liens de ce type doivent respecter "
|
||||||
|
"(par exemple `https://www.instagram.com`)"
|
||||||
|
),
|
||||||
|
unique=True,
|
||||||
|
verbose_name="url base",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"icon",
|
||||||
|
models.CharField(
|
||||||
|
help_text=(
|
||||||
|
"The fontawesome class to use "
|
||||||
|
"(e.g. `fa-brands fa-instagram`)"
|
||||||
|
),
|
||||||
|
max_length=40,
|
||||||
|
verbose_name="icon",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={"verbose_name": "link type", "verbose_name_plural": "link types"},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="ClubLink",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.AutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"name",
|
||||||
|
models.CharField(blank=True, max_length=40, verbose_name="name"),
|
||||||
|
),
|
||||||
|
("url", models.URLField(verbose_name="link url")),
|
||||||
|
(
|
||||||
|
"created_at",
|
||||||
|
models.DateTimeField(auto_now_add=True, verbose_name="created at"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"updated_at",
|
||||||
|
models.DateTimeField(auto_now=True, verbose_name="updated at"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"club",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="links",
|
||||||
|
to="club.club",
|
||||||
|
verbose_name="club",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"link_type",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="links",
|
||||||
|
to="club.linktype",
|
||||||
|
verbose_name="link type",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={"verbose_name": "club link", "verbose_name_plural": "club links"},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -774,3 +774,74 @@ class MailingSubscription(models.Model):
|
|||||||
|
|
||||||
def fetch_format(self):
|
def fetch_format(self):
|
||||||
return self.get_email + " "
|
return self.get_email + " "
|
||||||
|
|
||||||
|
|
||||||
|
class LinkType(models.Model):
|
||||||
|
"""A link type, in order to group links and give them icons.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
Among all club links, there is a special one, with an empty base url
|
||||||
|
and a default link icon.
|
||||||
|
It is use as a fallback item when no actual link type can be found.
|
||||||
|
|
||||||
|
Danger:
|
||||||
|
LinkType.icon is content that will be raw-rendered in the template.
|
||||||
|
It is NOT safe to allow users to give it.
|
||||||
|
The edition of this field must be reserved to trusted admins.
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = models.CharField(_("name"), max_length=40, unique=True)
|
||||||
|
url_base = models.URLField(
|
||||||
|
"url base",
|
||||||
|
unique=True,
|
||||||
|
help_text=_(
|
||||||
|
"The base url that links with this type must respect (e.g. `%(url)s`)"
|
||||||
|
)
|
||||||
|
% {"url": "https://www.instagram.com"},
|
||||||
|
)
|
||||||
|
icon = models.CharField(
|
||||||
|
_("icon"),
|
||||||
|
max_length=40,
|
||||||
|
help_text=_("The fontawesome class to use (e.g. `fa-brands fa-instagram`)"),
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("link type")
|
||||||
|
verbose_name_plural = _("link types")
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
|
class ClubLink(models.Model):
|
||||||
|
link_type = models.ForeignKey(
|
||||||
|
LinkType,
|
||||||
|
verbose_name=_("link type"),
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name="links",
|
||||||
|
)
|
||||||
|
name = models.CharField(_("name"), max_length=40, blank=True)
|
||||||
|
url = models.URLField(_("link url"))
|
||||||
|
club = models.ForeignKey(
|
||||||
|
Club, verbose_name=_("club"), on_delete=models.CASCADE, related_name="links"
|
||||||
|
)
|
||||||
|
created_at = models.DateTimeField(_("created at"), auto_now_add=True)
|
||||||
|
updated_at = models.DateTimeField(_("updated at"), auto_now=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("club link")
|
||||||
|
verbose_name_plural = _("club links")
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.url
|
||||||
|
|
||||||
|
def save(self, **kwargs):
|
||||||
|
if not self.name:
|
||||||
|
self.name = self.link_type.name
|
||||||
|
return super().save(**kwargs)
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
if not self.url.startswith(self.link_type.url_base):
|
||||||
|
raise ValidationError(
|
||||||
|
_("This link doesn't match with the url base of its type.")
|
||||||
|
)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from typing import Annotated
|
|||||||
|
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from ninja import FilterLookup, FilterSchema, ModelSchema
|
from ninja import FilterLookup, FilterSchema, ModelSchema
|
||||||
|
from pydantic import HttpUrl
|
||||||
|
|
||||||
from club.models import Club, ClubRole, Membership
|
from club.models import Club, ClubRole, Membership
|
||||||
from core.schemas import NonEmptyStr, SimpleUserSchema
|
from core.schemas import NonEmptyStr, SimpleUserSchema
|
||||||
@@ -62,6 +63,11 @@ class ClubSchema(ModelSchema):
|
|||||||
fields = ["id", "name", "logo", "is_active", "short_description", "address"]
|
fields = ["id", "name", "logo", "is_active", "short_description", "address"]
|
||||||
|
|
||||||
members: list[ClubMemberSchema]
|
members: list[ClubMemberSchema]
|
||||||
|
links: list[HttpUrl]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_links(obj: Club):
|
||||||
|
return [link.url for link in obj.links.all()]
|
||||||
|
|
||||||
|
|
||||||
class UserMembershipSchema(ModelSchema):
|
class UserMembershipSchema(ModelSchema):
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
#club-detail {
|
||||||
|
img.club-logo {
|
||||||
|
display: block;
|
||||||
|
max-height: 200px;
|
||||||
|
max-width: 200px;
|
||||||
|
}
|
||||||
|
#club-attributes {
|
||||||
|
ul {
|
||||||
|
list-style: none;
|
||||||
|
margin-left: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: .75rem;
|
||||||
|
|
||||||
|
li i {
|
||||||
|
margin-right: .5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(.has-links) {
|
||||||
|
#club-attributes {
|
||||||
|
float: right;
|
||||||
|
margin: 1em 0 1em 2em;
|
||||||
|
|
||||||
|
@media screen and (max-width: 650px) {
|
||||||
|
margin-left: 1em;
|
||||||
|
}
|
||||||
|
@media screen and (max-width: 400px) {
|
||||||
|
float: unset;
|
||||||
|
img.club-logo {
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.has-links {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
gap: 2em;
|
||||||
|
|
||||||
|
@media screen and (max-width: 650px) {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#club-attributes {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1em;
|
||||||
|
min-width: 200px;
|
||||||
|
@media screen and (max-width: 650px) {
|
||||||
|
margin-top: 1em;
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
justify-content: flex-end;
|
||||||
|
h4 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
img.club-logo {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,15 +21,43 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block additional_css %}
|
||||||
|
<link rel="stylesheet" href="{{ static("club/detail.scss") }}">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div id="club_detail">
|
<h3>{{ club.name }}</h3>
|
||||||
{% if club.logo %}
|
<div id="club-detail" {% if links %}class="has-links"{% endif %}>
|
||||||
<div class="club_logo"><img src="{{ club.logo.url }}" alt="{{ club.name }}"></div>
|
<div id="club-attributes">
|
||||||
{% endif %}
|
{% if club.logo %}
|
||||||
<h3>{{ club.name }}</h3>
|
<img
|
||||||
{% if page_revision %}
|
class="club-logo"
|
||||||
{{ page_revision|markdown }}
|
src="{{ club.logo.url }}"
|
||||||
{% endif %}
|
alt="{{ club.name }}"
|
||||||
|
width="{{ club.logo.width }}"
|
||||||
|
height="{{ club.logo.height }}"
|
||||||
|
>
|
||||||
|
{% endif %}
|
||||||
|
{% if links %}
|
||||||
|
<div id="club-links">
|
||||||
|
<h4>{% trans %}Links{% endtrans %}</h4>
|
||||||
|
<ul>
|
||||||
|
{% for link in links %}
|
||||||
|
<li>
|
||||||
|
<a href="{{ link.url }}" rel="noopener" target="_blank">
|
||||||
|
<i class="{{ link.link_type.icon }} fa-xl"></i>{{ link.name }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div id="club-page">
|
||||||
|
{% if page_revision %}
|
||||||
|
{{ page_revision|markdown }}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
{% if is_fragment %}
|
{% if is_fragment %}
|
||||||
{% extends "core/base_fragment.jinja" %}
|
{% extends "core/base_fragment.jinja" %}
|
||||||
|
|
||||||
|
{% block metatags %}
|
||||||
|
<meta property="og:url" content="{{ request.build_absolute_uri() }}" />
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
<meta property="og:title" content="Liste des clubs et assos" />
|
||||||
|
<meta property="og:image" content="{{ request.build_absolute_uri(static("core/img/logo_no_text.png")) }}" />
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{# Don't display tabs and errors #}
|
{# Don't display tabs and errors #}
|
||||||
{% block tabs %}
|
{% block tabs %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -62,6 +69,18 @@
|
|||||||
{{ club.name }} {% if not club.is_active %}({% trans %}inactive{% endtrans %}){% endif %}
|
{{ club.name }} {% if not club.is_active %}({% trans %}inactive{% endtrans %}){% endif %}
|
||||||
</h4>
|
</h4>
|
||||||
</a>
|
</a>
|
||||||
|
{% set links = club.links.all() %}
|
||||||
|
{% if links %}
|
||||||
|
<br>
|
||||||
|
<div class="row gap-2x">
|
||||||
|
{% for link in club.links.all() %}
|
||||||
|
<a href="{{ link.url }}">
|
||||||
|
<i class="{{ link.link_type.icon }} fa-xl"></i>
|
||||||
|
<strong>{{ link.name }}</strong>
|
||||||
|
</a>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
{{ club.short_description|markdown }}
|
{{ club.short_description|markdown }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,9 +1,60 @@
|
|||||||
{% extends "core/base.jinja" %}
|
{% extends "core/base.jinja" %}
|
||||||
|
|
||||||
|
{% block additional_js %}
|
||||||
|
<script type="module" src="{{ static("bundled/core/dynamic-formset-index.ts") }}"></script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{% trans name=object %}Edit {{ name }}{% endtrans %}
|
{% trans name=object %}Edit {{ name }}{% endtrans %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% macro link_form(form) %}
|
||||||
|
<fieldset
|
||||||
|
{# set url in x-init rather than in x-data,
|
||||||
|
in order to trigger the $watch on initial load #}
|
||||||
|
x-data="{ url: '', linkType: { icon: '', id: 0 } }"
|
||||||
|
x-init="() => {
|
||||||
|
$watch('url', (u) => linkType = linkTypes.find((t) => u.startsWith(t.url)));
|
||||||
|
url = '{{ form.url.value() or "" }}';
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ form.non_field_errors() }}
|
||||||
|
<div class="form-group row gap-2x">
|
||||||
|
<div>
|
||||||
|
{{ form.url.label_tag() }}
|
||||||
|
{{ form.url.errors }}
|
||||||
|
<span>
|
||||||
|
{# 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')") }}
|
||||||
|
<i :class="linkType.icon || 'fa fa-link'"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>{{ form.name.as_field_group() }}</div>
|
||||||
|
</div>
|
||||||
|
{%- if form.DELETE -%}
|
||||||
|
<div class="form-group row gap">
|
||||||
|
{{ form.DELETE.as_field_group() }}
|
||||||
|
</div>
|
||||||
|
{%- else -%}
|
||||||
|
<br>
|
||||||
|
<button
|
||||||
|
class="btn btn-grey"
|
||||||
|
@click.prevent="removeForm($event.target.closest('fieldset'))"
|
||||||
|
>
|
||||||
|
<i class="fa fa-minus"></i> {% trans %}Remove link{% endtrans %}
|
||||||
|
</button>
|
||||||
|
{%- endif -%}
|
||||||
|
{{ form.link_type|add_attr(":value=linkType.id") }}
|
||||||
|
{%- for field in form.hidden_fields() -%}
|
||||||
|
{%- if field != form.link_type -%}
|
||||||
|
{{ field }}
|
||||||
|
{%- endif -%}
|
||||||
|
{%- endfor -%}
|
||||||
|
</fieldset>
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>{% trans name=object %}Edit {{ name }}{% endtrans %}</h2>
|
<h2>{% trans name=object %}Edit {{ name }}{% endtrans %}</h2>
|
||||||
|
|
||||||
@@ -17,7 +68,7 @@
|
|||||||
and explicitly separate them from the non-admin ones,
|
and explicitly separate them from the non-admin ones,
|
||||||
with some help text.
|
with some help text.
|
||||||
Non-admin users will only see the regular form fields,
|
Non-admin users will only see the regular form fields,
|
||||||
so they don't need thoses explanations #}
|
so they don't need those explanations #}
|
||||||
<h3>{% trans %}Club properties{% endtrans %}</h3>
|
<h3>{% trans %}Club properties{% endtrans %}</h3>
|
||||||
<p class="helptext">
|
<p class="helptext">
|
||||||
{% trans trimmed %}
|
{% trans trimmed %}
|
||||||
@@ -25,7 +76,7 @@
|
|||||||
Only admin users can see and edit them.
|
Only admin users can see and edit them.
|
||||||
{% endtrans %}
|
{% endtrans %}
|
||||||
</p>
|
</p>
|
||||||
<fieldset class="required margin-bottom">
|
<fieldset class="margin-bottom">
|
||||||
{% for field_name in form.admin_fields %}
|
{% for field_name in form.admin_fields %}
|
||||||
{% set field = form[field_name] %}
|
{% set field = form[field_name] %}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
@@ -36,11 +87,13 @@
|
|||||||
{# Remove the the admin fields from the form.
|
{# Remove the the admin fields from the form.
|
||||||
The remaining non-admin fields will be rendered
|
The remaining non-admin fields will be rendered
|
||||||
at once with a simple {{ form.as_p() }} #}
|
at once with a simple {{ form.as_p() }} #}
|
||||||
{% set _ = form.fields.pop(field_name) %}
|
{% do form.fields.pop(field_name) %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<h3>{% trans %}Club informations{% endtrans %}</h3>
|
<h3>{% trans %}Club informations{% endtrans %}</h3>
|
||||||
|
{% if form.admin_fields %}
|
||||||
<p class="helptext">
|
<p class="helptext">
|
||||||
{% trans trimmed %}
|
{% trans trimmed %}
|
||||||
The following form fields are linked to the basic description of a club.
|
The following form fields are linked to the basic description of a club.
|
||||||
@@ -48,7 +101,45 @@
|
|||||||
{% endtrans %}
|
{% endtrans %}
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{{ form.as_p() }}
|
<fieldset class="margin-bottom">
|
||||||
<p><input type="submit" value="{% trans %}Save{% endtrans %}" /></p>
|
{{ form.as_p() }}
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<h3>{% trans %}Club links{% endtrans %}</h3>
|
||||||
|
<div x-data="dynamicFormSet({ prefix: '{{ form.link_formset.prefix }}' })" class="margin-bottom">
|
||||||
|
{{ form.link_formset.management_form }}
|
||||||
|
<div x-ref="formContainer">
|
||||||
|
{%- for f in form.link_formset.forms -%}
|
||||||
|
{{ link_form(f) }}
|
||||||
|
{%- endfor -%}
|
||||||
|
</div>
|
||||||
|
<template x-ref="formTemplate">
|
||||||
|
{{ link_form(form.link_formset.empty_form) }}
|
||||||
|
</template>
|
||||||
|
<p>
|
||||||
|
<i>{% 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 %}</i>
|
||||||
|
</p>
|
||||||
|
<br>
|
||||||
|
<button @click.prevent="addForm()" class="btn btn-grey">
|
||||||
|
<i class="fa fa-plus"></i>{% trans %}Add link{% endtrans %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<button type="submit" class="btn btn-blue">
|
||||||
|
<i class="fa fa-check"></i>{% trans %}Save{% endtrans %}
|
||||||
|
</button>
|
||||||
</form>
|
</form>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
||||||
|
{% block script %}
|
||||||
|
<script>
|
||||||
|
const linkTypes = [
|
||||||
|
{%- for t in link_types -%}
|
||||||
|
{ id: {{ t.id }}, url: '{{ t.url_base }}', icon: '{{ t.icon }}' },
|
||||||
|
{%- endfor -%}
|
||||||
|
];
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
|||||||
@@ -88,8 +88,8 @@ class TestFetchClub:
|
|||||||
def test_fetch_club_nb_queries(self, client: Client, club: Club):
|
def test_fetch_club_nb_queries(self, client: Client, club: Club):
|
||||||
user = subscriber_user.make()
|
user = subscriber_user.make()
|
||||||
client.force_login(user)
|
client.force_login(user)
|
||||||
with assertNumQueries(6):
|
with assertNumQueries(7):
|
||||||
# - 4 queries for authentication
|
# - 4 queries for authentication
|
||||||
# - 2 queries for the actual data
|
# - 3 queries for the actual data
|
||||||
res = client.get(reverse("api:fetch_club", kwargs={"club_id": club.id}))
|
res = client.get(reverse("api:fetch_club", kwargs={"club_id": club.id}))
|
||||||
assert res.status_code == 200
|
assert res.status_code == 200
|
||||||
|
|||||||
@@ -21,7 +21,13 @@ def test_club_board_member_cannot_edit_club_properties(client: Client):
|
|||||||
client.force_login(user)
|
client.force_login(user)
|
||||||
res = client.post(
|
res = client.post(
|
||||||
reverse("club:club_edit", kwargs={"club_id": club.id}),
|
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,
|
# The request should success,
|
||||||
# but admin-only fields shouldn't be taken into account
|
# but admin-only fields shouldn't be taken into account
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ def test_page_display_on_club_main_page(client: Client):
|
|||||||
|
|
||||||
assert res.status_code == 200
|
assert res.status_code == 200
|
||||||
soup = BeautifulSoup(res.text, "lxml")
|
soup = BeautifulSoup(res.text, "lxml")
|
||||||
detail_html = soup.find(id="club_detail").find(class_="markdown")
|
detail_html = soup.find(id="club-page").find(class_="markdown")
|
||||||
assertHTMLEqual(detail_html.decode_contents(), markdown(content))
|
assertHTMLEqual(detail_html.decode_contents(), markdown(content))
|
||||||
|
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ def test_club_main_page_without_content(client: Client):
|
|||||||
|
|
||||||
assert res.status_code == 200
|
assert res.status_code == 200
|
||||||
soup = BeautifulSoup(res.text, "lxml")
|
soup = BeautifulSoup(res.text, "lxml")
|
||||||
detail_html = soup.find(id="club_detail")
|
detail_html = soup.find(id="club-page")
|
||||||
assert detail_html.find_all("markdown") == []
|
assert detail_html.find_all("markdown") == []
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+23
-7
@@ -32,7 +32,8 @@ from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMix
|
|||||||
from django.contrib.messages.views import SuccessMessageMixin
|
from django.contrib.messages.views import SuccessMessageMixin
|
||||||
from django.core.exceptions import NON_FIELD_ERRORS, PermissionDenied, ValidationError
|
from django.core.exceptions import NON_FIELD_ERRORS, PermissionDenied, ValidationError
|
||||||
from django.core.paginator import InvalidPage, Paginator
|
from django.core.paginator import InvalidPage, Paginator
|
||||||
from django.db.models import F, Q, Sum
|
from django.db.models import F, Prefetch, Q, Sum
|
||||||
|
from django.db.models.functions import Length
|
||||||
from django.http import Http404, StreamingHttpResponse
|
from django.http import Http404, StreamingHttpResponse
|
||||||
from django.shortcuts import get_object_or_404, redirect
|
from django.shortcuts import get_object_or_404, redirect
|
||||||
from django.urls import reverse, reverse_lazy
|
from django.urls import reverse, reverse_lazy
|
||||||
@@ -60,7 +61,14 @@ from club.forms import (
|
|||||||
MailingForm,
|
MailingForm,
|
||||||
SellingsForm,
|
SellingsForm,
|
||||||
)
|
)
|
||||||
from club.models import Club, Mailing, MailingSubscription, Membership
|
from club.models import (
|
||||||
|
Club,
|
||||||
|
ClubLink,
|
||||||
|
LinkType,
|
||||||
|
Mailing,
|
||||||
|
MailingSubscription,
|
||||||
|
Membership,
|
||||||
|
)
|
||||||
from com.models import Poster
|
from com.models import Poster
|
||||||
from com.views import (
|
from com.views import (
|
||||||
PosterCreateBaseView,
|
PosterCreateBaseView,
|
||||||
@@ -204,20 +212,22 @@ class ClubListView(AllowFragment, FormMixin, ListView):
|
|||||||
|
|
||||||
template_name = "club/club_list.jinja"
|
template_name = "club/club_list.jinja"
|
||||||
form_class = ClubSearchForm
|
form_class = ClubSearchForm
|
||||||
queryset = Club.objects.order_by("name")
|
queryset = Club.objects.prefetch_related(
|
||||||
|
Prefetch("links", queryset=ClubLink.objects.select_related("link_type"))
|
||||||
|
).order_by("name")
|
||||||
paginate_by = 20
|
paginate_by = 20
|
||||||
|
|
||||||
def get_form_kwargs(self):
|
def get_form_kwargs(self):
|
||||||
res = super().get_form_kwargs()
|
res = super().get_form_kwargs()
|
||||||
if self.request.method == "GET":
|
# if request.GET is empty, the form will interpret club_status as None,
|
||||||
res |= {"data": self.request.GET, "initial": self.request.GET}
|
# even though we want it to be initially True,
|
||||||
|
# so we force a defaut True value.
|
||||||
|
res["data"] = {"club_status": True} | self.request.GET.dict()
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
form: ClubSearchForm = self.get_form()
|
form: ClubSearchForm = self.get_form()
|
||||||
qs = self.queryset
|
qs = self.queryset
|
||||||
if not form.is_bound:
|
|
||||||
return qs.filter(is_active=True)
|
|
||||||
if not form.is_valid():
|
if not form.is_valid():
|
||||||
return qs.none()
|
return qs.none()
|
||||||
if name := form.cleaned_data.get("name"):
|
if name := form.cleaned_data.get("name"):
|
||||||
@@ -243,6 +253,7 @@ class ClubView(ClubTabsMixin, DetailView):
|
|||||||
.values_list("content", flat=True)
|
.values_list("content", flat=True)
|
||||||
.first()
|
.first()
|
||||||
)
|
)
|
||||||
|
kwargs["links"] = list(self.object.links.select_related("link_type").all())
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
|
|
||||||
@@ -570,6 +581,11 @@ class ClubEditView(ClubTabsMixin, CanEditMixin, UpdateView):
|
|||||||
return ClubAdminEditForm
|
return ClubAdminEditForm
|
||||||
return ClubEditForm
|
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):
|
class ClubCreateView(PermissionRequiredMixin, CreateView):
|
||||||
"""Create a club (for the Sith admin)."""
|
"""Create a club (for the Sith admin)."""
|
||||||
|
|||||||
@@ -16,9 +16,13 @@
|
|||||||
#right_column {
|
#right_column {
|
||||||
flex: 20%;
|
flex: 20%;
|
||||||
margin: 3.2px;
|
margin: 3.2px;
|
||||||
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
|
|
||||||
|
@media screen and (min-width: 800px) {
|
||||||
|
max-width: 20%;
|
||||||
|
min-width: 200px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#left_column {
|
#left_column {
|
||||||
@@ -46,7 +50,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: $small-devices) {
|
@media screen and (max-width: 800px) {
|
||||||
#left_column,
|
#left_column,
|
||||||
#right_column {
|
#right_column {
|
||||||
flex: 100%;
|
flex: 100%;
|
||||||
@@ -76,10 +80,10 @@
|
|||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background: white;
|
background: white;
|
||||||
font-size: 70%;
|
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
|
|
||||||
#links_content {
|
#links_content {
|
||||||
|
font-size: 85%;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
box-shadow: $shadow-color 1px 1px 1px;
|
box-shadow: $shadow-color 1px 1px 1px;
|
||||||
min-height: 20em;
|
min-height: 20em;
|
||||||
@@ -95,24 +99,10 @@
|
|||||||
|
|
||||||
li {
|
li {
|
||||||
margin: 10px;
|
margin: 10px;
|
||||||
|
|
||||||
.fa-facebook {
|
|
||||||
color: $faceblue;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fa-discord {
|
|
||||||
color: $discordblurple;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fa-square-instagram::before {
|
|
||||||
background: $instagradient;
|
|
||||||
background-clip: text;
|
|
||||||
-webkit-text-fill-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
i {
|
i {
|
||||||
width: 25px;
|
width: 25px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
margin-right: .5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ from django.utils import timezone
|
|||||||
from django.utils.timezone import localdate
|
from django.utils.timezone import localdate
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
from club.models import Club, ClubRole, Membership
|
from club.models import Club, ClubLink, ClubRole, LinkType, Membership
|
||||||
from com.ics_calendar import IcsCalendar
|
from com.ics_calendar import IcsCalendar
|
||||||
from com.models import News, NewsDate, Sith, Weekmail
|
from com.models import News, NewsDate, Sith, Weekmail
|
||||||
from core.models import BanGroup, Group, Page, PageRev, SithFile, User
|
from core.models import BanGroup, Group, Page, PageRev, SithFile, User
|
||||||
@@ -830,6 +830,54 @@ class Command(BaseCommand):
|
|||||||
):
|
):
|
||||||
roles.append(ClubRole(club=club, order=i, name=role))
|
roles.append(ClubRole(club=club, order=i, name=role))
|
||||||
ClubRole.objects.bulk_create(roles)
|
ClubRole.objects.bulk_create(roles)
|
||||||
|
insta, fb, discord, _ = LinkType.objects.bulk_create(
|
||||||
|
[
|
||||||
|
LinkType(
|
||||||
|
name="instagram",
|
||||||
|
icon="fa-brands fa-square-instagram",
|
||||||
|
url_base="https://www.instagram.com",
|
||||||
|
),
|
||||||
|
LinkType(
|
||||||
|
name="facebook",
|
||||||
|
icon="fa-brands fa-facebook",
|
||||||
|
url_base="https://www.facebook.com",
|
||||||
|
),
|
||||||
|
LinkType(
|
||||||
|
name="discord",
|
||||||
|
icon="fa-brands fa-discord",
|
||||||
|
url_base="https://discord.gg",
|
||||||
|
),
|
||||||
|
LinkType(name="generic", icon="fa fa-link", url_base=""),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
ClubLink.objects.bulk_create(
|
||||||
|
[
|
||||||
|
ClubLink(
|
||||||
|
name="insta AE",
|
||||||
|
url="https://www.instagram.com/ae_utbm/",
|
||||||
|
club=ae,
|
||||||
|
link_type=insta,
|
||||||
|
),
|
||||||
|
ClubLink(
|
||||||
|
name="insta activités AE",
|
||||||
|
url="https://www.instagram.com/activites_ae/",
|
||||||
|
club=ae,
|
||||||
|
link_type=insta,
|
||||||
|
),
|
||||||
|
ClubLink(
|
||||||
|
name="facebook AE",
|
||||||
|
url="https://www.facebook.com/ae_utbm",
|
||||||
|
club=ae,
|
||||||
|
link_type=fb,
|
||||||
|
),
|
||||||
|
ClubLink(
|
||||||
|
name="Discord",
|
||||||
|
url="https://discord.gg/QvTm3XJrHR",
|
||||||
|
club=ae,
|
||||||
|
link_type=discord,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
return PopulatedClubs(ae=ae, troll=troll, pdf=pdf, refound=refound)
|
return PopulatedClubs(ae=ae, troll=troll, pdf=pdf, refound=refound)
|
||||||
|
|
||||||
def _create_groups(self) -> PopulatedGroups:
|
def _create_groups(self) -> PopulatedGroups:
|
||||||
|
|||||||
+22
-13
@@ -398,6 +398,28 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Fontawesome icons */
|
||||||
|
.fa-brands, .fa-link {
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fa-facebook {
|
||||||
|
color: $faceblue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fa-discord {
|
||||||
|
color: $discordblurple;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fa-square-instagram::before, .fa-instagram::before {
|
||||||
|
background: $instagradient;
|
||||||
|
background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fa-bluesky, .fa-square-bluesky {
|
||||||
|
color: #0f73ff;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: $small-devices) {
|
@media screen and (max-width: $small-devices) {
|
||||||
@@ -749,16 +771,3 @@ textarea {
|
|||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*--------------------------------JQuery-------------------------------*/
|
|
||||||
#club_detail {
|
|
||||||
.club_logo {
|
|
||||||
float: right;
|
|
||||||
|
|
||||||
img {
|
|
||||||
display: block;
|
|
||||||
max-height: 10em;
|
|
||||||
max-width: 10em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -103,7 +103,7 @@ def add_attr(field: BoundField, attr: str):
|
|||||||
if "=" not in d:
|
if "=" not in d:
|
||||||
attrs["class"] = d
|
attrs["class"] = d
|
||||||
else:
|
else:
|
||||||
key, val = d.split("=")
|
key, val = d.split("=", maxsplit=1)
|
||||||
attrs[key] = val
|
attrs[key] = val
|
||||||
|
|
||||||
return field.as_widget(attrs=attrs)
|
return field.as_widget(attrs=attrs)
|
||||||
|
|||||||
@@ -32,8 +32,9 @@ class Migration(migrations.Migration):
|
|||||||
(
|
(
|
||||||
"result",
|
"result",
|
||||||
models.OneToOneField(
|
models.OneToOneField(
|
||||||
help_text="The formula product.",
|
help_text="The product got with the formula.",
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="formula",
|
||||||
to="counter.product",
|
to="counter.product",
|
||||||
verbose_name="result product",
|
verbose_name="result product",
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-05-12 09:48+0200\n"
|
"POT-Creation-Date: 2026-05-12 11:12+0200\n"
|
||||||
"PO-Revision-Date: 2016-07-18\n"
|
"PO-Revision-Date: 2016-07-18\n"
|
||||||
"Last-Translator: Maréchal <thomas.girod@utbm.fr\n"
|
"Last-Translator: Maréchal <thomas.girod@utbm.fr\n"
|
||||||
"Language-Team: AE info <ae.info@utbm.fr>\n"
|
"Language-Team: AE info <ae.info@utbm.fr>\n"
|
||||||
@@ -364,6 +364,58 @@ msgstr "Cet email est déjà abonné à cette mailing"
|
|||||||
msgid "Unregistered user"
|
msgid "Unregistered user"
|
||||||
msgstr "Utilisateur non enregistré"
|
msgstr "Utilisateur non enregistré"
|
||||||
|
|
||||||
|
#: club/models.py
|
||||||
|
#, python-format
|
||||||
|
msgid "The base url that links with this type must respect (e.g. `%(url)s`)"
|
||||||
|
msgstr ""
|
||||||
|
"L'url de base que tous les liens de ce type doivent respecter (par exemple "
|
||||||
|
"`%(url)s`)"
|
||||||
|
|
||||||
|
#: club/models.py counter/models.py
|
||||||
|
msgid "icon"
|
||||||
|
msgstr "icône"
|
||||||
|
|
||||||
|
#: club/models.py
|
||||||
|
msgid "The fontawesome class to use (e.g. `fa-brands fa-instagram`)"
|
||||||
|
msgstr ""
|
||||||
|
"La classe fontawesome à utiliser (par exemple `fa-brands fa-instagram`)"
|
||||||
|
|
||||||
|
#: club/models.py
|
||||||
|
msgid "link type"
|
||||||
|
msgstr "type de lien"
|
||||||
|
|
||||||
|
#: club/models.py
|
||||||
|
msgid "link types"
|
||||||
|
msgstr "types de lien"
|
||||||
|
|
||||||
|
#: club/models.py
|
||||||
|
msgid "link url"
|
||||||
|
msgstr "url du lien"
|
||||||
|
|
||||||
|
#: club/models.py core/models.py counter/models.py
|
||||||
|
msgid "created at"
|
||||||
|
msgstr "créé le"
|
||||||
|
|
||||||
|
#: club/models.py core/models.py counter/models.py
|
||||||
|
msgid "updated at"
|
||||||
|
msgstr "mis à jour le"
|
||||||
|
|
||||||
|
#: club/models.py
|
||||||
|
msgid "club link"
|
||||||
|
msgstr "lien de club"
|
||||||
|
|
||||||
|
#: club/models.py
|
||||||
|
msgid "club links"
|
||||||
|
msgstr "liens de club"
|
||||||
|
|
||||||
|
#: club/models.py
|
||||||
|
msgid "This link doesn't match with the url base of its type."
|
||||||
|
msgstr "Ce lien ne correspond pas à l'url de base de son type."
|
||||||
|
|
||||||
|
#: club/templates/club/club_detail.jinja com/templates/com/news_list.jinja
|
||||||
|
msgid "Links"
|
||||||
|
msgstr "Liens"
|
||||||
|
|
||||||
#: club/templates/club/club_list.jinja
|
#: club/templates/club/club_list.jinja
|
||||||
msgid "The list of all clubs existing at UTBM."
|
msgid "The list of all clubs existing at UTBM."
|
||||||
msgstr "La liste de tous les clubs existants à l'UTBM"
|
msgstr "La liste de tous les clubs existants à l'UTBM"
|
||||||
@@ -591,6 +643,10 @@ msgstr "Comptoirs : "
|
|||||||
msgid "Edit %(name)s"
|
msgid "Edit %(name)s"
|
||||||
msgstr "Éditer %(name)s"
|
msgstr "Éditer %(name)s"
|
||||||
|
|
||||||
|
#: club/templates/club/edit_club.jinja
|
||||||
|
msgid "Remove link"
|
||||||
|
msgstr "Retirer le lien"
|
||||||
|
|
||||||
#: club/templates/club/edit_club.jinja
|
#: club/templates/club/edit_club.jinja
|
||||||
msgid "Club properties"
|
msgid "Club properties"
|
||||||
msgstr "Propriétés du club"
|
msgstr "Propriétés du club"
|
||||||
@@ -615,6 +671,22 @@ msgstr ""
|
|||||||
"Les champs de formulaire suivants sont liées à la description basique d'un "
|
"Les champs de formulaire suivants sont liées à la description basique d'un "
|
||||||
"club. Tous les membres du bureau du club peuvent voir et modifier ceux-ci."
|
"club. Tous les membres du bureau du club peuvent voir et modifier ceux-ci."
|
||||||
|
|
||||||
|
#: club/templates/club/edit_club.jinja
|
||||||
|
msgid "Club links"
|
||||||
|
msgstr "Liens du club"
|
||||||
|
|
||||||
|
#: club/templates/club/edit_club.jinja
|
||||||
|
msgid ""
|
||||||
|
"Note: if the icon of one of your links doesn't exist yet, you can ask the "
|
||||||
|
"info team to add it."
|
||||||
|
msgstr ""
|
||||||
|
"Note : si l'icône d'un de vos liens n'existe pas encore, vous pouvez demander "
|
||||||
|
"au pôle info de l'ajouter."
|
||||||
|
|
||||||
|
#: club/templates/club/edit_club.jinja
|
||||||
|
msgid "Add link"
|
||||||
|
msgstr "Ajouter un lien"
|
||||||
|
|
||||||
#: club/templates/club/edit_club.jinja club/templates/club/pagerev_edit.jinja
|
#: club/templates/club/edit_club.jinja club/templates/club/pagerev_edit.jinja
|
||||||
#: com/templates/com/news_edit.jinja com/templates/com/poster_edit.jinja
|
#: com/templates/com/news_edit.jinja com/templates/com/poster_edit.jinja
|
||||||
#: com/templates/com/screen_edit.jinja com/templates/com/weekmail.jinja
|
#: com/templates/com/screen_edit.jinja com/templates/com/weekmail.jinja
|
||||||
@@ -1139,10 +1211,6 @@ msgstr ""
|
|||||||
msgid "All coming events"
|
msgid "All coming events"
|
||||||
msgstr "Tous les événements à venir"
|
msgstr "Tous les événements à venir"
|
||||||
|
|
||||||
#: com/templates/com/news_list.jinja
|
|
||||||
msgid "Links"
|
|
||||||
msgstr "Liens"
|
|
||||||
|
|
||||||
#: com/templates/com/news_list.jinja
|
#: com/templates/com/news_list.jinja
|
||||||
msgid "Our services"
|
msgid "Our services"
|
||||||
msgstr "Nos services"
|
msgstr "Nos services"
|
||||||
@@ -1648,10 +1716,6 @@ msgstr "Visiteur"
|
|||||||
msgid "ban type"
|
msgid "ban type"
|
||||||
msgstr "type de ban"
|
msgstr "type de ban"
|
||||||
|
|
||||||
#: core/models.py counter/models.py
|
|
||||||
msgid "created at"
|
|
||||||
msgstr "créé le"
|
|
||||||
|
|
||||||
#: core/models.py
|
#: core/models.py
|
||||||
msgid "expires at"
|
msgid "expires at"
|
||||||
msgstr "expire le"
|
msgstr "expire le"
|
||||||
@@ -1741,10 +1805,6 @@ msgstr "taille"
|
|||||||
msgid "date"
|
msgid "date"
|
||||||
msgstr "date"
|
msgstr "date"
|
||||||
|
|
||||||
#: core/models.py counter/models.py
|
|
||||||
msgid "updated at"
|
|
||||||
msgstr "mis à jour le"
|
|
||||||
|
|
||||||
#: core/models.py
|
#: core/models.py
|
||||||
msgid "asked for removal"
|
msgid "asked for removal"
|
||||||
msgstr "retrait demandé"
|
msgstr "retrait demandé"
|
||||||
@@ -3219,10 +3279,6 @@ msgstr "prix d'achat"
|
|||||||
msgid "Initial cost of purchasing the product"
|
msgid "Initial cost of purchasing the product"
|
||||||
msgstr "Coût initial d'achat du produit"
|
msgstr "Coût initial d'achat du produit"
|
||||||
|
|
||||||
#: counter/models.py
|
|
||||||
msgid "icon"
|
|
||||||
msgstr "icône"
|
|
||||||
|
|
||||||
#: counter/models.py
|
#: counter/models.py
|
||||||
msgid "limit age"
|
msgid "limit age"
|
||||||
msgstr "âge limite"
|
msgstr "âge limite"
|
||||||
|
|||||||
@@ -88,6 +88,11 @@ X_FRAME_OPTIONS = "SAMEORIGIN"
|
|||||||
|
|
||||||
ALLOWED_HOSTS = ["*"]
|
ALLOWED_HOSTS = ["*"]
|
||||||
|
|
||||||
|
# RemovedInDjango60Warning: It's a transitional setting helpful in early
|
||||||
|
# adoption of "https" as the new default value of forms.URLField.assume_scheme.
|
||||||
|
# Remove this after upgrading to Django 6.x
|
||||||
|
FORMS_URLFIELD_ASSUME_HTTPS = True
|
||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
|
|
||||||
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
|
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
|
||||||
|
|||||||
Reference in New Issue
Block a user