Code reference › models › submission

class

ReviewPhase

pretalx.submission.models.review.ReviewPhase source

ReviewPhases determine reviewer access rights during a (potentially open) time frame.

Phases are ordered by (start, end), with null-start phases first.

Fields 16

idAutoFieldpk
eventForeignKeyEventsource
event = models.ForeignKey(
    to="event.Event", related_name="review_phases", on_delete=models.CASCADE
)
nameCharFieldsource
name = models.CharField(verbose_name=_("Name"), max_length=100)
startDateTimeFieldnullsource
start = DateTimeField(verbose_name=_("Phase start"), null=True, blank=True)
endDateTimeFieldnullsource
end = DateTimeField(verbose_name=_("Phase end"), null=True, blank=True)
is_activeBooleanFieldsource
is_active = models.BooleanField(default=False)
can_reviewBooleanFieldsource
can_review = models.BooleanField(
    verbose_name=_("Reviewers can write and edit reviews"), default=True
)
proposal_visibilityCharFieldsourceIf you select “all”, reviewers can review all proposals that their teams have access to (so either all, or specific tracks). In this mode, assigned proposals will be highlighted and will be shown first in the review workflow.
proposal_visibility = models.CharField(
    verbose_name=_("Reviewers may see these proposals"),
    choices=(("all", _("All")), ("assigned", _("Only assigned proposals"))),
    max_length=8,
    default="all",
    help_text=_(
        "If you select “all”, reviewers can review all proposals that their teams have access to (so either all, or specific tracks). "
        "In this mode, assigned proposals will be highlighted and will be shown first in the review workflow. "
    ),
)
can_see_other_reviewsCharFieldsource
can_see_other_reviews = models.CharField(
    verbose_name=_("Reviewers can see other reviews"),
    max_length=12,
    choices=(
        ("always", _("Always")),
        ("never", _("Never")),
        ("after_review", _("After reviewing the proposal")),
    ),
    default="after_review",
)
can_see_speaker_namesBooleanFieldsource
can_see_speaker_names = models.BooleanField(
    verbose_name=_("Reviewers can see speaker names"), default=True
)
can_see_reviewer_namesBooleanFieldsource
can_see_reviewer_names = models.BooleanField(
    verbose_name=_("Reviewers can see the names of other reviewers"), default=True
)
can_change_submission_stateBooleanFieldsource
can_change_submission_state = models.BooleanField(
    verbose_name=_("Reviewers can accept and reject proposals"), default=False
)
can_tag_submissionsCharFieldsource
can_tag_submissions = models.CharField(
    verbose_name=_("Reviewers can tag proposals"),
    max_length=12,
    choices=(
        ("never", _("Never")),
        ("use_tags", _("Add and remove existing tags")),
        ("create_tags", _("Add, remove and create tags")),
    ),
    default="never",
)
speakers_can_change_submissionsBooleanFieldsourceBy default, modification of proposals is locked after the CfP ends, and is re-enabled once the proposal was accepted.
speakers_can_change_submissions = models.BooleanField(
    verbose_name=_("Speakers can modify their proposals before acceptance"),
    help_text=_(
        "By default, modification of proposals is locked after the CfP ends, and is re-enabled once the proposal was accepted."
    ),
    default=False,
)
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
)

Attributes 1

log_prefix'pretalx.review_phase'
Inherited attributes 2
log_parentNoneLogMixin
objects<django_scopes.manager.ScopedManager.<locals>.Manager>PretalxModel

Properties 1

Defined here

log_parentsource
@property
def log_parent(self):
    return self.event

Methods 34

Defined here

clean()sourceHook for doing any extra model-wide validation after clean() has been called on every field by self.clean_fields.

on pretalx.submission.models.review.ReviewPhase source

Any ValidationError raised by this method will not be associated with a particular field; it will have a special-case association with the field defined by NON_FIELD_ERRORS.

def clean(self):
    super().clean()
    if self.start and self.end and self.start > self.end:
        raise ValidationError(
            {"end": _("The end of a phase has to be after its start.")}
        )

on django.db.models.Model

Hook for doing any extra model-wide validation after clean() has been called on every field by self.clean_fields. Any ValidationError raised by this method will not be associated with a particular field; it will have a special-case association with the field defined by NON_FIELD_ERRORS.

def clean(self):
    """
    Hook for doing any extra model-wide validation after clean() has been
    called on every field by self.clean_fields. Any ValidationError raised
    by this method will not be associated with a particular field; it will
    have a special-case association with the field defined by
    NON_FIELD_ERRORS.
    """
    pass
__str__()sourceReturn str(self).

on pretalx.submission.models.review.ReviewPhase source

def __str__(self):
    return self.name

on django.db.models.Model

Return str(self).

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

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()
get_instance_data()sourceGet 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
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,
    )
save(*args, **kwargs)source

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

From Model django.db.models

Standard Django API, unchanged — full_clean(), adelete(), arefresh_from_db() and 19 more.
adeletearefresh_from_dbasaveclean_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_can_see_other_reviews_displayget_can_tag_submissions_displayget_proposal_visibility_display