Availability
pretalx.schedule.models.availability.Availability source
The Availability class models when people or rooms are available for TalkSlot objects.
Fields 8
idAutoFieldpkeventForeignKeysource
event = models.ForeignKey(
to="event.Event", related_name="availabilities", on_delete=models.CASCADE
)
personForeignKeynullsource
person = models.ForeignKey(
to="person.SpeakerProfile",
related_name="availabilities",
on_delete=models.CASCADE,
null=True,
blank=True,
)
roomForeignKeynullsource
room = models.ForeignKey(
to="schedule.Room",
related_name="availabilities",
on_delete=models.CASCADE,
null=True,
blank=True,
)
startDateTimeFieldsource
start = models.DateTimeField()
endDateTimeFieldsource
end = models.DateTimeField()
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
)
Inherited attributes 3
log_parentNoneLogMixinlog_prefixNoneLogMixinobjects<django_scopes.manager.ScopedManager.<locals>.Manager>PretalxModelProperties 1
Defined here
all_daysourceChecks if the Availability spans one (or, technically: multiple) complete day.
@cached_property
def all_day(self) -> bool:
"""Checks if the Availability spans one (or, technically: multiple)
complete day."""
return self.start.time() == zerotime and self.end.time() == zerotime
Methods 42
Defined here
contains(other: 'Availability') -> boolsourceTests if this availability starts before and ends after the other.
def contains(self, other: "Availability") -> bool:
"""Tests if this availability starts before and ends after the other."""
return self.start <= other.start and self.end >= other.end
intersect_with(other: 'Availability') -> 'Availability'sourceReturn a new Availability which spans the range covered both by this one and the given one.
def intersect_with(self, other: "Availability") -> "Availability":
"""Return a new Availability which spans the range covered both by this
one and the given one."""
if not isinstance(other, Availability):
raise TypeError("Please provide an Availability object.")
if not other.overlaps(self, False):
raise ValueError("Only overlapping Availabilities can be intersected.")
return Availability(
start=max(self.start, other.start),
end=min(self.end, other.end),
event_id=getattr(self, "event_id", None),
person_id=getattr(self, "person_id", None),
room_id=getattr(self, "room_id", None),
)
merge_with(other: 'Availability') -> 'Availability'sourceReturn a new Availability which spans the range of this one and the given one.
def merge_with(self, other: "Availability") -> "Availability":
"""Return a new Availability which spans the range of this one and the given one."""
if not isinstance(other, Availability):
raise TypeError("Please provide an Availability object.")
if not other.overlaps(self, strict=False):
raise ValueError("Only overlapping Availabilities can be merged.")
return Availability(
start=min(self.start, other.start),
end=max(self.end, other.end),
event_id=getattr(self, "event_id", None),
person_id=getattr(self, "person_id", None),
room_id=getattr(self, "room_id", None),
)
overlaps(other: 'Availability', strict: bool) -> boolsourceTest if two Availabilities overlap.
Strict mode only counts real overlap, otherwise direct adjacency is also counted as overlap.
def overlaps(self, other: "Availability", strict: bool) -> bool:
"""Test if two Availabilities overlap.
Strict mode only counts real overlap, otherwise direct adjacency is
also counted as overlap.
"""
if not isinstance(other, Availability):
raise TypeError("Please provide an Availability object")
if strict:
return (
(self.start <= other.start < self.end)
or (self.start < other.end <= self.end)
or (other.start <= self.start < other.end)
or (other.start < self.end <= other.end)
)
return (
(self.start <= other.start <= self.end)
or (self.start <= other.end <= self.end)
or (other.start <= self.start <= other.end)
or (other.start <= self.end <= other.end)
)
serialize(full=True) -> dictsource
def serialize(self, full=True) -> dict:
result = {"start": self.start.isoformat(), "end": self.end.isoformat()}
if full:
result["id"] = self.id
result["allDay"] = self.all_day
return result
__and__(other: 'Availability') -> 'Availability'sourcePerforms the intersect operation: availability1 & availability2
def __and__(self, other: "Availability") -> "Availability":
"""Performs the intersect operation: ``availability1 & availability2``"""
return self.intersect_with(other)
__eq__(other: 'Availability') -> boolsourceComparisons like availability1 == availability2.
on pretalx.schedule.models.availability.Availability source
Checks if event, person, room, start and end are the same.
def __eq__(self, other: "Availability") -> bool:
"""Comparisons like ``availability1 == availability2``.
Checks if ``event``, ``person``, ``room``, ``start`` and ``end``
are the same.
"""
return all(
getattr(self, attribute, None) == getattr(other, attribute, None)
for attribute in ("person", "room", "start", "end")
)
Return self==value.
def __eq__(self, other):
if not isinstance(other, Model):
return NotImplemented
if self._meta.concrete_model != other._meta.concrete_model:
return False
my_pk = self.pk
if my_pk is None:
return self is other
return my_pk == other.pk
__hash__()sourceReturn hash(self).
on pretalx.schedule.models.availability.Availability source
def __hash__(self):
return hash((self.person, self.room, self.start, self.end))
Return hash(self).
def __hash__(self):
if not self._is_pk_set():
raise TypeError("Model instances without primary key value are unhashable")
return hash(self.pk)
__or__(other: 'Availability') -> 'Availability'sourcePerforms the merge operation: availability1 | availability2
def __or__(self, other: "Availability") -> "Availability":
"""Performs the merge operation: ``availability1 | availability2``"""
return self.merge_with(other)
__str__() -> strsourceReturn str(self).
on pretalx.schedule.models.availability.Availability source
def __str__(self) -> str:
person = self.person.get_display_name() if self.person else None
room = getattr(self.room, "name", None)
event = getattr(getattr(self, "event", None), "slug", None)
return f"Availability(event={event}, person={person}, room={room})"
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)
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
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,
)