pretalx.submission.domain.queries.submission
Functions 24
annotate_assigned_reviews(queryset, event, user)source
def annotate_assigned_reviews(queryset, event, user):
if "is_assigned" in queryset.query.annotations:
return queryset
assigned = user.assigned_reviews.filter(event=event, pk=OuterRef("pk"))
return queryset.annotate(is_assigned=Exists(Subquery(assigned)))
annotate_confirmed_signup_count(queryset, target=None)source
def annotate_confirmed_signup_count(queryset, target=None):
if "_annotated_confirmed_signup_count" in queryset.query.annotations:
return queryset
prefix = f"{target}__" if target else ""
relation = f"{prefix}attendee_signups"
return queryset.annotate(
_annotated_confirmed_signup_count=Count(
relation,
filter=Q(**{f"{relation}__state": AttendeeSignupStates.CONFIRMED}),
distinct=True,
)
)
annotate_requires_signup(queryset, target=None)source
def annotate_requires_signup(queryset, target=None):
if "_annotated_requires_signup" in queryset.query.annotations:
return queryset
prefix = f"{target}__" if target else ""
return queryset.annotate(
_annotated_requires_signup=Case(
When(**{f"{prefix}attendee_signup_required": True}, then=Value(True)),
When(**{f"{prefix}attendee_signup_required": False}, then=Value(False)),
When(
**{f"{prefix}track__attendee_signup_required": True}, then=Value(True)
),
When(
**{f"{prefix}submission_type__attendee_signup_required": True},
then=Value(True),
),
default=Value(False),
output_field=BooleanField(),
)
)
annotate_slot_confirmed_signup_count(slot_queryset)source
def annotate_slot_confirmed_signup_count(slot_queryset):
return annotate_confirmed_signup_count(slot_queryset, target="submission")
annotate_slot_requires_signup(slot_queryset)source
def annotate_slot_requires_signup(slot_queryset):
return annotate_requires_signup(slot_queryset, target="submission")
annotate_slot_signup_status(slot_queryset)source
def annotate_slot_signup_status(slot_queryset):
if "_annotated_signup_status" in slot_queryset.query.annotations:
return slot_queryset
slot_queryset = annotate_slot_requires_signup(slot_queryset)
slot_queryset = annotate_slot_confirmed_signup_count(slot_queryset)
slot_queryset = slot_queryset.annotate(
_annotated_signup_capacity=Coalesce(
"submission__attendee_signup_capacity", "room__capacity"
)
)
return slot_queryset.annotate(_annotated_signup_status=_signup_status_case())
annotate_submission_count(queryset, states=None)sourceCount related non-draft submissions, optionally scoped to states.
def annotate_submission_count(queryset, states=None):
"""Count related non-draft submissions, optionally scoped to ``states``."""
if states:
state_filter = Q(submissions__state__in=states)
else:
state_filter = ~Q(submissions__state=SubmissionStates.DRAFT)
return queryset.annotate(
submission_count=Count("submissions", distinct=True, filter=state_filter)
)
annotate_submission_signup_status(queryset, current_schedule)source
def annotate_submission_signup_status(queryset, current_schedule):
if "_annotated_signup_status" in queryset.query.annotations:
return queryset
queryset = annotate_requires_signup(queryset)
queryset = annotate_confirmed_signup_count(queryset)
if current_schedule is not None:
room_capacity = (
TalkSlot.objects.filter(
submission=OuterRef("pk"), schedule=current_schedule
)
.order_by("pk")
.values("room__capacity")[:1]
)
queryset = queryset.annotate(
_annotated_signup_capacity=Coalesce(
"attendee_signup_capacity", Subquery(room_capacity)
)
)
else:
queryset = queryset.annotate(
_annotated_signup_capacity=F("attendee_signup_capacity")
)
return queryset.annotate(_annotated_signup_status=_signup_status_case())
featured_submissions(event)sourceRender queryset for the public featured-sessions page of event.
def featured_submissions(event):
"""Render queryset for the public featured-sessions page of ``event``."""
return (
event.submissions.filter(is_featured=True)
.exclude(state__in=FEATURED_HIDDEN_STATES)
.select_related("event", "submission_type", "track")
.with_sorted_speakers()
.order_by("title")
)
filter_submissions_by_state(qs, state_filter)sourceFilter by an iterable of state values.
Values prefixed with pending_state__ (e.g. pending_state__accepted) match against the pending_state column instead of state; both kinds can be mixed and produce a disjunction.
def filter_submissions_by_state(qs, state_filter):
"""Filter by an iterable of state values.
Values prefixed with ``pending_state__`` (e.g. ``pending_state__accepted``)
match against the ``pending_state`` column instead of ``state``; both kinds
can be mixed and produce a disjunction.
"""
states = [s for s in state_filter if not s.startswith("pending_state__")]
pending = [
s.removeprefix("pending_state__")
for s in state_filter
if s.startswith("pending_state__")
]
if states and pending:
return qs.filter(Q(state__in=states) | Q(pending_state__in=pending))
if states:
return qs.filter(state__in=states)
if pending:
return qs.filter(pending_state__in=pending)
return qs
has_featured_submissions(event)sourceCheap existence check: does event have any visible featured rows?
def has_featured_submissions(event):
"""Cheap existence check: does ``event`` have any visible featured rows?"""
return (
event.submissions.filter(is_featured=True)
.exclude(state__in=FEATURED_HIDDEN_STATES)
.exists()
)
information_for_user(event, user)sourceReturn the SpeakerInformation entries this user is allowed to see.
A user can see an information item when they have a submission on the event whose track, submission type, and state match the item's filters and target group. Items with empty limit_tracks / limit_types impose no constraint on those axes.
def information_for_user(event, user):
"""Return the SpeakerInformation entries this user is allowed to see.
A user can see an information item when they have a submission on the
event whose track, submission type, and state match the item's filters
and target group. Items with empty ``limit_tracks`` / ``limit_types``
impose no constraint on those axes.
"""
if not user or user.is_anonymous:
return event.information.none()
track_links = SpeakerInformation.limit_tracks.through.objects.filter(
speakerinformation_id=OuterRef(OuterRef("pk"))
)
type_links = SpeakerInformation.limit_types.through.objects.filter(
speakerinformation_id=OuterRef(OuterRef("pk"))
)
user_subs = (
event.submissions.filter(speakers__user=user)
.filter(Q(track__in=track_links.values("track_id")) | ~Exists(track_links))
.filter(
Q(submission_type__in=type_links.values("submissiontype_id"))
| ~Exists(type_links)
)
)
return event.information.alias(
_has_submitter=Exists(user_subs),
_has_confirmed=Exists(user_subs.filter(state=SubmissionStates.CONFIRMED)),
_has_accepted=Exists(
user_subs.filter(state__in=list(SubmissionStates.accepted_states))
),
).filter(
Q(target_group="submitters", _has_submitter=True)
| Q(target_group="confirmed", _has_confirmed=True)
| Q(target_group="accepted", _has_accepted=True)
)
reviewable_submissions_for_user(event, user)sourceReturns all submissions the user is permitted to review right now.
Excludes submissions this user has submitted, and takes track team permissions, assignments and review phases into account. The result is ordered by review count.
def reviewable_submissions_for_user(event, user):
"""Returns all submissions the user is permitted to review right now.
Excludes submissions this user has submitted, and takes track team permissions,
assignments and review phases into account. The result is ordered by review count.
"""
queryset = submissions_for_user(
event, user, context=SubmissionContext.REVIEW
).filter(state=SubmissionStates.SUBMITTED)
queryset = annotate_review_count(queryset)
# Randomise within each priority tier so that "save and next" doesn't
# always hand reviewers the same deterministic sequence of proposals.
return queryset.order_by("-is_assigned", "review_count", "?")
search_submissions(qs, query, *, can_view_speakers, fulltext=False)sourceFree-text search over submissions.
With can_view_speakers=False the search honours anonymisation: redacted fields are matched against the anonymised value instead of the original. With fulltext=True the search expands to abstract/description/notes/ internal_notes in addition to the per-permission default fields.
def search_submissions(qs, query, *, can_view_speakers, fulltext=False):
"""Free-text search over submissions.
With ``can_view_speakers=False`` the search honours anonymisation: redacted
fields are matched against the anonymised value instead of the original.
With ``fulltext=True`` the search expands to abstract/description/notes/
internal_notes in addition to the per-permission default fields.
"""
if not query:
return qs
fields = ["code__icontains", "title__icontains"]
if fulltext:
fields += [
"description__icontains",
"abstract__icontains",
"notes__icontains",
"internal_notes__icontains",
]
if not can_view_speakers:
return _anonymised_search(qs, query, fields)
filters = speaker_search_q(query, prefix="speakers__", include_email=False)
for field in fields:
filters |= Q(**{field: query})
return qs.filter(filters)
signed_up_submission_codes(event, user)source
def signed_up_submission_codes(event, user):
if user.is_anonymous:
return set()
return set(
signed_up_submissions_for_user(event, user)
.values_list("code", flat=True)
.distinct()
)
signed_up_submissions_for_user(event, user)source
def signed_up_submissions_for_user(event, user):
if user.is_anonymous:
return event.submissions.none()
return submissions_for_user(event, user, context=SubmissionContext.PUBLIC).filter(
attendee_signups__attendee__user=user,
attendee_signups__state=AttendeeSignupStates.CONFIRMED,
)
sorted_speakers_prefetch(prefix='')sourcePrefetch for speakers ordered by their speaking position.
Use prefix="submission__" when prefetching from slot querysets.
def sorted_speakers_prefetch(prefix=""):
"""Prefetch for speakers ordered by their speaking position.
Use prefix="submission__" when prefetching from slot querysets.
"""
lookup = f"{prefix}speakers" if prefix else "speakers"
return Prefetch(
lookup,
queryset=SpeakerProfile.objects.select_related(
"profile_picture", "event", "user"
).order_by("speaker_roles__position"),
)
speaker_search_q(query, prefix='', include_email=True)source
def speaker_search_q(query, prefix="", include_email=True):
fields = ["name", "user__name"]
if include_email:
fields += ["email", "user__email"]
filters = Q()
for field in fields:
filters |= Q(**{f"{prefix}{field}__icontains": query})
return filters
submission_field_counts(qs, field)sourceGroup qs by field and return {value: count}.
Used by filter facets to display per-bucket counts alongside choices.
def submission_field_counts(qs, field):
"""Group ``qs`` by ``field`` and return ``{value: count}``.
Used by filter facets to display per-bucket counts alongside choices.
"""
return dict(qs.order_by(field).values_list(field).annotate(Count(field)))
submission_state_facets(event, *, usable_states=None)sourceCounts of submissions per state plus per pending state.
Returns {state_value: count, "pending_state__<value>": count}. Both buckets honour usable_states so a "Pending accepted" total that included submissions in unrelated states would not mislead.
def submission_state_facets(event, *, usable_states=None):
"""Counts of submissions per state plus per pending state.
Returns ``{state_value: count, "pending_state__<value>": count}``.
Both buckets honour ``usable_states`` so a "Pending accepted" total
that included submissions in unrelated states would not mislead.
"""
qs = event.submissions.all()
if usable_states:
qs = qs.filter(state__in=usable_states)
counts = submission_field_counts(qs, "state")
counts.update(
{
f"pending_state__{value}": count
for value, count in submission_field_counts(
qs.filter(pending_state__isnull=False), "pending_state"
).items()
}
)
return counts
submissions_for_reviewer(queryset, event, user)source
def submissions_for_reviewer(queryset, event, user):
if user.is_anonymous or "is_reviewer" not in user.get_permissions_for_event(event):
return event.submissions.none()
if not (phase := event.active_review_phase):
queryset = event.submissions.none()
queryset = queryset.exclude(speakers__user=user)
queryset = annotate_assigned_reviews(queryset, event, user)
if phase and phase.proposal_visibility == "assigned":
return queryset.filter(is_assigned__gte=1)
reviewer_tracks = user.get_reviewer_tracks(event)
if reviewer_tracks is not None:
queryset = queryset.filter(track__in=reviewer_tracks)
return queryset
submissions_for_user(event, user, context=<SubmissionContext.GENERAL: 'general'>)sourceReturn the Submission queryset a user may see for this event.
Always returns a Submission queryset. The context determines which of the user's roles apply (see SubmissionContext):
PUBLIC: everybody, including orga members and reviewers, gets sessions from the public schedule. Use for schedule features like favourites.
REVIEW: reviewers get their reviewable proposals; other orga members every proposal but their own as they must not see reviews on those.
GENERAL: reviewers with limited perms get their reviewable proposals, other organisers get all submissions, everybody else gets public ones.
def submissions_for_user(event, user, context=SubmissionContext.GENERAL):
"""Return the ``Submission`` queryset a user may see for this event.
Always returns a ``Submission`` queryset. The ``context`` determines
which of the user's roles apply (see ``SubmissionContext``):
- PUBLIC: everybody, including orga members and reviewers, gets sessions
from the public schedule. Use for schedule features like favourites.
- REVIEW: reviewers get their reviewable proposals; other orga members
every proposal but their own as they must not see reviews on those.
- GENERAL: reviewers with limited perms get their reviewable proposals,
other organisers get all submissions, everybody else gets public ones.
"""
if context != SubmissionContext.PUBLIC and not user.is_anonymous:
if is_only_reviewer(user, event):
return submissions_for_reviewer(
event.submissions.all(), event, user
).with_display_data()
if user.has_perm("submission.orga_list_submission", event):
queryset = event.submissions.all()
if context == SubmissionContext.REVIEW:
if user.pk in event.reviewers.values_list("pk", flat=True):
queryset = submissions_for_reviewer(queryset, event, user)
else:
queryset = annotate_assigned_reviews(
queryset.exclude(speakers__user=user), event, user
)
return queryset.with_display_data()
# Fall through: anon users and authenticated users without
# orga/reviewer permissions (e.g. speakers or attendees).
if user.has_perm("schedule.list_schedule", event) and event.current_schedule:
return event.current_schedule.slots
return event.submissions.none()
talks_for_event(event)sourceSubmissions that have a slot in event's current released schedule.
Returns an empty queryset before the first schedule release.
def talks_for_event(event):
"""Submissions that have a slot in ``event``'s current released schedule.
Returns an empty queryset before the first schedule release.
"""
if event.current_schedule:
return (
event.submissions.filter(slots__in=event.current_schedule.scheduled_talks)
.select_related("submission_type")
.with_sorted_speakers()
)
return event.submissions.none()
unreviewed_submissions_for_user(event, user)sourceReviewable submissions that the user has not yet reviewed.
def unreviewed_submissions_for_user(event, user):
"""Reviewable submissions that the user has not yet reviewed."""
return reviewable_submissions_for_user(event, user).exclude(reviews__user=user)