Merge 91d976855b15226f134beffb095126d927c5bd73 into bb3dfb7e8a87e4c4ca61d2ee095bb6c3f7ffc115

This commit is contained in:
thomas girod 2025-03-14 12:26:17 +00:00 committed by GitHub
commit 51de1bef4f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 502 additions and 505 deletions

View File

@ -19,8 +19,8 @@ from club.models import Club, Membership
@admin.register(Club) @admin.register(Club)
class ClubAdmin(admin.ModelAdmin): class ClubAdmin(admin.ModelAdmin):
list_display = ("name", "unix_name", "parent", "is_active") list_display = ("name", "slug_name", "parent", "is_active")
search_fields = ("name", "unix_name") search_fields = ("name", "slug_name")
autocomplete_fields = ( autocomplete_fields = (
"parent", "parent",
"board_group", "board_group",

View File

@ -34,13 +34,20 @@ from counter.models import Counter
class ClubEditForm(forms.ModelForm): class ClubEditForm(forms.ModelForm):
error_css_class = "error"
required_css_class = "required"
class Meta: class Meta:
model = Club model = Club
fields = ["address", "logo", "short_description"] fields = ["address", "logo", "short_description"]
widgets = {"short_description": forms.Textarea()}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) class ClubAdminEditForm(ClubEditForm):
self.fields["short_description"].widget = forms.Textarea() admin_fields = ["name", "parent", "is_active"]
class Meta(ClubEditForm.Meta):
fields = ["name", "parent", "is_active", *ClubEditForm.Meta.fields]
class MailingForm(forms.Form): class MailingForm(forms.Form):

View File

@ -1,10 +1,9 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import django.db.models.deletion import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models from django.db import migrations, models
import club.models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [("club", "0010_auto_20170912_2028")] dependencies = [("club", "0010_auto_20170912_2028")]
@ -15,7 +14,7 @@ class Migration(migrations.Migration):
name="owner_group", name="owner_group",
field=models.ForeignKey( field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, on_delete=django.db.models.deletion.CASCADE,
default=club.models.get_default_owner_group, default=lambda: settings.SITH_ROOT_USER_ID,
related_name="owned_club", related_name="owned_club",
to="core.Group", to="core.Group",
), ),

View File

@ -0,0 +1,75 @@
# Generated by Django 4.2.17 on 2025-02-28 20:34
import django.db.models.deletion
from django.db import migrations, models
import core.fields
class Migration(migrations.Migration):
dependencies = [
("core", "0044_alter_userban_options"),
("club", "0013_alter_club_board_group_alter_club_members_group_and_more"),
]
operations = [
migrations.AlterModelOptions(name="club", options={"ordering": ["name"]}),
migrations.RenameField(
model_name="club",
old_name="unix_name",
new_name="slug_name",
),
migrations.AlterField(
model_name="club",
name="name",
field=models.CharField(unique=True, max_length=64, verbose_name="name"),
),
migrations.AlterField(
model_name="club",
name="slug_name",
field=models.SlugField(
editable=False, max_length=30, unique=True, verbose_name="slug name"
),
),
migrations.AlterField(
model_name="club",
name="id",
field=models.AutoField(
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
),
),
migrations.AlterField(
model_name="club",
name="logo",
field=core.fields.ResizedImageField(
blank=True,
force_format="WEBP",
height=200,
null=True,
upload_to="club_logos",
verbose_name="logo",
width=200,
),
),
migrations.AlterField(
model_name="club",
name="page",
field=models.OneToOneField(
blank=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="club",
to="core.page",
),
),
migrations.AlterField(
model_name="club",
name="short_description",
field=models.CharField(
blank=True,
default="",
help_text="A summary of what your club does. This will be displayed on the club list page.",
max_length=1000,
verbose_name="short description",
),
),
]

View File

@ -26,7 +26,6 @@ from __future__ import annotations
from typing import Iterable, Self from typing import Iterable, Self
from django.conf import settings from django.conf import settings
from django.core import validators
from django.core.cache import cache from django.core.cache import cache
from django.core.exceptions import ObjectDoesNotExist, ValidationError from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.core.validators import RegexValidator, validate_email from django.core.validators import RegexValidator, validate_email
@ -35,48 +34,43 @@ from django.db.models import Exists, F, OuterRef, Q
from django.urls import reverse from django.urls import reverse
from django.utils import timezone from django.utils import timezone
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.text import slugify
from django.utils.timezone import localdate from django.utils.timezone import localdate
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from core.fields import ResizedImageField
from core.models import Group, Notification, Page, SithFile, User from core.models import Group, Notification, Page, SithFile, User
# Create your models here.
# This function prevents generating migration upon settings change
def get_default_owner_group():
return settings.SITH_GROUP_ROOT_ID
class Club(models.Model): class Club(models.Model):
"""The Club class, made as a tree to allow nice tidy organization.""" """The Club class, made as a tree to allow nice tidy organization."""
id = models.AutoField(primary_key=True, db_index=True) name = models.CharField(_("name"), unique=True, max_length=64)
name = models.CharField(_("name"), max_length=64)
parent = models.ForeignKey( parent = models.ForeignKey(
"Club", related_name="children", null=True, blank=True, on_delete=models.CASCADE "Club", related_name="children", null=True, blank=True, on_delete=models.CASCADE
) )
unix_name = models.CharField( slug_name = models.SlugField(
_("unix name"), _("slug name"), max_length=30, unique=True, editable=False
max_length=30,
unique=True,
validators=[
validators.RegexValidator(
r"^[a-z0-9][a-z0-9._-]*[a-z0-9]$",
_(
"Enter a valid unix name. This value may contain only "
"letters, numbers ./-/_ characters."
),
)
],
error_messages={"unique": _("A club with that unix name already exists.")},
) )
logo = models.ImageField( logo = ResizedImageField(
upload_to="club_logos", verbose_name=_("logo"), null=True, blank=True upload_to="club_logos",
verbose_name=_("logo"),
null=True,
blank=True,
force_format="WEBP",
height=200,
width=200,
) )
is_active = models.BooleanField(_("is active"), default=True) is_active = models.BooleanField(_("is active"), default=True)
short_description = models.CharField( short_description = models.CharField(
_("short description"), max_length=1000, default="", blank=True, null=True _("short description"),
max_length=1000,
default="",
blank=True,
help_text=_(
"A summary of what your club does. "
"This will be displayed on the club list page."
),
) )
address = models.CharField(_("address"), max_length=254) address = models.CharField(_("address"), max_length=254)
home = models.OneToOneField( home = models.OneToOneField(
@ -88,7 +82,7 @@ class Club(models.Model):
on_delete=models.SET_NULL, on_delete=models.SET_NULL,
) )
page = models.OneToOneField( page = models.OneToOneField(
Page, related_name="club", blank=True, null=True, on_delete=models.CASCADE Page, related_name="club", blank=True, on_delete=models.CASCADE
) )
members_group = models.OneToOneField( members_group = models.OneToOneField(
Group, related_name="club", on_delete=models.PROTECT Group, related_name="club", on_delete=models.PROTECT
@ -98,7 +92,7 @@ class Club(models.Model):
) )
class Meta: class Meta:
ordering = ["name", "unix_name"] ordering = ["name"]
def __str__(self): def __str__(self):
return self.name return self.name
@ -106,10 +100,12 @@ class Club(models.Model):
@transaction.atomic() @transaction.atomic()
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
creation = self._state.adding creation = self._state.adding
if (slug := slugify(self.name)[:30]) != self.slug_name:
self.slug_name = slug
if not creation: if not creation:
db_club = Club.objects.get(id=self.id) db_club = Club.objects.get(id=self.id)
if self.unix_name != db_club.unix_name: if self.name != db_club.name:
self.home.name = self.unix_name self.home.name = self.slug_name
self.home.save() self.home.save()
if self.name != db_club.name: if self.name != db_club.name:
self.board_group.name = f"{self.name} - Bureau" self.board_group.name = f"{self.name} - Bureau"
@ -123,11 +119,9 @@ class Club(models.Model):
self.members_group = Group.objects.create( self.members_group = Group.objects.create(
name=f"{self.name} - Membres", is_manually_manageable=False name=f"{self.name} - Membres", is_manually_manageable=False
) )
super().save(*args, **kwargs)
if creation:
self.make_home() self.make_home()
self.make_page() self.make_page()
cache.set(f"sith_club_{self.unix_name}", self) super().save(*args, **kwargs)
def get_absolute_url(self): def get_absolute_url(self):
return reverse("club:club_view", kwargs={"club_id": self.id}) return reverse("club:club_view", kwargs={"club_id": self.id})
@ -155,49 +149,37 @@ class Club(models.Model):
def make_home(self) -> None: def make_home(self) -> None:
if self.home: if self.home:
return return
home_root = SithFile.objects.filter(parent=None, name="clubs").first() home_root = SithFile.objects.get(parent=None, name="clubs")
root = User.objects.filter(username="root").first() root = User.objects.get(id=settings.SITH_ROOT_USER_ID)
if home_root and root: self.home = SithFile.objects.create(
home = SithFile(parent=home_root, name=self.unix_name, owner=root) parent=home_root, name=self.slug_name, owner=root
home.save() )
self.home = home
self.save()
def make_page(self) -> None: def make_page(self) -> None:
root = User.objects.filter(username="root").first() page_name = self.slug_name
if not self.page: if not self.page_id:
club_root = Page.objects.filter(name=settings.SITH_CLUB_ROOT_PAGE).first() # Club.page is a OneToOneField, so if we are inside this condition
if root and club_root: # then self._meta.state.adding is True.
public = Group.objects.filter(id=settings.SITH_GROUP_PUBLIC_ID).first() club_root = Page.objects.get(name=settings.SITH_CLUB_ROOT_PAGE)
p = Page(name=self.unix_name) public = Group.objects.get(id=settings.SITH_GROUP_PUBLIC_ID)
p.parent = club_root p = Page(name=page_name, parent=club_root)
p.save(force_lock=True) p.save(force_lock=True)
if public: p.view_groups.add(public)
p.view_groups.add(public) if self.parent and self.parent.page_id:
p.save(force_lock=True) p.parent_id = self.parent.page_id
if self.parent and self.parent.page: self.page = p
p.parent = self.parent.page return
self.page = p self.page.unset_lock()
self.save() if self.page.name != page_name:
elif self.page and self.page.name != self.unix_name: self.page.name = page_name
self.page.unset_lock() elif self.parent and self.parent.page and self.page.parent != self.parent.page:
self.page.name = self.unix_name
self.page.save(force_lock=True)
elif (
self.page
and self.parent
and self.parent.page
and self.page.parent != self.parent.page
):
self.page.unset_lock()
self.page.parent = self.parent.page self.page.parent = self.parent.page
self.page.save(force_lock=True) self.page.save(force_lock=True)
def delete(self, *args, **kwargs) -> tuple[int, dict[str, int]]: def delete(self, *args, **kwargs) -> tuple[int, dict[str, int]]:
# Invalidate the cache of this club and of its memberships # Invalidate the cache of this club and of its memberships
for membership in self.members.ongoing().select_related("user"): for membership in self.members.ongoing().select_related("user"):
cache.delete(f"membership_{self.id}_{membership.user.id}") cache.delete(f"membership_{self.id}_{membership.user.id}")
cache.delete(f"sith_club_{self.unix_name}")
self.board_group.delete() self.board_group.delete()
self.members_group.delete() self.members_group.delete()
return super().delete(*args, **kwargs) return super().delete(*args, **kwargs)

View File

@ -4,7 +4,7 @@
{% block content %} {% block content %}
<div id="club_detail"> <div id="club_detail">
{% if club.logo %} {% if club.logo %}
<div class="club_logo"><img src="{{ club.logo.url }}" alt="{{ club.unix_name }}"></div> <div class="club_logo"><img src="{{ club.logo.url }}" alt="{{ club.name }}"></div>
{% endif %} {% endif %}
{% if page_revision %} {% if page_revision %}
{{ page_revision|markdown }} {{ page_revision|markdown }}

View File

@ -16,7 +16,7 @@
</ul> </ul>
<h4>{% trans %}Counters:{% endtrans %}</h4> <h4>{% trans %}Counters:{% endtrans %}</h4>
<ul> <ul>
{% if object.unix_name == settings.SITH_LAUNDERETTE_MANAGER['unix_name'] %} {% if object.id == settings.SITH_LAUNDERETTE_CLUB_ID %}
{% for l in Launderette.objects.all() %} {% for l in Launderette.objects.all() %}
<li><a href="{{ url('launderette:main_click', launderette_id=l.id) }}">{{ l }}</a></li> <li><a href="{{ url('launderette:main_click', launderette_id=l.id) }}">{{ l }}</a></li>
{% endfor %} {% endfor %}
@ -37,7 +37,7 @@
{% endfor %} {% endfor %}
</ul> </ul>
{% endif %} {% endif %}
{% if object.unix_name == settings.SITH_LAUNDERETTE_MANAGER['unix_name'] %} {% if object.id == settings.SITH_LAUNDERETTE_CLUB_ID %}
<li><a href="{{ url('launderette:launderette_list') }}">{% trans %}Manage launderettes{% endtrans %}</a></li> <li><a href="{{ url('launderette:launderette_list') }}">{% trans %}Manage launderettes{% endtrans %}</a></li>
{% endif %} {% endif %}
</div> </div>

View File

@ -0,0 +1,54 @@
{% extends "core/base.jinja" %}
{% block title %}
{% trans name=object %}Edit {{ name }}{% endtrans %}
{% endblock %}
{% block content %}
<h2>{% trans name=object %}Edit {{ name }}{% endtrans %}</h2>
<form action="" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form.non_field_errors() }}
{% if form.admin_fields %}
{# If the user is admin, display the admin fields,
and explicitly separate them from the non-admin ones,
with some help text.
Non-admin users will only see the regular form fields,
so they don't need thoses explanations #}
<h3>{% trans %}Club properties{% endtrans %}</h3>
<p class="helptext">
{% trans trimmed %}
The following form fields are linked to the core properties of a club.
Only admin users can see and edit them.
{% endtrans %}
</p>
<fieldset class="required margin-bottom">
{% for field_name in form.admin_fields %}
{% set field = form.pop(field_name) %}
<div class="form-group">
{{ field.errors }}
{{ field.label_tag() }}
{{ field }}
</div>
{# Remove the the admin fields from the form.
The remaining non-admin fields will be rendered
at once with a simple {{ form.as_p() }} #}
{% set _ = form.fields.pop(field_name) %}
{% endfor %}
</fieldset>
<h3>{% trans %}Club informations{% endtrans %}</h3>
<p class="helptext">
{% trans trimmed %}
The following form fields are linked to the basic description of a club.
All board members of this club can see and edit them.
{% endtrans %}
</p>
{% endif %}
{{ form.as_p() }}
<p><input type="submit" value="{% trans %}Save{% endtrans %}" /></p>
</form>
{% endblock content %}

View File

@ -1,49 +0,0 @@
{% extends "core/base.jinja" %}
{% block title %}
{% trans %}Club stats{% endtrans %}
{% endblock %}
{% block content %}
{% if club_list %}
<h3>{% trans %}Club stats{% endtrans %}</h3>
<form action="" method="GET">
{% csrf_token %}
<p>
<select name="branch">
{% for b in settings.SITH_PROFILE_DEPARTMENTS %}
<option value="{{ b[0] }}">{{ b[0] }}</option>
{% endfor %}
</select>
</p>
<p><input type="submit" value="{% trans %}Show{% endtrans %}" /></p>
</form>
<table>
<thead>
<tr>
<td>Club</td>
<td>Member number</td>
<td>Old member number</td>
</tr>
</thead>
<tbody>
{% for c in club_list.order_by('id') %}
{% set members = c.members.all() %}
{% if request.GET['branch'] %}
{% set members = members.filter(user__department=request.GET['branch']) %}
{% endif %}
<tr>
<td>{{ c.get_display_name() }}</td>
<td>{{ members.filter(end_date=None, role__gt=settings.SITH_MAXIMUM_FREE_ROLE).count() }}</td>
<td>{{ members.exclude(end_date=None, role__gt=settings.SITH_MAXIMUM_FREE_ROLE).count() }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
{% trans %}There is no club in this website.{% endtrans %}
{% endif %}
{% endblock %}

View File

@ -14,20 +14,21 @@
# #
from datetime import timedelta from datetime import timedelta
import pytest
from django.conf import settings from django.conf import settings
from django.core.cache import cache from django.core.cache import cache
from django.test import TestCase from django.test import Client, TestCase
from django.urls import reverse from django.urls import reverse
from django.utils import timezone from django.utils import timezone
from django.utils.timezone import localdate, localtime, now from django.utils.timezone import localdate, localtime, now
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from model_bakery import baker from model_bakery import baker
from pytest_django.asserts import assertRedirects
from club.forms import MailingForm from club.forms import MailingForm
from club.models import Club, Mailing, Membership from club.models import Club, Mailing, Membership
from core.baker_recipes import subscriber_user from core.baker_recipes import subscriber_user
from core.models import AnonymousUser, User from core.models import AnonymousUser, User
from sith.settings import SITH_BAR_MANAGER, SITH_MAIN_CLUB_ID
class TestClub(TestCase): class TestClub(TestCase):
@ -64,12 +65,8 @@ class TestClub(TestCase):
# not subscribed # not subscribed
cls.public = User.objects.get(username="public") cls.public = User.objects.get(username="public")
cls.ae = Club.objects.filter(pk=SITH_MAIN_CLUB_ID)[0] cls.ae = Club.objects.get(pk=settings.SITH_MAIN_CLUB_ID)
cls.club = Club.objects.create( cls.club = baker.make(Club)
name="Fake Club",
unix_name="fake-club",
address="5 rue de la République, 90000 Belfort",
)
cls.members_url = reverse("club:club_members", kwargs={"club_id": cls.club.id}) cls.members_url = reverse("club:club_members", kwargs={"club_id": cls.club.id})
a_month_ago = now() - timedelta(days=30) a_month_ago = now() - timedelta(days=30)
yesterday = now() - timedelta(days=1) yesterday = now() - timedelta(days=1)
@ -579,13 +576,11 @@ class TestMailingForm(TestCase):
cls.krophil = User.objects.get(username="krophil") cls.krophil = User.objects.get(username="krophil")
cls.comunity = User.objects.get(username="comunity") cls.comunity = User.objects.get(username="comunity")
cls.root = User.objects.get(username="root") cls.root = User.objects.get(username="root")
cls.bdf = Club.objects.get(unix_name=SITH_BAR_MANAGER["unix_name"]) cls.club = Club.objects.get(id=settings.SITH_PDF_CLUB_ID)
cls.mail_url = reverse("club:mailing", kwargs={"club_id": cls.bdf.id}) cls.mail_url = reverse("club:mailing", kwargs={"club_id": cls.club.id})
def setUp(self):
Membership( Membership(
user=self.rbatsbak, user=cls.rbatsbak,
club=self.bdf, club=cls.club,
start_date=timezone.now(), start_date=timezone.now(),
role=settings.SITH_CLUB_ROLES_ID["Board member"], role=settings.SITH_CLUB_ROLES_ID["Board member"],
).save() ).save()
@ -894,13 +889,32 @@ class TestClubSellingView(TestCase):
@classmethod @classmethod
def setUpTestData(cls): def setUpTestData(cls):
cls.ae = Club.objects.get(unix_name="ae") cls.club = baker.make(Club)
cls.skia = User.objects.get(username="skia") cls.admin = baker.make(User, is_superuser=True)
def test_page_not_internal_error(self): def test_page_not_internal_error(self):
"""Test that the page does not return and internal error.""" """Test that the page does not return and internal error."""
self.client.force_login(self.skia) self.client.force_login(self.admin)
response = self.client.get( response = self.client.get(
reverse("club:club_sellings", kwargs={"club_id": self.ae.id}) reverse("club:club_sellings", kwargs={"club_id": self.club.id})
) )
assert response.status_code == 200 assert response.status_code == 200
@pytest.mark.django_db
def test_club_board_member_cannot_edit_club_properties(client: Client):
user = subscriber_user.make()
club = baker.make(Club, name="old name", is_active=True, address="old address")
baker.make(Membership, club=club, user=user, role=7)
client.force_login(user)
res = client.post(
reverse("club:club_edit", kwargs={"club_id": club.id}),
{"name": "new name", "is_active": False, "address": "new address"},
)
# The request should success,
# but admin-only fields shouldn't be taken into account
assertRedirects(res, club.get_absolute_url())
club.refresh_from_db()
assert club.name == "old name"
assert club.is_active
assert club.address == "new address"

View File

@ -26,7 +26,6 @@ from django.urls import path
from club.views import ( from club.views import (
ClubCreateView, ClubCreateView,
ClubEditPropView,
ClubEditView, ClubEditView,
ClubListView, ClubListView,
ClubMailingView, ClubMailingView,
@ -37,7 +36,6 @@ from club.views import (
ClubRevView, ClubRevView,
ClubSellingCSVView, ClubSellingCSVView,
ClubSellingView, ClubSellingView,
ClubStatView,
ClubToolsView, ClubToolsView,
ClubView, ClubView,
MailingAutoGenerationView, MailingAutoGenerationView,
@ -54,7 +52,6 @@ from club.views import (
urlpatterns = [ urlpatterns = [
path("", ClubListView.as_view(), name="club_list"), path("", ClubListView.as_view(), name="club_list"),
path("new/", ClubCreateView.as_view(), name="club_new"), path("new/", ClubCreateView.as_view(), name="club_new"),
path("stats/", ClubStatView.as_view(), name="club_stats"),
path("<int:club_id>/", ClubView.as_view(), name="club_view"), path("<int:club_id>/", ClubView.as_view(), name="club_view"),
path( path(
"<int:club_id>/rev/<int:rev_id>/", ClubRevView.as_view(), name="club_view_rev" "<int:club_id>/rev/<int:rev_id>/", ClubRevView.as_view(), name="club_view_rev"
@ -72,7 +69,6 @@ urlpatterns = [
path( path(
"<int:club_id>/sellings/csv/", ClubSellingCSVView.as_view(), name="sellings_csv" "<int:club_id>/sellings/csv/", ClubSellingCSVView.as_view(), name="sellings_csv"
), ),
path("<int:club_id>/prop/", ClubEditPropView.as_view(), name="club_prop"),
path("<int:club_id>/tools/", ClubToolsView.as_view(), name="tools"), path("<int:club_id>/tools/", ClubToolsView.as_view(), name="tools"),
path("<int:club_id>/mailing/", ClubMailingView.as_view(), name="mailing"), path("<int:club_id>/mailing/", ClubMailingView.as_view(), name="mailing"),
path( path(

View File

@ -39,10 +39,16 @@ from django.urls import reverse, reverse_lazy
from django.utils import timezone from django.utils import timezone
from django.utils.translation import gettext as _t from django.utils.translation import gettext as _t
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views.generic import DetailView, ListView, TemplateView, View from django.views.generic import DetailView, ListView, View
from django.views.generic.edit import CreateView, DeleteView, UpdateView from django.views.generic.edit import CreateView, DeleteView, UpdateView
from club.forms import ClubEditForm, ClubMemberForm, MailingForm, SellingsForm from club.forms import (
ClubAdminEditForm,
ClubEditForm,
ClubMemberForm,
MailingForm,
SellingsForm,
)
from club.models import Club, Mailing, MailingSubscription, Membership from club.models import Club, Mailing, MailingSubscription, Membership
from com.views import ( from com.views import (
PosterCreateBaseView, PosterCreateBaseView,
@ -50,12 +56,7 @@ from com.views import (
PosterEditBaseView, PosterEditBaseView,
PosterListBaseView, PosterListBaseView,
) )
from core.auth.mixins import ( from core.auth.mixins import CanCreateMixin, CanEditMixin, CanViewMixin
CanCreateMixin,
CanEditMixin,
CanEditPropMixin,
CanViewMixin,
)
from core.models import PageRev from core.models import PageRev
from core.views import DetailFormView, PageEditViewBase from core.views import DetailFormView, PageEditViewBase
from core.views.mixins import TabedViewMixin from core.views.mixins import TabedViewMixin
@ -78,23 +79,23 @@ class ClubTabsMixin(TabedViewMixin):
} }
] ]
if self.request.user.can_view(self.object): if self.request.user.can_view(self.object):
tab_list.append( tab_list.extend(
{ [
"url": reverse( {
"club:club_members", kwargs={"club_id": self.object.id} "url": reverse(
), "club:club_members", kwargs={"club_id": self.object.id}
"slug": "members", ),
"name": _("Members"), "slug": "members",
} "name": _("Members"),
) },
tab_list.append( {
{ "url": reverse(
"url": reverse( "club:club_old_members", kwargs={"club_id": self.object.id}
"club:club_old_members", kwargs={"club_id": self.object.id} ),
), "slug": "elderlies",
"slug": "elderlies", "name": _("Old members"),
"name": _("Old members"), },
} ]
) )
if self.object.page: if self.object.page:
tab_list.append( tab_list.append(
@ -107,21 +108,23 @@ class ClubTabsMixin(TabedViewMixin):
} }
) )
if self.request.user.can_edit(self.object): if self.request.user.can_edit(self.object):
tab_list.append( tab_list.extend(
{ [
"url": reverse("club:tools", kwargs={"club_id": self.object.id}), {
"slug": "tools", "url": reverse(
"name": _("Tools"), "club:tools", kwargs={"club_id": self.object.id}
} ),
) "slug": "tools",
tab_list.append( "name": _("Tools"),
{ },
"url": reverse( {
"club:club_edit", kwargs={"club_id": self.object.id} "url": reverse(
), "club:club_edit", kwargs={"club_id": self.object.id}
"slug": "edit", ),
"name": _("Edit"), "slug": "edit",
} "name": _("Edit"),
},
]
) )
if self.object.page and self.request.user.can_edit(self.object.page): if self.object.page and self.request.user.can_edit(self.object.page):
tab_list.append( tab_list.append(
@ -134,40 +137,30 @@ class ClubTabsMixin(TabedViewMixin):
"name": _("Edit club page"), "name": _("Edit club page"),
} }
) )
tab_list.append( tab_list.extend(
{ [
"url": reverse( {
"club:club_sellings", kwargs={"club_id": self.object.id} "url": reverse(
), "club:club_sellings", kwargs={"club_id": self.object.id}
"slug": "sellings", ),
"name": _("Sellings"), "slug": "sellings",
} "name": _("Sellings"),
) },
tab_list.append( {
{ "url": reverse(
"url": reverse("club:mailing", kwargs={"club_id": self.object.id}), "club:mailing", kwargs={"club_id": self.object.id}
"slug": "mailing", ),
"name": _("Mailing list"), "slug": "mailing",
} "name": _("Mailing list"),
) },
tab_list.append( {
{ "url": reverse(
"url": reverse( "club:poster_list", kwargs={"club_id": self.object.id}
"club:poster_list", kwargs={"club_id": self.object.id} ),
), "slug": "posters",
"slug": "posters", "name": _("Posters list"),
"name": _("Posters list"), },
} ]
)
if self.request.user.is_owner(self.object):
tab_list.append(
{
"url": reverse(
"club:club_prop", kwargs={"club_id": self.object.id}
),
"slug": "props",
"name": _("Props"),
}
) )
return tab_list return tab_list
@ -189,8 +182,11 @@ class ClubView(ClubTabsMixin, DetailView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs = super().get_context_data(**kwargs) kwargs = super().get_context_data(**kwargs)
if self.object.page and self.object.page.revisions.exists(): kwargs["page_revision"] = (
kwargs["page_revision"] = self.object.page.revisions.last().content PageRev.objects.filter(page_id=self.object.page_id)
.order_by("-date")
.first()
)
return kwargs return kwargs
@ -452,23 +448,23 @@ class ClubSellingCSVView(ClubSellingView):
class ClubEditView(ClubTabsMixin, CanEditMixin, UpdateView): class ClubEditView(ClubTabsMixin, CanEditMixin, UpdateView):
"""Edit a Club's main informations (for the club's members).""" """Edit a Club.
Regular club board members will be able to edit the main infos
(like the logo and the description).
Admins will also be able to edit the club properties
(like the name and the parent club).
"""
model = Club model = Club
pk_url_kwarg = "club_id" pk_url_kwarg = "club_id"
form_class = ClubEditForm template_name = "club/edit_club.jinja"
template_name = "core/edit.jinja"
current_tab = "edit" current_tab = "edit"
def get_form_class(self):
class ClubEditPropView(ClubTabsMixin, CanEditPropMixin, UpdateView): if self.object.is_owned_by(self.request.user):
"""Edit the properties of a Club object (for the Sith admins).""" return ClubAdminEditForm
return ClubEditForm
model = Club
pk_url_kwarg = "club_id"
fields = ["name", "unix_name", "parent", "is_active"]
template_name = "core/edit.jinja"
current_tab = "props"
class ClubCreateView(PermissionRequiredMixin, CreateView): class ClubCreateView(PermissionRequiredMixin, CreateView):
@ -476,8 +472,8 @@ class ClubCreateView(PermissionRequiredMixin, CreateView):
model = Club model = Club
pk_url_kwarg = "club_id" pk_url_kwarg = "club_id"
fields = ["name", "unix_name", "parent"] fields = ["name", "parent"]
template_name = "core/edit.jinja" template_name = "core/create.jinja"
permission_required = "club.add_club" permission_required = "club.add_club"
@ -522,15 +518,6 @@ class MembershipDeleteView(PermissionRequiredMixin, DeleteView):
return reverse_lazy("core:user_clubs", kwargs={"user_id": self.object.user.id}) return reverse_lazy("core:user_clubs", kwargs={"user_id": self.object.user.id})
class ClubStatView(TemplateView):
template_name = "club/stats.jinja"
def get_context_data(self, **kwargs):
kwargs = super().get_context_data(**kwargs)
kwargs["club_list"] = Club.objects.all()
return kwargs
class ClubMailingView(ClubTabsMixin, CanEditMixin, DetailFormView): class ClubMailingView(ClubTabsMixin, CanEditMixin, DetailFormView):
"""A list of mailing for a given club.""" """A list of mailing for a given club."""
@ -542,26 +529,19 @@ class ClubMailingView(ClubTabsMixin, CanEditMixin, DetailFormView):
def get_form_kwargs(self): def get_form_kwargs(self):
kwargs = super().get_form_kwargs() kwargs = super().get_form_kwargs()
kwargs["club_id"] = self.get_object().id kwargs["club_id"] = self.object.id
kwargs["user_id"] = self.request.user.id kwargs["user_id"] = self.request.user.id
kwargs["mailings"] = self.mailings kwargs["mailings"] = self.object.mailings.all()
return kwargs return kwargs
def dispatch(self, request, *args, **kwargs):
self.mailings = Mailing.objects.filter(club_id=self.get_object().id).all()
return super().dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs = super().get_context_data(**kwargs) kwargs = super().get_context_data(**kwargs)
kwargs["club"] = self.get_object() mailings = list(self.object.mailings.all())
kwargs["club"] = self.object
kwargs["user"] = self.request.user kwargs["user"] = self.request.user
kwargs["mailings"] = self.mailings kwargs["mailings"] = mailings
kwargs["mailings_moderated"] = ( kwargs["mailings_moderated"] = [m for m in mailings if m.is_moderated]
kwargs["mailings"].exclude(is_moderated=False).all() kwargs["mailings_not_moderated"] = [m for m in mailings if not m.is_moderated]
)
kwargs["mailings_not_moderated"] = (
kwargs["mailings"].exclude(is_moderated=True).all()
)
kwargs["form_actions"] = { kwargs["form_actions"] = {
"NEW_MALING": self.form_class.ACTION_NEW_MAILING, "NEW_MALING": self.form_class.ACTION_NEW_MAILING,
"NEW_SUBSCRIPTION": self.form_class.ACTION_NEW_SUBSCRIPTION, "NEW_SUBSCRIPTION": self.form_class.ACTION_NEW_SUBSCRIPTION,
@ -572,7 +552,7 @@ class ClubMailingView(ClubTabsMixin, CanEditMixin, DetailFormView):
def add_new_mailing(self, cleaned_data) -> ValidationError | None: def add_new_mailing(self, cleaned_data) -> ValidationError | None:
"""Create a new mailing list from the form.""" """Create a new mailing list from the form."""
mailing = Mailing( mailing = Mailing(
club=self.get_object(), club=self.object,
email=cleaned_data["mailing_email"], email=cleaned_data["mailing_email"],
moderator=self.request.user, moderator=self.request.user,
is_moderated=False, is_moderated=False,
@ -649,7 +629,7 @@ class ClubMailingView(ClubTabsMixin, CanEditMixin, DetailFormView):
return resp return resp
def get_success_url(self, **kwargs): def get_success_url(self, **kwargs):
return reverse_lazy("club:mailing", kwargs={"club_id": self.get_object().id}) return reverse("club:mailing", kwargs={"club_id": self.object.id})
class MailingDeleteView(CanEditMixin, DeleteView): class MailingDeleteView(CanEditMixin, DeleteView):

View File

@ -120,10 +120,7 @@ class Command(BaseCommand):
club_root = SithFile.objects.create(name="clubs", owner=root) club_root = SithFile.objects.create(name="clubs", owner=root)
sas = SithFile.objects.create(name="SAS", owner=root) sas = SithFile.objects.create(name="SAS", owner=root)
main_club = Club.objects.create( main_club = Club.objects.create(
id=1, id=1, name="AE", address="6 Boulevard Anatole France, 90000 Belfort"
name=settings.SITH_MAIN_CLUB["name"],
unix_name=settings.SITH_MAIN_CLUB["unix_name"],
address=settings.SITH_MAIN_CLUB["address"],
) )
main_club.board_group.permissions.add( main_club.board_group.permissions.add(
*Permission.objects.filter( *Permission.objects.filter(
@ -131,16 +128,14 @@ class Command(BaseCommand):
) )
) )
bar_club = Club.objects.create( bar_club = Club.objects.create(
id=2, id=settings.SITH_PDF_CLUB_ID,
name=settings.SITH_BAR_MANAGER["name"], name="PdF",
unix_name=settings.SITH_BAR_MANAGER["unix_name"], address="6 Boulevard Anatole France, 90000 Belfort",
address=settings.SITH_BAR_MANAGER["address"],
) )
Club.objects.create( Club.objects.create(
id=84, id=settings.SITH_LAUNDERETTE_CLUB_ID,
name=settings.SITH_LAUNDERETTE_MANAGER["name"], name="Laverie",
unix_name=settings.SITH_LAUNDERETTE_MANAGER["unix_name"], address="6 Boulevard Anatole France, 90000 Belfort",
address=settings.SITH_LAUNDERETTE_MANAGER["address"],
) )
self.reset_index("club") self.reset_index("club")
@ -353,31 +348,17 @@ Welcome to the wiki page!
# Clubs # Clubs
Club.objects.create( Club.objects.create(
name="Bibo'UT", name="Bibo'UT", address="46 de la Boustifaille", parent=main_club
unix_name="bibout",
address="46 de la Boustifaille",
parent=main_club,
) )
guyut = Club.objects.create( guyut = Club.objects.create(
name="Guy'UT", name="Guy'UT", address="42 de la Boustifaille", parent=main_club
unix_name="guyut",
address="42 de la Boustifaille",
parent=main_club,
)
Club.objects.create(
name="Woenzel'UT", unix_name="woenzel", address="Woenzel", parent=guyut
) )
Club.objects.create(name="Woenzel'UT", address="Woenzel", parent=guyut)
troll = Club.objects.create( troll = Club.objects.create(
name="Troll Penché", name="Troll Penché", address="Terre Du Milieu", parent=main_club
unix_name="troll",
address="Terre Du Milieu",
parent=main_club,
) )
refound = Club.objects.create( refound = Club.objects.create(
name="Carte AE", name="Carte AE", address="Jamais imprimée", parent=main_club
unix_name="carte_ae",
address="Jamais imprimée",
parent=main_club,
) )
Membership.objects.create(user=skia, club=main_club, role=3) Membership.objects.create(user=skia, club=main_club, role=3)

View File

@ -64,12 +64,12 @@ class Command(BaseCommand):
) )
) )
self.make_club( self.make_club(
Club.objects.get(unix_name="ae"), Club.objects.get(id=settings.SITH_MAIN_CLUB_ID),
random.sample(subscribers_now, k=min(30, len(subscribers_now))), random.sample(subscribers_now, k=min(30, len(subscribers_now))),
random.sample(old_subscribers, k=min(60, len(old_subscribers))), random.sample(old_subscribers, k=min(60, len(old_subscribers))),
) )
self.make_club( self.make_club(
Club.objects.get(unix_name="troll"), Club.objects.get(name="Troll Penché"),
random.sample(subscribers_now, k=min(20, len(subscribers_now))), random.sample(subscribers_now, k=min(20, len(subscribers_now))),
random.sample(old_subscribers, k=min(80, len(old_subscribers))), random.sample(old_subscribers, k=min(80, len(old_subscribers))),
) )
@ -235,7 +235,7 @@ class Command(BaseCommand):
categories = list( categories = list(
ProductType.objects.filter(name__in=[c.name for c in categories]) ProductType.objects.filter(name__in=[c.name for c in categories])
) )
ae = Club.objects.get(unix_name="ae") ae = Club.objects.get(id=settings.SITH_MAIN_CLUB_ID)
other_clubs = random.sample(list(Club.objects.all()), k=3) other_clubs = random.sample(list(Club.objects.all()), k=3)
groups = list( groups = list(
Group.objects.filter(name__in=["Subscribers", "Old subscribers", "Public"]) Group.objects.filter(name__in=["Subscribers", "Old subscribers", "Public"])

View File

@ -421,13 +421,9 @@ class User(AbstractUser):
def is_launderette_manager(self): def is_launderette_manager(self):
from club.models import Club from club.models import Club
return ( return Club.objects.get(
Club.objects.filter( id=settings.SITH_LAUNDERETTE_CLUB_ID
unix_name=settings.SITH_LAUNDERETTE_MANAGER["unix_name"] ).get_membership_for(self)
)
.first()
.get_membership_for(self)
)
@cached_property @cached_property
def is_banned_alcohol(self) -> bool: def is_banned_alcohol(self) -> bool:

View File

@ -132,111 +132,104 @@
</div> </div>
</div> </div>
</main> </main>
{% if {% if user == profile or user.memberships.ongoing().exists() %}
user == profile {# if the user is member of a club, he can view the subscription state #}
or user.memberships.ongoing().exists() <hr>
or user.is_board_member {% if profile.is_subscribed %}
or user.is_in_group(name=settings.SITH_BAR_MANAGER_BOARD_GROUP) {% if user == profile or user.is_root or user.is_board_member %}
%} <div>
{# if the user is member of a club, he can view the subscription state #} {{ user_subscription(profile) }}
<hr> </div>
{% if profile.is_subscribed %}
{% if user == profile or user.is_root or user.is_board_member %}
<div>
{{ user_subscription(profile) }}
</div>
{% endif %}
{% if user == profile or user.is_root or user.is_board_member or user.is_launderette_manager %}
<div>
{# Shows tokens bought by the user #}
{{ show_tokens(profile) }}
{# Shows slots took by the user #}
{{ show_slots(profile) }}
</div>
{% endif %}
{% else %}
<div>
{% trans %}Not subscribed{% endtrans %}
{% if user.is_board_member %}
<a href="{{ url('subscription:subscription') }}?member={{ profile.id }}">
{% trans %}New subscription{% endtrans %}
</a>
{% endif %} {% endif %}
{% if user == profile or user.is_root or user.is_board_member or user.is_launderette_manager %}
<div>
{{ show_tokens(profile) }}
{{ show_slots(profile) }}
</div>
{% endif %}
{% else %}
<div>
{% trans %}Not subscribed{% endtrans %}
{% if user.is_board_member %}
<a href="{{ url('subscription:subscription') }}?member={{ profile.id }}">
{% trans %}New subscription{% endtrans %}
</a>
{% endif %}
{% endif %}
</div>
{% endif %} {% endif %}
</div> <br>
{% endif %} {% if profile.was_subscribed and (user == profile or user.has_perm("subscription.view_subscription")) %}
<br>
{% if profile.was_subscribed and (user == profile or user.has_perm("subscription.view_subscription")) %}
<div class="collapse" :class="{'shadow': collapsed}" x-data="{collapsed: false}" x-cloak>
<div class="collapse-header clickable" @click="collapsed = !collapsed">
<span class="collapse-header-text">
{% trans %}Subscription history{% endtrans %}
</span>
<span class="collapse-header-icon" :class="{'reverse': collapsed}">
<i class="fa fa-caret-down"></i>
</span>
</div>
<div class="collapse-body" x-show="collapsed" x-transition.scale.origin.top>
<table>
<thead>
<tr>
<th>{% trans %}Subscription start{% endtrans %}</th>
<th>{% trans %}Subscription end{% endtrans %}</th>
<th>{% trans %}Subscription type{% endtrans %}</th>
<th>{% trans %}Payment method{% endtrans %}</th>
</tr>
</thead>
{% for sub in profile.subscriptions.all() %}
<tr>
<td>{{ sub.subscription_start }}</td>
<td>{{ sub.subscription_end }}</td>
<td>{{ sub.subscription_type }}</td>
<td>{{ sub.get_payment_method_display() }}</td>
</tr>
{% endfor %}
</table>
</div>
</div>
<hr>
{% endif %}
<div>
{% if user.is_root or user.is_board_member %}
<form class="form-gifts" action="{{ url('core:user_gift_create', user_id=profile.id) }}" method="post">
{% csrf_token %}
{{ gift_form.label }}
{{ gift_form.user }}
<input type="submit" value="{% trans %}Give gift{% endtrans %}">
</form>
{% if profile.gifts.exists() %}
{% set gifts = profile.gifts.order_by("-date")|list %}
<br>
<div class="collapse" :class="{'shadow': collapsed}" x-data="{collapsed: false}" x-cloak> <div class="collapse" :class="{'shadow': collapsed}" x-data="{collapsed: false}" x-cloak>
<div class="collapse-header clickable" @click="collapsed = !collapsed"> <div class="collapse-header clickable" @click="collapsed = !collapsed">
<span class="collapse-header-text"> <span class="collapse-header-text">
{% trans %}Last given gift :{% endtrans %} {{ gifts[0] }} {% trans %}Subscription history{% endtrans %}
</span> </span>
<span class="collapse-header-icon" :class="{'reverse': collapsed}"> <span class="collapse-header-icon" :class="{'reverse': collapsed}">
<i class="fa fa-caret-down"></i> <i class="fa fa-caret-down"></i>
</span> </span>
</div> </div>
<div class="collapse-body" x-show="collapsed" x-transition.scale.origin.top> <div class="collapse-body" x-show="collapsed" x-transition.scale.origin.top>
<ul> <table>
{% for gift in gifts %} <thead>
<li>{{ gift }} <tr>
<a href="{{ url('core:user_gift_delete', user_id=profile.id, gift_id=gift.id) }}"> <th>{% trans %}Subscription start{% endtrans %}</th>
<i class="fa-solid fa-trash-can delete-action"></i> <th>{% trans %}Subscription end{% endtrans %}</th>
</a> <th>{% trans %}Subscription type{% endtrans %}</th>
</li> <th>{% trans %}Payment method{% endtrans %}</th>
</tr>
</thead>
{% for sub in profile.subscriptions.all() %}
<tr>
<td>{{ sub.subscription_start }}</td>
<td>{{ sub.subscription_end }}</td>
<td>{{ sub.subscription_type }}</td>
<td>{{ sub.get_payment_method_display() }}</td>
</tr>
{% endfor %} {% endfor %}
</ul> </table>
</div> </div>
{% else %} </div>
<em>{% trans %}No gift given yet{% endtrans %}</em> <hr>
{% endif %} {% endif %}
<div>
{% if user.is_root or user.is_board_member %}
<form class="form-gifts" action="{{ url('core:user_gift_create', user_id=profile.id) }}" method="post">
{% csrf_token %}
{{ gift_form.label }}
{{ gift_form.user }}
<input type="submit" value="{% trans %}Give gift{% endtrans %}">
</form>
{% if profile.gifts.exists() %}
{% set gifts = profile.gifts.order_by("-date")|list %}
<br>
<div class="collapse" :class="{'shadow': collapsed}" x-data="{collapsed: false}" x-cloak>
<div class="collapse-header clickable" @click="collapsed = !collapsed">
<span class="collapse-header-text">
{% trans %}Last given gift :{% endtrans %} {{ gifts[0] }}
</span>
<span class="collapse-header-icon" :class="{'reverse': collapsed}">
<i class="fa fa-caret-down"></i>
</span>
</div>
<div class="collapse-body" x-show="collapsed" x-transition.scale.origin.top>
<ul>
{% for gift in gifts %}
<li>{{ gift }}
<a href="{{ url('core:user_gift_delete', user_id=profile.id, gift_id=gift.id) }}">
<i class="fa-solid fa-trash-can delete-action"></i>
</a>
</li>
{% endfor %}
</ul>
</div>
{% else %}
<em>{% trans %}No gift given yet{% endtrans %}</em>
{% endif %}
</div>
{% endif %}
</div> </div>
{% endif %}
</div>
{% endblock %} {% endblock %}

View File

@ -251,17 +251,7 @@ class UserTabsMixin(TabedViewMixin):
if ( if (
hasattr(user, "customer") hasattr(user, "customer")
and user.customer and user.customer
and ( and (user == self.request.user or user.has_perm("counter.view_customer"))
user == self.request.user
or self.request.user.is_in_group(
pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID
)
or self.request.user.is_in_group(
name=settings.SITH_BAR_MANAGER["unix_name"]
+ settings.SITH_BOARD_SUFFIX
)
or self.request.user.is_root
)
): ):
tab_list.append( tab_list.append(
{ {
@ -370,12 +360,7 @@ class UserStatsView(UserTabsMixin, CanViewMixin, DetailView):
raise Http404 raise Http404
if not ( if not (
profile == request.user profile == request.user or request.user.has_perm("counter.view_customer")
or request.user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID)
or request.user.is_in_group(
name=settings.SITH_BAR_MANAGER["unix_name"] + settings.SITH_BOARD_SUFFIX
)
or request.user.is_root
): ):
raise PermissionDenied raise PermissionDenied
@ -599,14 +584,9 @@ class UserAccountBase(UserTabsMixin, DetailView):
current_tab = "account" current_tab = "account"
queryset = User.objects.select_related("customer") queryset = User.objects.select_related("customer")
def dispatch(self, request, *arg, **kwargs): # Manually validates the rights def dispatch(self, request, *arg, **kwargs):
if ( if kwargs.get("user_id") == request.user.id or request.user.has_perm(
kwargs.get("user_id") == request.user.id "counter.view_customer"
or request.user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID)
or request.user.is_in_group(
name=settings.SITH_BAR_MANAGER["unix_name"] + settings.SITH_BOARD_SUFFIX
)
or request.user.is_root
): ):
return super().dispatch(request, *arg, **kwargs) return super().dispatch(request, *arg, **kwargs)
raise PermissionDenied raise PermissionDenied

View File

@ -44,7 +44,6 @@ from core.fields import ResizedImageField
from core.models import Group, Notification, User from core.models import Group, Notification, User
from core.utils import get_start_of_semester from core.utils import get_start_of_semester
from counter.apps import PAYMENT_METHOD from counter.apps import PAYMENT_METHOD
from sith.settings import SITH_MAIN_CLUB
from subscription.models import Subscription from subscription.models import Subscription
@ -569,7 +568,7 @@ class Counter(models.Model):
if self.type != "BAR": if self.type != "BAR":
return False return False
# at least one of the barmen is in the AE board # at least one of the barmen is in the AE board
ae = Club.objects.get(unix_name=SITH_MAIN_CLUB["unix_name"]) ae = Club.objects.get(id=settings.SITH_MAIN_CLUB_ID)
return any(ae.get_membership_for(barman) for barman in self.barmen_list) return any(ae.get_membership_for(barman) for barman in self.barmen_list)
def get_top_barmen(self) -> QuerySet: def get_top_barmen(self) -> QuerySet:

View File

@ -783,7 +783,7 @@ class TestCounterStats(TestCase):
s = Selling( s = Selling(
label=barbar.name, label=barbar.name,
product=barbar, product=barbar,
club=Club.objects.get(name=settings.SITH_MAIN_CLUB["name"]), club=baker.make(Club),
counter=cls.counter, counter=cls.counter,
unit_price=2, unit_price=2,
seller=cls.skia, seller=cls.skia,

View File

@ -86,7 +86,7 @@ class Command(BaseCommand):
self.logger.info("The Galaxy is being populated by the Sith.") self.logger.info("The Galaxy is being populated by the Sith.")
self.logger.info("Cleaning old Galaxy population") self.logger.info("Cleaning old Galaxy population")
Club.objects.filter(unix_name__startswith="galaxy-").delete() Club.objects.filter(name__startswith="galaxy-").delete()
Group.objects.filter(name__startswith="galaxy-").delete() Group.objects.filter(name__startswith="galaxy-").delete()
Page.objects.filter(name__startswith="galaxy-").delete() Page.objects.filter(name__startswith="galaxy-").delete()
User.objects.filter(username__startswith="galaxy-").delete() User.objects.filter(username__startswith="galaxy-").delete()
@ -127,15 +127,19 @@ class Command(BaseCommand):
# the galaxy doesn't care about the club groups, # the galaxy doesn't care about the club groups,
# but it's necessary to add them nonetheless in order # but it's necessary to add them nonetheless in order
# not to break the integrity constraints # not to break the integrity constraints
pages = Page.objects.bulk_create(
[Page(name="page", _full_name="page") for _ in range(self.NB_CLUBS)]
)
self.clubs = Club.objects.bulk_create( self.clubs = Club.objects.bulk_create(
[ [
Club( Club(
unix_name=f"galaxy-club-{i}", name=f"galaxy-club-{i}",
name=f"club-{i}", slug_name=f"galaxy-club-{i}",
board_group=Group.objects.create(name=f"board {i}"), board_group=Group.objects.create(name=f"board {i}"),
members_group=Group.objects.create(name=f"members {i}"), members_group=Group.objects.create(name=f"members {i}"),
page=page,
) )
for i in range(self.NB_CLUBS) for i, page in enumerate(pages)
] ]
) )

View File

@ -47,16 +47,12 @@ class Launderette(models.Model):
"""Method to see if that object can be edited by the given user.""" """Method to see if that object can be edited by the given user."""
if user.is_anonymous: if user.is_anonymous:
return False return False
launderette_club = Club.objects.filter( launderette_club = Club.objects.get(id=settings.SITH_LAUNDERETTE_CLUB_ID)
unix_name=settings.SITH_LAUNDERETTE_MANAGER["unix_name"]
).first()
m = launderette_club.get_membership_for(user) m = launderette_club.get_membership_for(user)
return bool(m and m.role >= 9) return bool(m and m.role >= 9)
def can_be_edited_by(self, user): def can_be_edited_by(self, user):
launderette_club = Club.objects.filter( launderette_club = Club.objects.get(id=settings.SITH_LAUNDERETTE_CLUB_ID)
unix_name=settings.SITH_LAUNDERETTE_MANAGER["unix_name"]
).first()
m = launderette_club.get_membership_for(user) m = launderette_club.get_membership_for(user)
return bool(m and m.role >= 2) return bool(m and m.role >= 2)
@ -105,9 +101,7 @@ class Machine(models.Model):
"""Method to see if that object can be edited by the given user.""" """Method to see if that object can be edited by the given user."""
if user.is_anonymous: if user.is_anonymous:
return False return False
launderette_club = Club.objects.filter( launderette_club = Club.objects.get(id=settings.SITH_LAUNDERETTE_CLUB_ID)
unix_name=settings.SITH_LAUNDERETTE_MANAGER["unix_name"]
).first()
m = launderette_club.get_membership_for(user) m = launderette_club.get_membership_for(user)
return bool(m and m.role >= 9) return bool(m and m.role >= 9)
@ -154,9 +148,7 @@ class Token(models.Model):
"""Method to see if that object can be edited by the given user.""" """Method to see if that object can be edited by the given user."""
if user.is_anonymous: if user.is_anonymous:
return False return False
launderette_club = Club.objects.filter( launderette_club = Club.objects.get(id=settings.SITH_LAUNDERETTE_CLUB_ID)
unix_name=settings.SITH_LAUNDERETTE_MANAGER["unix_name"]
).first()
m = launderette_club.get_membership_for(user) m = launderette_club.get_membership_for(user)
return bool(m and m.role >= 9) return bool(m and m.role >= 9)

View File

@ -196,9 +196,7 @@ class LaunderetteCreateView(PermissionRequiredMixin, CreateView):
permission_required = "launderette.add_launderette" permission_required = "launderette.add_launderette"
def form_valid(self, form): def form_valid(self, form):
club = Club.objects.filter( club = Club.objects.get(id=settings.SITH_LAUNDERETTE_CLUB_ID)
unix_name=settings.SITH_LAUNDERETTE_MANAGER["unix_name"]
).first()
c = Counter(name=form.instance.name, club=club, type="OFFICE") c = Counter(name=form.instance.name, club=club, type="OFFICE")
c.save() c.save()
form.instance.counter = c form.instance.counter = c

View File

@ -6,7 +6,7 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-02-25 16:38+0100\n" "POT-Creation-Date: 2025-03-01 10:56+0100\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"
@ -656,11 +656,11 @@ msgid "Linked operation:"
msgstr "Opération liée : " msgstr "Opération liée : "
#: accounting/templates/accounting/operation_edit.jinja #: accounting/templates/accounting/operation_edit.jinja
#: com/templates/com/news_edit.jinja com/templates/com/poster_edit.jinja #: club/templates/club/edit_club.jinja com/templates/com/news_edit.jinja
#: com/templates/com/screen_edit.jinja com/templates/com/weekmail.jinja #: com/templates/com/poster_edit.jinja com/templates/com/screen_edit.jinja
#: core/templates/core/create.jinja core/templates/core/edit.jinja #: com/templates/com/weekmail.jinja core/templates/core/create.jinja
#: core/templates/core/file_edit.jinja core/templates/core/macros_pages.jinja #: core/templates/core/edit.jinja core/templates/core/file_edit.jinja
#: core/templates/core/page_prop.jinja #: core/templates/core/macros_pages.jinja core/templates/core/page_prop.jinja
#: core/templates/core/user_godfathers.jinja #: core/templates/core/user_godfathers.jinja
#: core/templates/core/user_godfathers_tree.jinja #: core/templates/core/user_godfathers_tree.jinja
#: core/templates/core/user_preferences.jinja #: core/templates/core/user_preferences.jinja
@ -882,20 +882,8 @@ msgid "You do not have the permission to do that"
msgstr "Vous n'avez pas la permission de faire cela" msgstr "Vous n'avez pas la permission de faire cela"
#: club/models.py #: club/models.py
msgid "unix name" msgid "slug name"
msgstr "nom unix" msgstr "nom slug"
#: club/models.py
msgid ""
"Enter a valid unix name. This value may contain only letters, numbers ./-/_ "
"characters."
msgstr ""
"Entrez un nom UNIX valide. Cette valeur peut contenir uniquement des "
"lettres, des nombres, et les caractères ./-/_"
#: club/models.py
msgid "A club with that unix name already exists."
msgstr "Un club avec ce nom UNIX existe déjà."
#: club/models.py #: club/models.py
msgid "logo" msgid "logo"
@ -909,6 +897,14 @@ msgstr "actif"
msgid "short description" msgid "short description"
msgstr "description courte" msgstr "description courte"
#: club/models.py
msgid ""
"A summary of what your club does. This will be displayed on the club list "
"page."
msgstr ""
"Un résumé des activités des activités de votre club. Ceci sera affiché sur "
"la page de la liste des clubs."
#: club/models.py core/models.py #: club/models.py core/models.py
msgid "address" msgid "address"
msgstr "Adresse" msgstr "Adresse"
@ -988,7 +984,7 @@ msgstr "inactif"
msgid "New club" msgid "New club"
msgstr "Nouveau club" msgstr "Nouveau club"
#: club/templates/club/club_list.jinja club/templates/club/stats.jinja #: club/templates/club/club_list.jinja
msgid "There is no club in this website." msgid "There is no club in this website."
msgstr "Il n'y a pas de club dans ce site web." msgstr "Il n'y a pas de club dans ce site web."
@ -1059,7 +1055,7 @@ msgstr "Suivant"
msgid "Sales" msgid "Sales"
msgstr "Ventes" msgstr "Ventes"
#: club/templates/club/club_sellings.jinja club/templates/club/stats.jinja #: club/templates/club/club_sellings.jinja
#: counter/templates/counter/cash_summary_list.jinja #: counter/templates/counter/cash_summary_list.jinja
msgid "Show" msgid "Show"
msgstr "Montrer" msgstr "Montrer"
@ -1160,6 +1156,35 @@ msgstr "Comptabilité : "
msgid "Manage launderettes" msgid "Manage launderettes"
msgstr "Gestion des laveries" msgstr "Gestion des laveries"
#: club/templates/club/edit_club.jinja core/templates/core/edit.jinja
#, python-format
msgid "Edit %(name)s"
msgstr "Éditer %(name)s"
#: club/templates/club/edit_club.jinja
msgid "Club properties"
msgstr "Propriétés du club"
#: club/templates/club/edit_club.jinja
msgid ""
"The following form fields are linked to the core properties of a club. Only "
"admin users can see and edit them."
msgstr ""
"Les champs de formulaire suivants sont liées aux propriétés essentielles d'un "
"club. Seuls les administrateurs peuvent voir et modifier ceux-ci."
#: club/templates/club/edit_club.jinja
msgid "Club informations"
msgstr "Informations du club"
#: club/templates/club/edit_club.jinja
msgid ""
"The following form fields are linked to the basic description of a club. All "
"board members of this club can see and edit them."
msgstr ""
"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/templates/club/mailing.jinja #: club/templates/club/mailing.jinja
msgid "Mailing lists" msgid "Mailing lists"
msgstr "Mailing listes" msgstr "Mailing listes"
@ -1218,10 +1243,6 @@ msgstr "Créer une liste de diffusion"
msgid "No page existing for this club" msgid "No page existing for this club"
msgstr "Aucune page n'existe pour ce club" msgstr "Aucune page n'existe pour ce club"
#: club/templates/club/stats.jinja
msgid "Club stats"
msgstr "Statistiques du club"
#: club/views.py #: club/views.py
msgid "Members" msgid "Members"
msgstr "Membres" msgstr "Membres"
@ -2427,11 +2448,6 @@ msgstr "Confirmation"
msgid "Cancel" msgid "Cancel"
msgstr "Annuler" msgstr "Annuler"
#: core/templates/core/edit.jinja
#, python-format
msgid "Edit %(name)s"
msgstr "Éditer %(name)s"
#: core/templates/core/file.jinja core/templates/core/file_list.jinja #: core/templates/core/file.jinja core/templates/core/file_list.jinja
msgid "File list" msgid "File list"
msgstr "Liste de fichiers" msgstr "Liste de fichiers"
@ -2837,6 +2853,7 @@ msgid "Users"
msgstr "Utilisateurs" msgstr "Utilisateurs"
#: core/templates/core/search.jinja core/views/user.py #: core/templates/core/search.jinja core/views/user.py
#: counter/templates/counter/product_list.jinja
msgid "Clubs" msgid "Clubs"
msgstr "Clubs" msgstr "Clubs"
@ -3182,7 +3199,7 @@ msgid "Bans"
msgstr "Bans" msgstr "Bans"
#: core/templates/core/user_tools.jinja counter/forms.py #: core/templates/core/user_tools.jinja counter/forms.py
#: counter/views/mixins.py #: counter/templates/counter/product_list.jinja counter/views/mixins.py
msgid "Counters" msgid "Counters"
msgstr "Comptoirs" msgstr "Comptoirs"

View File

@ -18,6 +18,7 @@ from django.conf import settings
from django.test import TestCase from django.test import TestCase
from django.urls import reverse from django.urls import reverse
from django.utils.timezone import localtime, now from django.utils.timezone import localtime, now
from model_bakery import baker
from club.models import Club from club.models import Club
from core.models import Group, User from core.models import Group, User
@ -28,7 +29,7 @@ from subscription.models import Subscription
class TestMergeUser(TestCase): class TestMergeUser(TestCase):
@classmethod @classmethod
def setUpTestData(cls): def setUpTestData(cls):
cls.ae = Club.objects.get(unix_name="ae") cls.club = baker.make(Club)
cls.eboutic = Counter.objects.get(name="Eboutic") cls.eboutic = Counter.objects.get(name="Eboutic")
cls.barbar = Product.objects.get(code="BARB") cls.barbar = Product.objects.get(code="BARB")
cls.barbar.selling_price = 2 cls.barbar.selling_price = 2
@ -97,7 +98,7 @@ class TestMergeUser(TestCase):
Selling( Selling(
label="barbar", label="barbar",
counter=self.eboutic, counter=self.eboutic,
club=self.ae, club=self.club,
product=self.barbar, product=self.barbar,
customer=self.to_keep.customer, customer=self.to_keep.customer,
seller=self.root, seller=self.root,
@ -108,7 +109,7 @@ class TestMergeUser(TestCase):
Selling( Selling(
label="barbar", label="barbar",
counter=self.eboutic, counter=self.eboutic,
club=self.ae, club=self.club,
product=self.barbar, product=self.barbar,
customer=self.to_delete.customer, customer=self.to_delete.customer,
seller=self.root, seller=self.root,
@ -180,7 +181,7 @@ class TestMergeUser(TestCase):
Selling( Selling(
label="barbar", label="barbar",
counter=self.eboutic, counter=self.eboutic,
club=self.ae, club=self.club,
product=self.barbar, product=self.barbar,
customer=self.to_delete.customer, customer=self.to_delete.customer,
seller=self.root, seller=self.root,
@ -208,7 +209,7 @@ class TestMergeUser(TestCase):
Selling( Selling(
label="barbar", label="barbar",
counter=self.eboutic, counter=self.eboutic,
club=self.ae, club=self.club,
product=self.barbar, product=self.barbar,
customer=self.to_keep.customer, customer=self.to_keep.customer,
seller=self.root, seller=self.root,

View File

@ -346,27 +346,9 @@ SITH_TWITTER = "@ae_utbm"
SITH_ENABLE_GALAXY = False SITH_ENABLE_GALAXY = False
# AE configuration # AE configuration
# TODO: keep only that first setting, with the ID, and do the same for the other clubs
SITH_MAIN_CLUB_ID = env.int("SITH_MAIN_CLUB_ID", default=1) SITH_MAIN_CLUB_ID = env.int("SITH_MAIN_CLUB_ID", default=1)
SITH_MAIN_CLUB = { SITH_PDF_CLUB_ID = env.int("SITH_PDF_CLUB_ID", default=2)
"name": "AE", SITH_LAUNDERETTE_CLUB_ID = env.int("SITH_LAUNDERETTE_CLUB_ID", default=84)
"unix_name": "ae",
"address": "6 Boulevard Anatole France, 90000 Belfort",
}
# Bar managers
SITH_BAR_MANAGER = {
"name": "Pdf",
"unix_name": "pdfesti",
"address": "6 Boulevard Anatole France, 90000 Belfort",
}
# Launderette managers
SITH_LAUNDERETTE_MANAGER = {
"name": "Laverie",
"unix_name": "laverie",
"address": "6 Boulevard Anatole France, 90000 Belfort",
}
# Main root for club pages # Main root for club pages
SITH_CLUB_ROOT_PAGE = "clubs" SITH_CLUB_ROOT_PAGE = "clubs"
@ -419,10 +401,6 @@ SITH_SAS_IMAGES_PER_PAGE = 60
SITH_BOARD_SUFFIX = "-bureau" SITH_BOARD_SUFFIX = "-bureau"
SITH_MEMBER_SUFFIX = "-membres" SITH_MEMBER_SUFFIX = "-membres"
SITH_MAIN_BOARD_GROUP = SITH_MAIN_CLUB["unix_name"] + SITH_BOARD_SUFFIX
SITH_MAIN_MEMBERS_GROUP = SITH_MAIN_CLUB["unix_name"] + SITH_MEMBER_SUFFIX
SITH_BAR_MANAGER_BOARD_GROUP = SITH_BAR_MANAGER["unix_name"] + SITH_BOARD_SUFFIX
SITH_PROFILE_DEPARTMENTS = [ SITH_PROFILE_DEPARTMENTS = [
("TC", _("TC")), ("TC", _("TC")),
("IMSI", _("IMSI")), ("IMSI", _("IMSI")),