Source code for pretalx.person.models.profile

# SPDX-FileCopyrightText: 2017-present Tobias Kunze
# SPDX-License-Identifier: AGPL-3.0-only WITH LicenseRef-Pretalx-AGPL-3.0-Terms

from django.db import models
from django.utils.functional import cached_property
from django.utils.translation import gettext_lazy as _

from pretalx.agenda.rules import can_view_schedule, is_speaker_viewable
from pretalx.common.models.fields import MarkdownField
from pretalx.common.models.mixins import GenerateCode, PretalxModel
from pretalx.common.urls import EventUrls
from pretalx.orga.rules import can_view_speaker_names
from pretalx.person.models.picture import ProfilePictureMixin
from pretalx.person.rules import (
    can_mark_speakers_arrived,
    is_administrator,
    is_reviewer,
)
from pretalx.submission.rules import orga_can_change_submissions


[docs] class SpeakerProfile(ProfilePictureMixin, GenerateCode, PretalxModel): """All :class:`~pretalx.event.models.event.Event` related data concerning a. :class:`~pretalx.person.models.user.User` is stored here. :param has_arrived: Can be set to track speaker arrival. Will be used in warnings about missing speakers. """ code_scope = ("event",) user = models.ForeignKey( to="person.User", related_name="profiles", on_delete=models.CASCADE, null=True, blank=True, ) event = models.ForeignKey( to="event.Event", related_name="+", on_delete=models.CASCADE ) name = models.CharField( max_length=120, null=True, blank=True, verbose_name=_("Name") ) code = models.CharField(max_length=16) biography = MarkdownField(verbose_name=_("Biography"), null=True, blank=True) has_arrived = models.BooleanField( default=False, verbose_name=_("The speaker has arrived") ) internal_notes = models.TextField( null=True, blank=True, verbose_name=_("Internal notes"), help_text=_( "Internal notes for other organisers/reviewers. Not visible to the speakers or the public." ), ) profile_picture = models.ForeignKey( "person.ProfilePicture", verbose_name=_("Profile picture"), null=True, blank=True, on_delete=models.SET_NULL, related_name="speakers", ) log_prefix = "pretalx.user.profile" class Meta: unique_together = (("event", "code"), ("event", "user")) # These permissions largely apply to event-scoped user actions rules_permissions = { "list": can_view_schedule | (is_reviewer & can_view_speaker_names), "reviewer_list": is_reviewer & can_view_speaker_names, "orga_list": orga_can_change_submissions | (is_reviewer & can_view_speaker_names), "view": is_speaker_viewable | orga_can_change_submissions | (is_reviewer & can_view_speaker_names), "orga_view": orga_can_change_submissions | (is_reviewer & can_view_speaker_names), "create": is_administrator, "update": orga_can_change_submissions, "mark_arrived": orga_can_change_submissions & can_mark_speakers_arrived, "delete": is_administrator, } class urls(EventUrls): public = "{self.event.urls.base}speaker/{self.code}/" social_image = "{public}og-image" talks_ical = "{public}talks.ics" class orga_urls(EventUrls): base = "{self.event.orga_urls.speakers}{self.code}/" password_reset = "{base}reset" # noqa: S105 -- URL pattern, not a password toggle_arrived = "{base}toggle-arrived" send_mail = "{self.event.orga_urls.compose_mails_sessions}?speakers={self.code}" def __str__(self): """Help when debugging.""" return ( f"SpeakerProfile(event={self.event.slug}, user={self.get_display_name()})" ) def get_display_name(self): return ( self.name or (self.user.name if self.user else None) or str(_("Unnamed speaker")) ) @cached_property def talks(self): """A queryset of. :class:`~pretalx.submission.models.submission.Submission` objects. Contains all visible talks by this user on this event. """ return self.event.talks.filter(speakers=self) def get_talk_slots(self, schedule=None): schedule = schedule or self.event.current_schedule from pretalx.schedule.models import TalkSlot # noqa: PLC0415 if not schedule: return TalkSlot.objects.none() return ( schedule.talks.filter(submission__speakers=self, is_visible=True) .select_related( "submission", "room", "submission__event", "submission__track", "submission__submission_type", ) .with_sorted_speakers() ) @cached_property def current_talk_slots(self): return self.get_talk_slots() @cached_property def all_answers(self): """A queryset of :class:`~pretalx.submission.models.question.Answer` objects. Includes all answers the user has given either for themselves or for their talks for this event. """ from pretalx.submission.models import Answer, Submission # noqa: PLC0415 submissions = Submission.objects.filter(event=self.event, speakers=self) return Answer.objects.filter( models.Q(submission__in=submissions) | models.Q(speaker=self) ).order_by("question__position") @property def reviewer_answers(self): return self.all_answers.filter(question__is_visible_to_reviewers=True).order_by( "question__position" ) def get_instance_data(self): data = {} if self.pk: data = { "name": self.name or (self.user.name if self.user else None), "email": self.user.email if self.user else None, "profile_picture": ( self.profile_picture.avatar.name if self.profile_picture_id and self.profile_picture.avatar else None ), } return super().get_instance_data() | data @cached_property def full_availability(self): from pretalx.schedule.models import Availability # noqa: PLC0415 return Availability.union(self.availabilities.all())