Code reference › models › submission

class

CfP

pretalx.submission.models.cfp.CfP source

Every Event has one Call for Papers/Participation/Proposals.

Fields 11

idAutoFieldpk
eventOneToOneFielduniqueEventsource
event = models.OneToOneField(to="event.Event", on_delete=models.PROTECT)
headlineI18nCharFieldnullsource
headline = I18nCharField(
    max_length=300, null=True, blank=True, verbose_name=_("headline")
)
textI18nTextFieldnullsourceYou can use <a href="https://docs.pretalx.org/user/markdown/" target="_blank" rel="noopener">Markdown</a> here.
text = I18nTextField(
    null=True,
    blank=True,
    verbose_name=pgettext_lazy("CfP text body", "text"),
    help_text=phrases.base.use_markdown,
)
default_typeForeignKeySubmissionTypesource
default_type = models.ForeignKey(
    to="submission.SubmissionType",
    on_delete=models.PROTECT,
    related_name="+",
    verbose_name=_("Default session type"),
)
openingDateTimeFieldnullsourcePlease put in the date you want to start accepting proposals from users. Leave the date empty to start accepting proposals immediately once the event is live.
opening = DateTimeField(
    null=True,
    blank=True,
    verbose_name=_("Opening"),
    help_text=_(
        "Please put in the date you want to start accepting proposals from users. "
        "Leave the date empty to start accepting proposals immediately once the event is live."
    ),
)
deadlineDateTimeFieldnullsourcePlease put in the last date you want to accept proposals from users.
deadline = DateTimeField(
    null=True,
    blank=True,
    verbose_name=_("Deadline"),
    help_text=_(
        "Please put in the last date you want to accept proposals from users."
    ),
)
settingsJSONFieldsource
settings = models.JSONField(default=default_settings)
fieldsJSONFieldsource
fields = models.JSONField(default=default_fields)
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.cfp'
Inherited attributes 2
log_parentNoneLogMixin
objects<django_scopes.manager.ScopedManager.<locals>.Manager>PretalxModel

Properties 41

Defined here

after_deadlinesourceReturns True if a (maximum) deadline is set and the current time is after that deadline.
@cached_property
def after_deadline(self) -> bool:
    """Returns True if a (maximum) deadline is set and the current time is after that deadline."""
    return bool(self.deadline and self.max_deadline and self.max_deadline < now())
before_openingsourceReturns True if an opening date is set and the current time is before that date.
@cached_property
def before_opening(self) -> bool:
    """Returns True if an opening date is set and the current time is before that date."""
    return bool(self.opening and now() < self.opening)
is_opensourceReturns True if the CfP is currently open, i.e.

not before the opening date and not after the deadline.

@cached_property
def is_open(self) -> bool:
    """Returns True if the CfP is currently open, i.e. not before the opening date and not after the deadline."""
    return not (self.before_opening or self.after_deadline)
is_open_without_access_codesource
@cached_property
def is_open_without_access_code(self) -> bool:
    from pretalx.submission.domain.cfp import (  # noqa: PLC0415 -- thin method
        can_submit_without_access_code,
    )

    return self.is_open and can_submit_without_access_code(self.event)
max_deadlinesourceReturns the latest date any submission is possible.

This includes the deadlines set on any submission type for this event.

@cached_property
def max_deadline(self) -> dt.datetime:
    """Returns the latest date any submission is possible.

    This includes the deadlines set on any submission type for this event.
    """
    deadlines = list(
        self.event.submission_types.filter(deadline__isnull=False).values_list(
            "deadline", flat=True
        )
    )
    if self.deadline:
        deadlines.append(self.deadline)
    return max(deadlines) if deadlines else None
max_speakerssource
@property
def max_speakers(self) -> int | None:
    field = (
        self.fields.get("additional_speaker")
        or default_fields()["additional_speaker"]
    )
    return field.get("max")
request_abstractsource
def is_field_requested(self, field):
    return (
        self.fields.get(field, default_fields()[field])["visibility"]
        != "do_not_ask"
    )
request_additional_speakersource
def is_field_requested(self, field):
    return (
        self.fields.get(field, default_fields()[field])["visibility"]
        != "do_not_ask"
    )
request_availabilitiessource
def is_field_requested(self, field):
    return (
        self.fields.get(field, default_fields()[field])["visibility"]
        != "do_not_ask"
    )
request_avatarsource
def is_field_requested(self, field):
    return (
        self.fields.get(field, default_fields()[field])["visibility"]
        != "do_not_ask"
    )
request_biographysource
def is_field_requested(self, field):
    return (
        self.fields.get(field, default_fields()[field])["visibility"]
        != "do_not_ask"
    )
request_content_localesource
def is_field_requested(self, field):
    return (
        self.fields.get(field, default_fields()[field])["visibility"]
        != "do_not_ask"
    )
request_descriptionsource
def is_field_requested(self, field):
    return (
        self.fields.get(field, default_fields()[field])["visibility"]
        != "do_not_ask"
    )
request_do_not_recordsource
def is_field_requested(self, field):
    return (
        self.fields.get(field, default_fields()[field])["visibility"]
        != "do_not_ask"
    )
request_durationsource
def is_field_requested(self, field):
    return (
        self.fields.get(field, default_fields()[field])["visibility"]
        != "do_not_ask"
    )
request_imagesource
def is_field_requested(self, field):
    return (
        self.fields.get(field, default_fields()[field])["visibility"]
        != "do_not_ask"
    )
request_namesource
def is_field_requested(self, field):
    return (
        self.fields.get(field, default_fields()[field])["visibility"]
        != "do_not_ask"
    )
request_notessource
def is_field_requested(self, field):
    return (
        self.fields.get(field, default_fields()[field])["visibility"]
        != "do_not_ask"
    )
request_resourcessource
def is_field_requested(self, field):
    return (
        self.fields.get(field, default_fields()[field])["visibility"]
        != "do_not_ask"
    )
request_submission_typesource
def is_field_requested(self, field):
    return (
        self.fields.get(field, default_fields()[field])["visibility"]
        != "do_not_ask"
    )
request_tagssource
def is_field_requested(self, field):
    return (
        self.fields.get(field, default_fields()[field])["visibility"]
        != "do_not_ask"
    )
request_titlesource
def is_field_requested(self, field):
    return (
        self.fields.get(field, default_fields()[field])["visibility"]
        != "do_not_ask"
    )
request_tracksource
def is_field_requested(self, field):
    return (
        self.fields.get(field, default_fields()[field])["visibility"]
        != "do_not_ask"
    )
require_abstractsource
def is_field_required(self, field):
    return (
        self.fields.get(field, default_fields()[field])["visibility"] == "required"
    )
require_additional_speakersource
def is_field_required(self, field):
    return (
        self.fields.get(field, default_fields()[field])["visibility"] == "required"
    )
require_availabilitiessource
def is_field_required(self, field):
    return (
        self.fields.get(field, default_fields()[field])["visibility"] == "required"
    )
require_avatarsource
def is_field_required(self, field):
    return (
        self.fields.get(field, default_fields()[field])["visibility"] == "required"
    )
require_biographysource
def is_field_required(self, field):
    return (
        self.fields.get(field, default_fields()[field])["visibility"] == "required"
    )
require_content_localesource
def is_field_required(self, field):
    return (
        self.fields.get(field, default_fields()[field])["visibility"] == "required"
    )
require_descriptionsource
def is_field_required(self, field):
    return (
        self.fields.get(field, default_fields()[field])["visibility"] == "required"
    )
require_do_not_recordsource
def is_field_required(self, field):
    return (
        self.fields.get(field, default_fields()[field])["visibility"] == "required"
    )
require_durationsource
def is_field_required(self, field):
    return (
        self.fields.get(field, default_fields()[field])["visibility"] == "required"
    )
require_imagesource
def is_field_required(self, field):
    return (
        self.fields.get(field, default_fields()[field])["visibility"] == "required"
    )
require_namesource
def is_field_required(self, field):
    return (
        self.fields.get(field, default_fields()[field])["visibility"] == "required"
    )
require_notessource
def is_field_required(self, field):
    return (
        self.fields.get(field, default_fields()[field])["visibility"] == "required"
    )
require_resourcessource
def is_field_required(self, field):
    return (
        self.fields.get(field, default_fields()[field])["visibility"] == "required"
    )
require_submission_typesource
def is_field_required(self, field):
    return (
        self.fields.get(field, default_fields()[field])["visibility"] == "required"
    )
require_tagssource
def is_field_required(self, field):
    return (
        self.fields.get(field, default_fields()[field])["visibility"] == "required"
    )
require_titlesource
def is_field_required(self, field):
    return (
        self.fields.get(field, default_fields()[field])["visibility"] == "required"
    )
require_tracksource
def is_field_required(self, field):
    return (
        self.fields.get(field, default_fields()[field])["visibility"] == "required"
    )
tag_limitssource
@property
def tag_limits(self) -> tuple[int | None, int | None]:
    field = self.fields.get("tags") or default_fields()["tags"]
    return field.get("min"), field.get("max")

Methods 32

Defined here

copy_data_from(other_cfp, skip_attributes=None)source
def copy_data_from(self, other_cfp, skip_attributes=None):
    # default_type gets set by pretalx.event.domain.event.copy_event_data
    clonable_attributes = [
        "headline",
        "text",
        "opening",
        "deadline",
        "settings",
        "fields",
    ]
    if skip_attributes:
        clonable_attributes = [
            attr for attr in clonable_attributes if attr not in skip_attributes
        ]
    for field in clonable_attributes:
        setattr(self, field, getattr(other_cfp, field))
    self.save()
__str__() -> strsourceReturn str(self).

on pretalx.submission.models.cfp.CfP source

def __str__(self) -> str:
    return f"CfP(event={self.event.slug})"

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