Aller au contenu

Views

Notification

Bases: Model

User

Bases: AbstractUser

Defines the base user class, useable in every app.

This is almost the same as the auth module AbstractUser since it inherits from it, but some fields are required, and the username is generated automatically with the name of the user (see generate_username()).

Added field: nick_name, date_of_birth Required fields: email, first_name, last_name, date_of_birth

cached_groups: list[Group] property

Get the list of groups this user is in.

The result is cached for the default duration (should be 5 minutes)

Returns: A list of all the groups this user is in.

is_in_group(*, pk=None, name=None)

Check if this user is in the given group. Either a group id or a group name must be provided. If both are passed, only the id will be considered.

The group will be fetched using the given parameter. If no group is found, return False. If a group is found, check if this user is in the latter.

Returns:

Type Description
bool

True if the user is the group, else False

Source code in core/models.py
def is_in_group(self, *, pk: int | None = None, name: str | None = None) -> bool:
    """Check if this user is in the given group.
    Either a group id or a group name must be provided.
    If both are passed, only the id will be considered.

    The group will be fetched using the given parameter.
    If no group is found, return False.
    If a group is found, check if this user is in the latter.

    Returns:
         True if the user is the group, else False
    """
    if pk is not None:
        group: Optional[Group] = get_group(pk=pk)
    elif name is not None:
        group: Optional[Group] = get_group(name=name)
    else:
        raise ValueError("You must either provide the id or the name of the group")
    if group is None:
        return False
    if group.id == settings.SITH_GROUP_PUBLIC_ID:
        return True
    if group.id == settings.SITH_GROUP_SUBSCRIBERS_ID:
        return self.is_subscribed
    if group.id == settings.SITH_GROUP_OLD_SUBSCRIBERS_ID:
        return self.was_subscribed
    if group.id == settings.SITH_GROUP_ROOT_ID:
        return self.is_root
    return group in self.cached_groups

age()

Return the age this user has the day the method is called. If the user has not filled his age, return 0.

Source code in core/models.py
@cached_property
def age(self) -> int:
    """Return the age this user has the day the method is called.
    If the user has not filled his age, return 0.
    """
    if self.date_of_birth is None:
        return 0
    today = timezone.now()
    age = today.year - self.date_of_birth.year
    # remove a year if this year's birthday is yet to come
    age -= (today.month, today.day) < (
        self.date_of_birth.month,
        self.date_of_birth.day,
    )
    return age

get_short_name()

Returns the short name for the user.

Source code in core/models.py
def get_short_name(self):
    """Returns the short name for the user."""
    if self.nick_name:
        return self.nick_name
    return self.first_name + " " + self.last_name

get_display_name()

Returns the display name of the user.

A nickname if possible, otherwise, the full name.

Source code in core/models.py
def get_display_name(self) -> str:
    """Returns the display name of the user.

    A nickname if possible, otherwise, the full name.
    """
    if self.nick_name:
        return "%s (%s)" % (self.get_full_name(), self.nick_name)
    return self.get_full_name()

get_family(godfathers_depth=4, godchildren_depth=4)

Get the family of the user, with the given depth.

Parameters:

Name Type Description Default
godfathers_depth NonNegativeInt

The number of generations of godfathers to fetch

4
godchildren_depth NonNegativeInt

The number of generations of godchildren to fetch

4

Returns:

Type Description
set[through]

A list of family relationships in this user's family

Source code in core/models.py
def get_family(
    self,
    godfathers_depth: NonNegativeInt = 4,
    godchildren_depth: NonNegativeInt = 4,
) -> set[User.godfathers.through]:
    """Get the family of the user, with the given depth.

    Args:
        godfathers_depth: The number of generations of godfathers to fetch
        godchildren_depth: The number of generations of godchildren to fetch

    Returns:
        A list of family relationships in this user's family
    """
    res = []
    for depth, key, reverse_key in [
        (godfathers_depth, "from_user_id", "to_user_id"),
        (godchildren_depth, "to_user_id", "from_user_id"),
    ]:
        if depth == 0:
            continue
        links = list(User.godfathers.through.objects.filter(**{key: self.id}))
        res.extend(links)
        for _ in range(1, depth):  # noqa: F402 we don't care about gettext here
            ids = [getattr(c, reverse_key) for c in links]
            links = list(
                User.godfathers.through.objects.filter(
                    **{f"{key}__in": ids}
                ).exclude(id__in=[r.id for r in res])
            )
            if not links:
                break
            res.extend(links)
    return set(res)

email_user(subject, message, from_email=None, **kwargs)

Sends an email to this User.

Source code in core/models.py
def email_user(self, subject, message, from_email=None, **kwargs):
    """Sends an email to this User."""
    if from_email is None:
        from_email = settings.DEFAULT_FROM_EMAIL
    send_mail(subject, message, from_email, [self.email], **kwargs)

generate_username()

Generates a unique username based on the first and last names.

For example: Guy Carlier gives gcarlier, and gcarlier1 if the first one exists.

Returns:

Type Description
str

The generated username.

Source code in core/models.py
def generate_username(self) -> str:
    """Generates a unique username based on the first and last names.

    For example: Guy Carlier gives gcarlier, and gcarlier1 if the first one exists.

    Returns:
        The generated username.
    """

    def remove_accents(data):
        return "".join(
            x
            for x in unicodedata.normalize("NFKD", data)
            if unicodedata.category(x)[0] == "L"
        ).lower()

    user_name = (
        remove_accents(self.first_name[0] + self.last_name)
        .encode("ascii", "ignore")
        .decode("utf-8")
    )
    # load all usernames which could conflict with the new one.
    # we need to actually load them, instead of performing a count,
    # because we cannot be sure that two usernames refer to the
    # actual same word (eg. tmore and tmoreau)
    possible_conflicts: list[str] = list(
        User.objects.filter(username__startswith=user_name).values_list(
            "username", flat=True
        )
    )
    nb_conflicts = sum(
        1 for name in possible_conflicts if name.rstrip(string.digits) == user_name
    )
    if nb_conflicts > 0:
        user_name += str(nb_conflicts)  # exemple => exemple1
    self.username = user_name
    return user_name

is_owner(obj)

Determine if the object is owned by the user.

Source code in core/models.py
def is_owner(self, obj):
    """Determine if the object is owned by the user."""
    if hasattr(obj, "is_owned_by") and obj.is_owned_by(self):
        return True
    if hasattr(obj, "owner_group") and self.is_in_group(pk=obj.owner_group.id):
        return True
    return self.is_root

can_edit(obj)

Determine if the object can be edited by the user.

Source code in core/models.py
def can_edit(self, obj):
    """Determine if the object can be edited by the user."""
    if hasattr(obj, "can_be_edited_by") and obj.can_be_edited_by(self):
        return True
    if hasattr(obj, "edit_groups"):
        for pk in obj.edit_groups.values_list("pk", flat=True):
            if self.is_in_group(pk=pk):
                return True
    if isinstance(obj, User) and obj == self:
        return True
    return self.is_owner(obj)

can_view(obj)

Determine if the object can be viewed by the user.

Source code in core/models.py
def can_view(self, obj):
    """Determine if the object can be viewed by the user."""
    if hasattr(obj, "can_be_viewed_by") and obj.can_be_viewed_by(self):
        return True
    if hasattr(obj, "view_groups"):
        for pk in obj.view_groups.values_list("pk", flat=True):
            if self.is_in_group(pk=pk):
                return True
    return self.can_edit(obj)

clubs_with_rights()

The list of clubs where the user has rights

Source code in core/models.py
@cached_property
def clubs_with_rights(self) -> list[Club]:
    """The list of clubs where the user has rights"""
    memberships = self.memberships.ongoing().board().select_related("club")
    return [m.club for m in memberships]

CanCreateMixin

Bases: View

Protect any child view that would create an object.

Raises:

Type Description
PermissionDenied

If the user has not the necessary permission to create the object of the view.

CanEditPropMixin

Bases: GenericContentPermissionMixinBuilder

Ensure the user has owner permissions on the child view object.

In other word, you can make a view with this view as parent, and it will be retricted to the users that are in the object's owner_group or that pass the obj.can_be_viewed_by test.

Raises:

Type Description
PermissionDenied

If the user cannot see the object

CanViewMixin

Bases: GenericContentPermissionMixinBuilder

Ensure the user has permission to view this view's object.

Raises:

Type Description
PermissionDenied

if the user cannot edit this view's object.

DetailFormView

Bases: SingleObjectMixin, FormView

Class that allow both a detail view and a form view.

get_object()

Get current group from id in url.

Source code in core/views/__init__.py
def get_object(self):
    """Get current group from id in url."""
    return self.cached_object

cached_object()

Optimisation on group retrieval.

Source code in core/views/__init__.py
@cached_property
def cached_object(self):
    """Optimisation on group retrieval."""
    return super().get_object()

FormerSubscriberMixin

Bases: AccessMixin

Check if the user was at least an old subscriber.

Raises:

Type Description
PermissionDenied

if the user never subscribed.

UVCommentForm(author_id, uv_id, is_creation, *args, **kwargs)

Bases: ModelForm

Form handeling creation and edit of an UVComment.

Source code in pedagogy/forms.py
def __init__(self, author_id, uv_id, is_creation, *args, **kwargs):
    super().__init__(*args, **kwargs)
    self.fields["author"].queryset = User.objects.filter(id=author_id).all()
    self.fields["author"].initial = author_id
    self.fields["uv"].queryset = UV.objects.filter(id=uv_id).all()
    self.fields["uv"].initial = uv_id
    self.is_creation = is_creation

UVCommentModerationForm

Bases: Form

Form handeling bulk comment deletion.

UVCommentReportForm(reporter_id, comment_id, *args, **kwargs)

Bases: ModelForm

Form handeling creation and edit of an UVReport.

Source code in pedagogy/forms.py
def __init__(self, reporter_id, comment_id, *args, **kwargs):
    super().__init__(*args, **kwargs)
    self.fields["reporter"].queryset = User.objects.filter(id=reporter_id).all()
    self.fields["reporter"].initial = reporter_id
    self.fields["comment"].queryset = UVComment.objects.filter(id=comment_id).all()
    self.fields["comment"].initial = comment_id

UVForm(author_id, *args, **kwargs)

Bases: ModelForm

Form handeling creation and edit of an UV.

Source code in pedagogy/forms.py
def __init__(self, author_id, *args, **kwargs):
    super().__init__(*args, **kwargs)
    self.fields["author"].queryset = User.objects.filter(id=author_id).all()
    self.fields["author"].initial = author_id

UV

Bases: Model

Contains infos about an UV (course).

is_owned_by(user)

Can be created by superuser, root or pedagogy admin user.

Source code in pedagogy/models.py
def is_owned_by(self, user):
    """Can be created by superuser, root or pedagogy admin user."""
    return user.is_in_group(pk=settings.SITH_GROUP_PEDAGOGY_ADMIN_ID)

can_be_viewed_by(user)

Only visible by subscribers.

Source code in pedagogy/models.py
def can_be_viewed_by(self, user):
    """Only visible by subscribers."""
    return user.is_subscribed

has_user_already_commented(user)

Help prevent multiples comments from the same user.

This function checks that no other comment has been posted by a specified user.

Returns:

Type Description
bool

True if the user has already posted a comment on this UV, else False.

Source code in pedagogy/models.py
def has_user_already_commented(self, user: User) -> bool:
    """Help prevent multiples comments from the same user.

    This function checks that no other comment has been posted by a specified user.

    Returns:
        True if the user has already posted a comment on this UV, else False.
    """
    return self.comments.filter(author=user).exists()

UVComment

Bases: Model

A comment about an UV.

is_owned_by(user)

Is owned by a pedagogy admin, a superuser or the author himself.

Source code in pedagogy/models.py
def is_owned_by(self, user):
    """Is owned by a pedagogy admin, a superuser or the author himself."""
    return self.author == user or user.is_owner(self.uv)

is_reported()

Return True if someone reported this UV.

Source code in pedagogy/models.py
@cached_property
def is_reported(self):
    """Return True if someone reported this UV."""
    return self.reports.exists()

UVCommentReport

Bases: Model

Report an inapropriate comment.

is_owned_by(user)

Can be created by a pedagogy admin, a superuser or a subscriber.

Source code in pedagogy/models.py
def is_owned_by(self, user):
    """Can be created by a pedagogy admin, a superuser or a subscriber."""
    return user.is_subscribed or user.is_owner(self.comment.uv)

UVDetailFormView

Bases: CanViewMixin, DetailFormView

Display every comment of an UV and detailed infos about it.

Allow to comment the UV.

UVCommentUpdateView

Bases: CanEditPropMixin, UpdateView

Allow edit of a given comment.

UVCommentDeleteView

Bases: CanEditPropMixin, DeleteView

Allow delete of a given comment.

UVGuideView

Bases: LoginRequiredMixin, FormerSubscriberMixin, TemplateView

UV guide main page.

UVCommentReportCreateView

Bases: CanCreateMixin, CreateView

Create a new report for an inapropriate comment.

UVModerationFormView

Bases: FormView

Moderation interface (Privileged).

UVCreateView

Bases: CanCreateMixin, CreateView

Add a new UV (Privileged).

UVDeleteView

Bases: CanEditPropMixin, DeleteView

Allow to delete an UV (Privileged).

UVUpdateView

Bases: CanEditPropMixin, UpdateView

Allow to edit an UV (Privilegied).