Aller au contenu

Views

ClubRoleFormSet = forms.inlineformset_factory(Club, ClubRole, ClubRoleForm, ClubRoleBaseFormSet, can_delete=False, can_order=True, edit_only=True, extra=0) module-attribute

ClubAddMemberForm(*args, club, request_user, **kwargs)

Bases: ClubMemberForm

Form to add a member to the club, as a board member.

Source code in club/forms.py
def __init__(self, *args, club: Club, request_user: User, **kwargs):
    self.club = club
    self.request_user = request_user
    self.request_user_membership = self.club.get_membership_for(self.request_user)
    super().__init__(*args, **kwargs)
    self.fields["role"].queryset = self.available_roles
    self.instance.club = club

available_roles()

The roles that will be obtainable with this form.

Admins and the club president can attribute any role. Board members can attribute roles lower than their own. Other users cannot attribute roles with this form

Source code in club/forms.py
@cached_property
def available_roles(self):
    """The roles that will be obtainable with this form.

    Admins and the club president can attribute any role.
    Board members can attribute roles lower than their own.
    Other users cannot attribute roles with this form
    """
    qs = self.club.roles.filter(is_active=True)
    if self.request_user.has_perm("club.add_membership"):
        return qs.all()
    membership = self.request_user_membership
    if membership is None or not membership.role.is_board:
        return ClubRole.objects.none()
    if membership.role.is_presidency:
        return qs.all()
    return qs.above_instance(membership.role)

clean_user()

Check that the user is not trying to add a user already in the club.

Also check that the user is valid and has a valid subscription.

Source code in club/forms.py
def clean_user(self):
    """Check that the user is not trying to add a user already in the club.

    Also check that the user is valid and has a valid subscription.
    """
    user = self.cleaned_data["user"]
    if not user.is_subscribed:
        raise forms.ValidationError(
            _("User must be subscriber to take part to a club"), code="invalid"
        )
    if self.club.get_membership_for(user):
        raise forms.ValidationError(
            _("You can not add the same user twice"), code="invalid"
        )
    return user

ClubAdminEditForm(*args, prefix=None, instance=None, **kwargs)

Bases: ClubEditForm

Source code in club/forms.py
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
    )

ClubEditForm(*args, prefix=None, instance=None, **kwargs)

Bases: ModelForm

Source code in club/forms.py
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
    )

ClubOldMemberForm(*args, user, club, **kwargs)

Bases: Form

Source code in club/forms.py
def __init__(self, *args, user: User, club: Club, **kwargs):
    super().__init__(*args, **kwargs)
    self.fields["members_old"].queryset = club.members.ongoing().editable_by(user)

ClubRoleCreateForm(*args, club, is_presidency, is_board, **kwargs)

Bases: ModelForm

Form to create a club role.

Notes

For UX purposes, users are not meant to fill is_presidency and is_board, so those values are required by the form constructor in order to initialize the instance properly.

Source code in club/forms.py
def __init__(
    self, *args, club: Club, is_presidency: bool, is_board: bool, **kwargs
):
    super().__init__(*args, **kwargs)
    self.instance.club = club
    self.instance.is_presidency = is_presidency
    self.instance.is_board = is_board

ClubSearchForm(*args, data=None, **kwargs)

Bases: ModelForm

Source code in club/forms.py
def __init__(self, *args, data: dict | None = None, **kwargs):
    super().__init__(*args, data=data, **kwargs)
    if data is not None and "club_status" not in data:
        # if the key is missing, it is considered as None,
        # even though we want the default True value to be applied in such a case
        # so we enforce it.
        self.fields["club_status"].value = True
    self.fields["name"].required = False

JoinClubForm(*args, club, request_user, **kwargs)

Bases: ClubMemberForm

Form to join a club.

Source code in club/forms.py
def __init__(self, *args, club: Club, request_user: User, **kwargs):
    super().__init__(*args, club=club, request_user=request_user, **kwargs)
    self.instance.user = self.request_user

clean()

Check that the user is subscribed and isn't already in the club.

Source code in club/forms.py
def clean(self):
    """Check that the user is subscribed and isn't already in the club."""
    if not self.request_user.is_subscribed:
        raise forms.ValidationError(
            _("You must be subscribed to join a club"), code="invalid"
        )
    if self.club.get_membership_for(self.request_user):
        raise forms.ValidationError(
            _("You are already a member of this club"), code="invalid"
        )
    return super().clean()

SellingsForm(club, *args, **kwargs)

Bases: Form

Source code in club/forms.py
def __init__(self, club, *args, **kwargs):
    super().__init__(*args, **kwargs)
    # postgres struggles really hard with a single query having three WHERE conditions,
    # but deals perfectly fine with UNION of multiple queryset with their own WHERE clause,
    # so we do this to get the ids, which we use to build another queryset that can be used by django.
    club_sales_subquery = Selling.objects.filter(counter=OuterRef("pk"), club=club)
    ids = (
        Counter.objects.filter(Q(club=club) | Q(products__club=club))
        .union(Counter.objects.filter(Exists(club_sales_subquery)))
        .values_list("id", flat=True)
    )
    counters_qs = Counter.objects.filter(id__in=ids).order_by(Lower("name"))
    self.fields["counters"] = forms.ModelMultipleChoiceField(
        counters_qs, label=_("Counter"), required=False
    )
    self.fields["products"] = forms.ModelMultipleChoiceField(
        club.products.order_by("name").filter(archived=False).all(),
        label=_("Products"),
        required=False,
    )
    self.fields["archived_products"] = forms.ModelMultipleChoiceField(
        club.products.order_by("name").filter(archived=True).all(),
        label=_("Archived products"),
        required=False,
    )

Club

Bases: Model

The Club class, made as a tree to allow nice tidy organization.

president()

Fetch the membership of the current president of this club.

Source code in club/models.py
@cached_property
def president(self) -> Membership | None:
    """Fetch the membership of the current president of this club."""
    return self.members.filter(end_date=None).order_by("role__order").first()

check_loop()

Raise a validation error when a loop is found within the parent list.

Source code in club/models.py
def check_loop(self):
    """Raise a validation error when a loop is found within the parent list."""
    objs = []
    cur = self
    while cur.parent is not None:
        if cur in objs:
            raise ValidationError(_("You can not make loops in clubs"))
        objs.append(cur)
        cur = cur.parent

create_default_roles()

Create some roles that should exist by default for this club.

The created roles are : president, treasurer, active member and curious.

Source code in club/models.py
def create_default_roles(self):
    """Create some roles that should exist by default for this club.

    The created roles are : president, treasurer, active member and curious.

    Warnings:
        When calling this method, no club must exist yet for this club.
    """
    if self.roles.exists():
        raise ProgrammingError(
            "Default roles can be created only for clubs "
            "that don't have associated roles yet"
        )
    # The names are written in French, because there is no gettext involved
    # for strings stored in database, and the majority of users are french.
    roles = [
        ClubRole(name="Président⸱e", is_board=True, is_presidency=True),
        ClubRole(name="Trésorier⸱e", is_board=True, is_presidency=False),
        ClubRole(name="Membre actif⸱ve", is_board=False, is_presidency=False),
        ClubRole(
            name="Curieux⸱euse",
            description=(
                "Les gens qui suivent l'activité "
                "du club sans forcément y participer"
            ),
            is_board=False,
            is_presidency=False,
        ),
    ]
    for i, role in enumerate(roles):
        role.club = self
        role.order = i
    ClubRole.objects.bulk_create(roles)

is_owned_by(user)

Method to see if that object can be super edited by the given user.

Source code in club/models.py
def is_owned_by(self, user: User) -> bool:
    """Method to see if that object can be super edited by the given user."""
    if user.is_anonymous:
        return False
    return user.is_root or user.is_board_member

can_be_edited_by(user)

Method to see if that object can be edited by the given user.

Source code in club/models.py
def can_be_edited_by(self, user: User) -> bool:
    """Method to see if that object can be edited by the given user."""
    return user.has_perm("club.change_club") or self.has_rights_in_club(user)

can_roles_be_edited_by(user)

Return True if the given user can edit the roles of this club

Source code in club/models.py
def can_roles_be_edited_by(self, user: User) -> bool:
    """Return True if the given user can edit the roles of this club"""
    return user.is_authenticated and (
        user.has_perm("club.change_clubrole")
        or self.members.ongoing()
        .filter(user=user, role__is_presidency=True)
        .exists()
    )

get_membership_for(user)

Return the current membership of the given user.

Source code in club/models.py
def get_membership_for(self, user: User) -> Membership | None:
    """Return the current membership of the given user."""
    if user.is_anonymous:
        return None
    return next((m for m in self.current_members if m.user_id == user.id), None)

Bases: Model

ClubRole

Bases: OrderedModel

LinkType

Bases: 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.

Membership

Bases: Model

The Membership class makes the connection between User and Clubs.

Both Users and Clubs can have many Membership objects
  • a user can be a member of many clubs at a time
  • a club can have many members at a time too

A User is currently member of all the Clubs where its Membership has an end_date set to null/None. Otherwise, it's a past membership kept because it can be very useful to see who was in which Club in the past.

is_owned_by(user)

Method to see if that object can be super edited by the given user.

Source code in club/models.py
def is_owned_by(self, user: User) -> bool:
    """Method to see if that object can be super edited by the given user."""
    if user.is_anonymous:
        return False
    return user.is_root or user.is_board_member

can_be_edited_by(user)

Check if that object can be edited by the given user.

Source code in club/models.py
def can_be_edited_by(self, user: User) -> bool:
    """Check if that object can be edited by the given user."""
    if user.is_root or user.is_board_member:
        return True
    membership = self.club.get_membership_for(user)
    if not membership:
        return False
    return membership.user_id == user.id or (
        membership.is_board and membership.role.order < self.role.order
    )

ClubTabsMixin

ClubListView

Bases: AllowFragment, FormMixin, ListView

List the clubs of the AE, with a form to perform basic search.

Notes

This view is fully public, because we want to advertise as much as possible the cultural life of the AE. In accordance with that matter, searching and listing the clubs is done entirely server-side (no AlpineJS involved) ; this is done this way in order to be sure the page is the most accessible and SEO-friendly possible, even if it makes the UX slightly less smooth.

ClubView

Bases: ClubTabsMixin, DetailView

Front page of a Club.

ClubRevView

Bases: LoginRequiredMixin, ClubView

Display a specific page revision.

ClubPageEditView

ClubPageHistView

Bases: ClubTabsMixin, PermissionRequiredMixin, DetailView

Modification history of the page.

ClubToolsView

Bases: ClubTabsMixin, CanEditMixin, DetailView

Tools page of a Club.

ClubAddMembersFragment

Bases: FragmentMixin, PermissionRequiredMixin, SuccessMessageMixin, CreateView

ClubMembersView

Bases: ClubTabsMixin, UseFragmentsMixin, PermissionRequiredMixin, DetailFormView

View of a club's members.

ClubOldMembersView

Bases: ClubTabsMixin, PermissionRequiredMixin, DetailView

Old members of a club.

ClubRoleUpdateView

Bases: ClubTabsMixin, UserPassesTestMixin, SuccessMessageMixin, UpdateView

ClubRoleBaseCreateView

Bases: UserPassesTestMixin, SuccessMessageMixin, CreateView

View to create a new Club Role, using .

This view isn't meant to be called directly, but rather subclassed for each type of role that can exist :

  • [ClubRolePresidencyCreateView][club.views.ClubRolePresidencyCreateView] to create a presidency role
  • [ClubRoleBoardCreateView][club.views.ClubRoleBoardCreateView] to create a board role
  • [ClubRoleMemberCreateView][club.views.ClubRoleMemberCreateView] to create a member role

Each subclass have to override the following variables :

  • is_presidency and is_board, indicating what type of role the view creates.
  • role_description, which is the title of the page, indication the user what kind of role is being created.

This way, we are making sure the correct type of role will be created, without bothering the user with the implementation details.

ClubRolePresidencyCreateView

ClubRoleBoardCreateView

ClubRoleMemberCreateView

ClubSellingView

Bases: ClubTabsMixin, CanEditMixin, DetailFormView

Sales of a club.

ClubSellingCSVView

Bases: ClubSellingView

Generate sellings in csv for a given period.

StreamWriter

Implements a file-like interface for streaming the CSV.

write(value)

Write the value by returning it, instead of storing in a buffer.

Source code in club/views.py
def write(self, value):
    """Write the value by returning it, instead of storing in a buffer."""
    return value

ClubEditView

Bases: ClubTabsMixin, CanEditMixin, UpdateView

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).

ClubCreateView

Bases: PermissionRequiredMixin, CreateView

Create a club (for the Sith admin).

MembershipSetOldView

Bases: CanEditMixin, SingleObjectMixin, View

Set a membership as being old.

MembershipDeleteView

Bases: PermissionRequiredMixin, DeleteView

Delete a membership (for admins only).

PosterListView

Bases: PermissionOrClubBoardRequiredMixin, ClubTabsMixin, PosterListBaseView

List communication posters.

PosterCreateView

Bases: ClubTabsMixin, PosterCreateBaseView

Create communication poster.

PosterEditView

Bases: ClubTabsMixin, PosterEditBaseView

Edit communication poster.

PosterDeleteView

Bases: ClubTabsMixin, PosterDeleteBaseView

Delete communication poster.

ClubNewsView

Bases: PermissionRequiredMixin, ClubTabsMixin, DetailView