Code reference › models › person

class

SpeakerProfile

pretalx.person.models.profile.SpeakerProfile source

A speaker in a specific event.

If a speaker has no user, it is "managed". If a speaker has a user, empty fields fall back on the corresponding user fields.

Fields 17

idAutoFieldpk
userForeignKeynullUsersource
user = models.ForeignKey(
    to="person.User",
    related_name="profiles",
    on_delete=models.CASCADE,
    null=True,
    blank=True,
)
eventForeignKeyEventsource
event = models.ForeignKey(
    to="event.Event", related_name="+", on_delete=models.CASCADE
)
nameCharFieldnullsource
name = models.CharField(
    max_length=120, null=True, blank=True, verbose_name=_("Name")
)
codeCharFieldsource
code = models.CharField(max_length=16)
emailEmailFieldnullsource
email = models.EmailField(null=True, blank=True, verbose_name=_("Contact email"))
localeCharFieldnullsource
locale = models.CharField(
    max_length=32,
    null=True,
    blank=True,
    choices=settings.LANGUAGES,
    verbose_name=_("Preferred language"),
)
invitation_tokenCharFieldnulluniquesource
invitation_token = models.CharField(
    max_length=64, null=True, blank=True, unique=True
)
invitation_sentDateTimeFieldnullsource
invitation_sent = models.DateTimeField(null=True, blank=True)
originCharFieldsource
origin = models.CharField(
    max_length=8,
    choices=SpeakerProfileOrigin.choices,
    default=SpeakerProfileOrigin.CFP,
)
guidCharFieldsource
guid = models.CharField(max_length=36, editable=False)
biographyMarkdownFieldnullsource
biography = MarkdownField(verbose_name=_("Biography"), null=True, blank=True)
has_arrivedBooleanFieldsource
has_arrived = models.BooleanField(
    default=False, verbose_name=_("The speaker has arrived")
)
internal_notesTextFieldnullsourceInternal notes for other organisers/reviewers. Not visible to the speakers or the public.
internal_notes = models.TextField(
    null=True,
    blank=True,
    verbose_name=phrases.base.internal_notes,
    help_text=phrases.base.internal_notes_help,
)
profile_pictureForeignKeynullProfilePicturesource
profile_picture = models.ForeignKey(
    "person.ProfilePicture",
    verbose_name=_("Profile picture"),
    null=True,
    blank=True,
    on_delete=models.SET_NULL,
    related_name="speakers",
)
createdDateTimeFieldnullTimestampedModelsource

on pretalx.common.models.mixins.TimestampedModel source

created = models.DateTimeField(
    verbose_name=_("Created"), auto_now_add=True, blank=True, null=True
)
updatedDateTimeFieldnullTimestampedModelsource

on pretalx.common.models.mixins.TimestampedModel source

updated = models.DateTimeField(
    verbose_name=_("Updated"), auto_now=True, blank=True, null=True
)
Reverse relations 6
mailsManyToManyRelQueuedMail
availabilitiesManyToOneRelAvailability
feedbackManyToOneRelFeedback
answersManyToOneRelAnswer
speaker_rolesManyToOneRelSpeakerRole
submissionsManyToManyRelSubmission

Attributes 3

code_scope('event',)
log_prefix'pretalx.user.profile'
objects<django_scopes.manager.ScopedManager.<locals>.Manager>
Inherited attributes 4
code_charset['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '3', '7', '8', '9']GenerateCode
code_length6GenerateCode
code_property'code'GenerateCode
log_parentNoneLogMixin

Properties 9

Defined here

current_talk_slotssource
@cached_property
def current_talk_slots(self):
    from pretalx.person.domain.queries.profile import (  # noqa: PLC0415 -- thin method
        visible_talk_slots,
    )

    return visible_talk_slots(self)
effective_emailsource
@property
def effective_email(self) -> str | None:
    return self.email or (self.user.email if self.user_id else None)
effective_localesource
@property
def effective_locale(self) -> str:
    locale = self.locale or (self.user.locale if self.user_id else None)
    if locale and locale in self.event.locales:
        return locale
    return self.event.locale
full_availabilitysource
@cached_property
def full_availability(self):
    return Availability.union(self.availabilities.select_related("event"))
has_pending_invitationsource
@property
def has_pending_invitation(self) -> bool:
    return self.is_managed and bool(self.invitation_token)
is_managedsource
@property
def is_managed(self) -> bool:
    return self.user_id is None
talkssourceA queryset of.

Submission objects.

Contains all visible talks by this user on this event.

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

From ProfilePictureMixin pretalx.person.models.picture

avatarsource
@cached_property
def avatar(self):
    if self.profile_picture_id:
        return self.profile_picture.avatar
avatar_urlsourceRelative avatar URL, safe for use in HTML templates served from any host (main site or an event's custom domain).

For absolute URLs (API responses, exports, widget data, emails), use get_avatar_url() instead.

@cached_property
def avatar_url(self):
    """Relative avatar URL, safe for use in HTML templates served from any
    host (main site or an event's custom domain).

    For absolute URLs (API responses, exports, widget data, emails), use
    :meth:`get_avatar_url` instead.
    """
    if self.profile_picture_id:
        return self.profile_picture.avatar_url

Methods 38

Defined here

assign_code(length=None)source

on pretalx.person.models.profile.SpeakerProfile source

def assign_code(self, length=None):
    super().assign_code(length=length)
    self.guid = self.compute_guid()

on pretalx.common.models.mixins.GenerateCode source

def assign_code(self, length=None):
    length = length or self.code_length
    while True:
        code = self.generate_code(length=length)
        filter_kwargs = {f"{self.code_property}__iexact": code}
        for field in self.code_scope:
            filter_kwargs[field] = getattr(self, field)
        with scopes_disabled():
            if not self.__class__.objects.filter(**filter_kwargs).exists():
                setattr(self, self.code_property, code)
                return
compute_guid() -> str | Nonesource
def compute_guid(self) -> str | None:
    prefix = None
    code = None
    if self.user_id:
        prefix = "user"
        code = self.user.code
    if not code:
        prefix = "speaker"
        code = self.code
    if not code:
        # code is always set except for unsaved objects
        return None
    return str(
        uuid.uuid5(GlobalSettings().get_instance_identifier(), f"{prefix}:{code}")
    )
get_display_name(allow_empty=False)source
def get_display_name(self, allow_empty=False):
    name = self.name or (self.user.name if self.user else None)
    if name or allow_empty:
        return name or ""
    return str(_("Unnamed speaker"))
get_initials() -> strsource
def get_initials(self) -> str:
    return "".join(part[0] for part in self.get_display_name().split()[:2]).upper()
get_instance_data()sourceGet a dictionary of field values for this instance.

on pretalx.person.models.profile.SpeakerProfile source

Used for change tracking in log_action. Excludes auto-updated fields like timestamps and sensitive data. Does not handle many-to-many fields.

def get_instance_data(self):
    data = {}
    if not self._state.adding:
        data = {
            "name": self.name or (self.user.name if self.user else None),
            "email": self.email,
            "user_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
            ),
        }
    result = super().get_instance_data() | data
    result.pop("invitation_token", None)
    return result

on pretalx.common.models.mixins.LogMixin source

Get a dictionary of field values for this instance.

Used for change tracking in log_action. Excludes auto-updated fields like timestamps and sensitive data. Does not handle many-to-many fields.

def get_instance_data(self):
    """Get a dictionary of field values for this instance.

    Used for change tracking in log_action. Excludes auto-updated
    fields like timestamps and sensitive data.
    Does not handle many-to-many fields.
    """
    excluded_fields = {
        "created",
        "updated",
        "is_active",
        "last_login",
        "user",
        "event",
        "code",
    }
    data = {}

    for field in self._meta.fields:
        if (
            field.name in excluded_fields
            or field.name in SENSITIVE_KEYS
            or "thumbnail" in field.name
            or getattr(field, "auto_now", False)
            or getattr(field, "auto_now_add", False)
        ):
            continue

        if isinstance(field, models.ForeignKey):
            data[field.name] = getattr(self, field.attname, None)
            continue

        value = getattr(self, field.name, None)

        if isinstance(field, models.FileField):
            data[field.name] = value.name if value else None
        elif isinstance(field, models.UUIDField):
            data[field.name] = str(value) if value else None
        elif isinstance(value, LazyI18nString):
            if isinstance(getattr(value, "data", None), dict):
                data[field.name] = {k: v for k, v in value.data.items() if v}
            else:
                data[field.name] = str(value)
        else:
            data[field.name] = json_roundtrip(value)
    return data
save(*args, **kwargs)sourceSave the current instance.

on pretalx.person.models.profile.SpeakerProfile source

Override this in a subclass if you want to control the saving process.

The 'force_insert' and 'force_update' parameters can be used to insist that the "save" must be an SQL insert or update (or equivalent for non-SQL backends), respectively. Normally, they should not be set.

def save(self, *args, **kwargs):
    if not self.guid:
        new_fields = {"guid"}
        if not self.user_id and not self.code:
            new_fields.add(self.code_property)
        else:
            self.guid = self.compute_guid()
        if update_fields := kwargs.get("update_fields"):
            kwargs["update_fields"] = new_fields.union(update_fields)
    return super().save(*args, **kwargs)

on pretalx.common.models.mixins.GenerateCode source

def save(self, *args, **kwargs):
    if getattr(self, self.code_property, None):
        return super().save(*args, **kwargs)

    # Auto-generate code with retry loop to handle unlikely race conditions
    if "update_fields" in kwargs:
        kwargs["update_fields"] = {self.code_property}.union(
            kwargs["update_fields"]
        )
    for attempt in range(
        3
    ):  # pragma: no branch -- loop always exits via return or raise
        self.assign_code()
        try:
            with transaction.atomic():
                return super().save(*args, **kwargs)
        except IntegrityError:
            if attempt == 2:
                raise
            setattr(self, self.code_property, None)

on pretalx.common.models.mixins.FileCleanupMixin source

def save(self, *args, **kwargs):
    update_fields = kwargs.get("update_fields")
    if self._state.adding or (
        update_fields and not set(self._file_fields) & set(update_fields)
    ):
        return super().save(*args, **kwargs)

    try:
        pre_save_instance = self.__class__.objects.get(pk=self.pk)
    except ObjectDoesNotExist:
        return super().save(*args, **kwargs)

    old_files = {}
    for field in self._file_fields:
        if old_value := getattr(pre_save_instance, field):
            new_value = getattr(self, field)
            if new_value and old_value.path != new_value.path:
                old_files[field] = old_value.path

    result = super().save(*args, **kwargs)
    for field, path in old_files.items():
        self._schedule_file_cleanup(field=field, path=path)
    return result

on django.db.models.Model

Save the current instance. Override this in a subclass if you want to control the saving process.

The 'force_insert' and 'force_update' parameters can be used to insist that the "save" must be an SQL insert or update (or equivalent for non-SQL backends), respectively. Normally, they should not be set.

def save(
    self,
    *,
    force_insert=False,
    force_update=False,
    using=None,
    update_fields=None,
):
    """
    Save the current instance. Override this in a subclass if you want to
    control the saving process.

    The 'force_insert' and 'force_update' parameters can be used to insist
    that the "save" must be an SQL insert or update (or equivalent for
    non-SQL backends), respectively. Normally, they should not be set.
    """

    self._prepare_related_fields_for_save(operation_name="save")

    using = using or router.db_for_write(self.__class__, instance=self)
    if force_insert and (force_update or update_fields):
        raise ValueError("Cannot force both insert and updating in model saving.")

    deferred_non_generated_fields = {
        f.attname
        for f in self._meta.concrete_fields
        if f.attname not in self.__dict__ and f.generated is False
    }
    if update_fields is not None:
        # If update_fields is empty, skip the save. We do also check for
        # no-op saves later on for inheritance cases. This bailout is
        # still needed for skipping signal sending.
        if not update_fields:
            return

        update_fields = frozenset(update_fields)
        field_names = self._meta._non_pk_concrete_field_names
        not_updatable_fields = update_fields.difference(field_names)

        if not_updatable_fields:
            raise ValueError(
                "The following fields do not exist in this model, are m2m "
                "fields, primary keys, or are non-concrete fields: %s"
                % ", ".join(not_updatable_fields)
            )

    # If saving to the same database, and this model is deferred, then
    # automatically do an "update_fields" save on the loaded fields.
    elif (
        not force_insert
        and deferred_non_generated_fields
        and using == self._state.db
        and self._is_pk_set()
    ):
        field_names = set()
        pk_fields = self._meta.pk_fields
        for field in self._meta.concrete_fields:
            if field not in pk_fields and not hasattr(field, "through"):
                field_names.add(field.attname)
        loaded_fields = field_names.difference(deferred_non_generated_fields)
        if loaded_fields:
            update_fields = frozenset(loaded_fields)

    self.save_base(
        using=using,
        force_insert=force_insert,
        force_update=force_update,
        update_fields=update_fields,
    )
__str__()sourceHelp when debugging.

on pretalx.person.models.profile.SpeakerProfile source

def __str__(self):
    """Help when debugging."""
    return (
        f"SpeakerProfile(event={self.event.slug}, user={self.get_display_name()})"
    )

on django.db.models.Model

Return str(self).

def __str__(self):
    return "%s object (%s)" % (self.__class__.__name__, self.pk)

From ProfilePictureMixin pretalx.person.models.picture

get_avatar_url(event=None, thumbnail=None)sourceAbsolute avatar URL, optionally for a thumbnail size.

When the related event has a custom domain, that domain is used as the base; otherwise settings.SITE_URL is used. event defaults to self.event when the model defines one (e.g. SpeakerProfile).

def get_avatar_url(self, event=None, thumbnail=None):
    """Absolute avatar URL, optionally for a thumbnail size.

    When the related event has a custom domain, that domain is used as
    the base; otherwise ``settings.SITE_URL`` is used. ``event`` defaults
    to ``self.event`` when the model defines one (e.g. ``SpeakerProfile``).
    """
    if not self.profile_picture_id:
        return ""
    if event is None:
        event = getattr(self, "event", None)
    return self.profile_picture.get_avatar_url(event=event, thumbnail=thumbnail)

From LogMixin pretalx.common.models.mixins

delete(*args, log_kwargs=None, skip_log=False, **kwargs)source

on pretalx.common.models.mixins.LogMixin source

def delete(self, *args, log_kwargs=None, skip_log=False, **kwargs):
    parent = self.log_parent
    result = super().delete(*args, **kwargs)
    if (
        not skip_log
        and parent
        and getattr(parent, "log_action", None)
        and self.log_prefix
    ):
        log_kwargs = log_kwargs or {}
        parent.log_action(f"{self.log_prefix}.delete", **log_kwargs)
    return result

on pretalx.common.models.mixins.FileCleanupMixin source

def delete(self, *args, **kwargs):
    self.delete_files()
    return super().delete(*args, **kwargs)

on django.db.models.Model

def delete(self, using=None, keep_parents=False):
    if not self._is_pk_set():
        raise ValueError(
            "%s object can't be deleted because its %s attribute is set "
            "to None." % (self._meta.object_name, self._meta.pk.attname)
        )
    using = using or router.db_for_write(self.__class__, instance=self)
    collector = Collector(using=using, origin=self)
    collector.collect([self], keep_parents=keep_parents)
    return collector.delete()
log_action(action, data=None, person=None, orga=False, content_object=None, old_data=None, new_data=None)source
def log_action(
    self,
    action,
    data=None,
    person=None,
    orga=False,
    content_object=None,
    old_data=None,
    new_data=None,
):
    if self._state.adding or not isinstance(self.pk, int):
        return

    if action.startswith("."):
        if self.log_prefix:
            action = f"{self.log_prefix}{action}"
        else:
            return

    if old_data is not None or new_data is not None:
        from pretalx.common.log import (  # noqa: PLC0415 -- thin method
            compute_log_changes,
        )

        changes = compute_log_changes(old_data, new_data)
        if not changes and not data:
            return
        if changes:
            if data is None:
                data = {}
            data["changes"] = changes

    if data:
        if not isinstance(data, dict):
            raise TypeError(
                f"Logged data should always be a dictionary, not {type(data)}."
            )
        for key in data:
            if any(sensitive_key in key for sensitive_key in SENSITIVE_KEYS):
                data[key] = "********" if data[key] else data[key]
        data = json_roundtrip(data)

    return ActivityLog.objects.create(
        person=person,
        content_object=content_object or self,
        action_type=action,
        data=data,
        is_orga_action=orga,
        **self._log_event_kwargs(),
    )
logged_actions()source
def logged_actions(self):
    return (
        ActivityLog.objects.filter(
            content_type=ContentType.objects.get_for_model(type(self)),
            object_id=self.pk,
        )
        .select_related("event", "person")
        .prefetch_related("content_object")
    )

From FileCleanupMixin pretalx.common.models.mixins

delete_files()sourceSchedule cleanup of every uploaded file attached to this object.

Public hook: called by delete() and by anonymisation paths (e.g. person.domain.user.deactivate_user) that want to drop files without removing the row. Overridable by subclasses that need to recurse into related objects.

def delete_files(self):
    """Schedule cleanup of every uploaded file attached to this object.

    Public hook: called by ``delete()`` and by anonymisation paths
    (e.g. ``person.domain.user.deactivate_user``) that want to drop
    files without removing the row. Overridable by subclasses that
    need to recurse into related objects."""
    for field in self._file_fields:
        value = getattr(self, field, None)
        if not value:
            continue
        with suppress(Exception):
            self._schedule_file_cleanup(field=field, path=value.path)
process_image(field, generate_thumbnail=False)source
def process_image(self, field, generate_thumbnail=False):
    task_process_image.apply_async(
        kwargs={
            "field": field,
            "model": self._meta.model_name.capitalize(),
            "pk": self.pk,
            "generate_thumbnail": generate_thumbnail,
        },
        countdown=10,
    )

From Model django.db.models

Standard Django API, unchanged — full_clean(), clean(), adelete() and 20 more.
adeletearefresh_from_dbasavecleanclean_fieldsdate_error_messagefull_cleanget_constraintsget_deferred_fieldsprepare_database_saverefresh_from_dbsave_baseserializable_valueunique_error_messagevalidate_constraintsvalidate_unique__eq____getstate____hash____init____reduce____repr____setstate__

Added at runtime

Added by Django or another library dynamically after class creation.
get_locale_displayget_origin_display