Aller au contenu

Views

FragmentMixin

Bases: TemplateResponseMixin, ContextMixin

Make a view buildable as a fragment that can be embedded in a template.

Most fragments are used in two different ways : - in the request/response cycle, like any regular view - in templates, where the rendering is done in another view

This mixin aims to simplify the initial fragment rendering. The rendered fragment will then be able to re-render itself through the request/response cycle if it uses HTMX.

Example

class MyFragment(FragmentMixin, FormView):
    template_name = "app/fragment.jinja"
    form_class = MyForm
    success_url = reverse_lazy("foo:bar")

# in another view :
def some_view(request):
    fragment = MyFragment.as_fragment()
    return render(
        request,
        "app/template.jinja",
        context={"fragment": fragment(request)
    }

# in urls.py
urlpatterns = [
    path("foo/view", some_view),
    path("foo/fragment", MyFragment.as_view()),
]

reload_on_redirect = False class-attribute instance-attribute

If True, this fragment will trigger a full page reload on redirect.

UseFragmentsMixin

Bases: ContextMixin

Mark a view as using fragments.

This mixin is not mandatory (you may as well render manually your fragments in the get_context_data method). However, the interface of this class bring some distinction between fragments and other context data, which may reduce boilerplate.

Example

class FooFragment(FragmentMixin, FormView): ...

class BarFragment(FragmentMixin, FormView): ...

class AdminFragment(FragmentMixin, FormView): ...

class MyView(UseFragmentsMixin, TemplateView)
    template_name = "app/view.jinja"
    fragments = {
        "foo": FooFragment
        "bar": BarFragment(template_name="some_template.jinja")
    }
    fragments_data = {
        "foo": {"some": "data"}  # this will be passed to the FooFragment renderer
    }

    def get_fragments(self):
        res = super().get_fragments()
        if self.request.user.is_superuser:
            res["admin_fragment"] = AdminFragment
        return res

get_fragment_data()

Return eventual data used to initialize the fragments.

Source code in core/views/mixins.py
def get_fragment_data(self) -> dict[str, dict[str, Any]]:
    """Return eventual data used to initialize the fragments."""
    return self.fragment_data if self.fragment_data is not None else {}

get_fragment_context_data()

Return the rendered fragments as context data.

Source code in core/views/mixins.py
def get_fragment_context_data(self) -> dict[str, SafeString]:
    """Return the rendered fragments as context data."""
    res = {}
    data = self.get_fragment_data()
    for name, fragment in self.get_fragments().items():
        is_cls = inspect.isclass(fragment) and issubclass(fragment, FragmentMixin)
        _fragment = fragment.as_fragment() if is_cls else fragment
        fragment_data = data.get(name, {})
        res[name] = _fragment(self.request, **fragment_data)
    return res

PermissionGroupsUpdateView

Bases: PermissionRequiredMixin, SuccessMessageMixin, UpdateView

Manage the groups that have a specific permission.

Notes

This is an UpdateView, but unlike typical UpdateView, it doesn't accept url arguments to retrieve the object to update. As such, a PermissionGroupsUpdateView can only deal with a single hardcoded permission.

This is not a limitation, but an on-purpose design, mainly for security matters.

Example
class SubscriptionPermissionView(PermissionGroupsUpdateView):
    permission = "subscription.add_subscription"

SelectionDateForm(*args, **kwargs)

Bases: Form

Source code in subscription/forms.py
def __init__(self, *args, **kwargs):
    super().__init__(*args, **kwargs)
    self.fields["start_date"] = forms.DateTimeField(
        label=_("Start date"), widget=SelectDateTime, required=True
    )
    self.fields["end_date"] = forms.DateTimeField(
        label=_("End date"), widget=SelectDateTime, required=True
    )

SubscriptionExistingUserForm(*args, initial=None, **kwargs)

Bases: SubscriptionForm

Form to add a subscription to an existing user.

Source code in subscription/forms.py
def __init__(self, *args, initial=None, **kwargs):
    super().__init__(*args, initial=initial, **kwargs)
    self.fields["birthdate"].required = True
    if not initial:
        return
    member: str | None = initial.get("member")
    if member and member.isdigit():
        member: User | None = User.objects.filter(id=int(member)).first()
    else:
        member = None
    if member and member.date_of_birth:
        # if there is an initial member with a birthdate,
        # there is no need to ask this to the user
        self.fields["birthdate"].initial = member.date_of_birth
    elif member:
        # if there is an initial member without a birthdate,
        # then the field must be displayed
        self.fields["birthdate"].widget.attrs.update({"hidden": False})

SubscriptionNewUserForm(*args, initial=None, **kwargs)

Bases: SubscriptionForm

Form to create subscriptions with the user they belong to.

Examples:

```py assert not User.objects.filter(email=request.POST.get("email")).exists() form = SubscriptionNewUserForm(request.POST) if form.is_valid(): form.save()

now the user exists and is subscribed

user = User.objects.get(email=request.POST.get("email")) assert user.is_subscribed

Source code in subscription/forms.py
def __init__(self, *args, initial=None, **kwargs):
    initial = initial or {}
    if "subscription_type" not in initial:
        initial["subscription_type"] = "deux-semestres"
    if "payment_method" not in initial:
        initial["payment_method"] = "CARD"
    super().__init__(*args, initial=initial, **kwargs)
    self.fields["payment_method"].choices = [
        m
        for m in settings.SITH_SUBSCRIPTION_PAYMENT_METHOD
        if m[0] in self.allowed_payment_methods
    ]
    self.fields["location"].choices = [
        m for m in settings.SITH_SUBSCRIPTION_LOCATIONS if m[0] != "EBOUTIC"
    ]

clean()

Initialize the User linked to this subscription.

Warning

The User is initialized, but not saved. Don't use it for operations that expect a persisted object.

Source code in subscription/forms.py
def clean(self) -> dict[str, Any]:
    """Initialize the [User][core.models.User] linked to this subscription.

    Warning:
        The `User` is initialized, but not saved.
        Don't use it for operations that expect
        a persisted object.
    """
    member = User(
        first_name=self.cleaned_data.get("first_name"),
        last_name=self.cleaned_data.get("last_name"),
        email=self.cleaned_data.get("email"),
        date_of_birth=self.cleaned_data.get("date_of_birth"),
    )
    if self.errors:
        # don't bother generating username, password and other data.
        # The form validation failed anyway, so using a dummy User
        # (just for Subscription.clean not to crash) is enough
        self.instance.member = member
        return super().clean()
    if self.cleaned_data.get("subscription_type") in [
        "un-semestre",
        "deux-semestres",
        "cursus-tronc-commun",
        "cursus-branche",
        "cursus-alternant",
    ]:
        member.role = "STUDENT"
        member.school = "UTBM"
    if self.cleaned_data.get("subscription_type") == "cursus-tronc-commun":
        member.promo = get_last_promo()
    if self.cleaned_data.get("subscription_type") in [
        "cursus-branche",
        "cursus-alternant",
    ]:
        member.promo = get_last_promo() - 2
    member.generate_username()
    member.set_password(secrets.token_urlsafe(nbytes=10))
    self.instance.member = member
    return super().clean()

Subscription

Bases: Model

semester_duration property

Duration of this subscription, in number of semester.

Notes

The Subscription object doesn't have to actually exist in the database to access this property

Examples:

subscription = Subscription(subscription_type="deux-semestres")
assert subscription.semester_duration == 2.0

compute_start(d=None, duration=1, user=None) staticmethod

Computes the start date of the subscription.

The computation is done with respect to the given date (default is today) and the start date given in settings.SITH_SEMESTER_START_AUTUMN. It takes the nearest past start date. Exemples: with SITH_SEMESTER_START_AUTUMN = (8, 15) Today -> Start date 2015-03-17 -> 2015-02-15 2015-01-11 -> 2014-08-15.

Source code in subscription/models.py
@staticmethod
def compute_start(
    d: date | None = None, duration: int | float = 1, user: User | None = None
) -> date:
    """Computes the start date of the subscription.

    The computation is done with respect to the given date (default is today)
    and the start date given in settings.SITH_SEMESTER_START_AUTUMN.
    It takes the nearest past start date.
    Exemples: with SITH_SEMESTER_START_AUTUMN = (8, 15)
        Today      -> Start date
        2015-03-17 -> 2015-02-15
        2015-01-11 -> 2014-08-15.
    """
    if not d:
        d = date.today()
    if user is not None and user.subscriptions.exists():
        last = user.subscriptions.last()
        if last.is_valid_now():
            d = last.subscription_end
    if duration <= 2:  # Sliding subscriptions for 1 or 2 semesters
        return d
    return get_start_of_semester(d)

compute_end(duration, start=None, user=None) staticmethod

Compute the end date of the subscription.

Parameters:

Name Type Description Default
duration int | float

the duration of the subscription, in semester (for example, 2 => 2 semesters => 1 year)

required
start date | None

The start date of the subscription

None
user User | None

the user which is (or will be) subscribed

None
Exemples

Start - Duration -> End date 2015-09-18 - 1 -> 2016-03-18 2015-09-18 - 2 -> 2016-09-18 2015-09-18 - 3 -> 2017-03-18 2015-09-18 - 4 -> 2017-09-18.

Source code in subscription/models.py
@staticmethod
def compute_end(
    duration: int | float, start: date | None = None, user: User | None = None
) -> date:
    """Compute the end date of the subscription.

    Args:
        duration:
            the duration of the subscription, in semester
            (for example, 2 => 2 semesters => 1 year)
        start: The start date of the subscription
        user: the user which is (or will be) subscribed

    Exemples:
        Start - Duration -> End date
        2015-09-18 - 1 -> 2016-03-18
        2015-09-18 - 2 -> 2016-09-18
        2015-09-18 - 3 -> 2017-03-18
        2015-09-18 - 4 -> 2017-09-18.
    """
    if start is None:
        start = Subscription.compute_start(duration=duration, user=user)

    return start + relativedelta(
        months=round(6 * duration),
        days=math.ceil((6 * duration - round(6 * duration)) * 30),
    )

CreateSubscriptionFragment

Bases: PermissionRequiredMixin, FragmentMixin, CreateView

CreateSubscriptionExistingUserFragment

Bases: CreateSubscriptionFragment

Create a subscription for a user who already exists.

CreateSubscriptionNewUserFragment

Bases: CreateSubscriptionFragment

Create a subscription for a user who doesn't exist yet.

NewSubscription

Bases: PermissionRequiredMixin, UseFragmentsMixin, TemplateView

SubscriptionCreatedFragment

Bases: PermissionRequiredMixin, DetailView

SubscriptionPermissionView

Bases: PermissionGroupsUpdateView

Manage the groups that have access to the subscription creation page.

SubscriptionsStatsView

Bases: FormView