Source code for pretalx.event.models.organiser

import json
import string

from django.core.validators import RegexValidator
from django.db import models, transaction
from django.utils.crypto import get_random_string
from django.utils.functional import cached_property
from django.utils.translation import get_language
from django.utils.translation import gettext_lazy as _
from django_scopes import scope
from i18nfield.fields import I18nCharField

from pretalx.common.models.mixins import PretalxModel
from pretalx.common.urls import EventUrls, build_absolute_uri
from pretalx.event.models.event import FULL_SLUG_REGEX
from pretalx.person.models import User


def check_access_permissions(organiser):
    """We run this method when team permissions are changed, inside a transaction.

    We need to make sure that after the change is made, there is still somebody who has
    administrator access to every event and the organiser itself.
    """
    warnings = []
    teams = (
        organiser.teams.all()
        .annotate(member_count=models.Count("members"))
        .filter(member_count__gt=0)
    )
    if not [t for t in teams if t.can_change_teams]:
        raise Exception(
            _(
                "There must be at least one team with the permission to change teams, as otherwise nobody can create new teams or grant permissions to existing teams."
            )
        )
    if not [t for t in teams if t.can_create_events]:
        warnings.append(
            (
                "no_can_create_events",
                _("Nobody on your teams has the permission to create new events."),
            )
        )
    if not [t for t in teams if t.can_change_organiser_settings]:
        warnings.append(
            (
                "no_can_change_organiser_settings",
                _(
                    "Nobody on your teams has the permission to change organiser-level settings."
                ),
            )
        )

    for event in organiser.events.all():
        event_teams = (
            event.teams.all()
            .annotate(member_count=models.Count("members"))
            .filter(member_count__gt=0)
        )
        if not event_teams:
            raise Exception(
                str(
                    _(
                        "There must be at least one team with access to every event. Currently, nobody has access to {event_name}."
                    )
                ).format(event_name=event.name)
            )
        if not [t for t in event_teams if t.can_change_event_settings]:
            warnings.append(
                (
                    "no_can_cange_event_settings",
                    str(
                        _(
                            "Nobody on your teams has the permissions to change settings for the event {event_name}"
                        )
                    ).format(event_name=event.name),
                )
            )
    return warnings


[docs] class Organiser(PretalxModel): """The Organiser model represents the entity responsible for at least one. :class:`~pretalx.event.models.event.Event`. """ name = I18nCharField(max_length=190, verbose_name=_("Name")) slug = models.SlugField( max_length=50, db_index=True, unique=True, validators=[ RegexValidator( regex=FULL_SLUG_REGEX, message=_( "The slug may only contain letters, numbers, dots and dashes." ), ) ], verbose_name=_("Short form"), help_text=_( "Should be short, only contain lowercase letters and numbers, and must be unique, as it is used in URLs." ), ) objects = models.Manager() def __str__(self) -> str: """Used in generated forms.""" return str(self.name) class orga_urls(EventUrls): base = "/orga/organiser/{self.slug}/" settings = "/orga/organiser/{self.slug}/settings/" delete = "{settings}delete" teams = "{base}teams/" new_team = "{teams}new" user_search = "{base}api/users"
[docs] @transaction.atomic def shred(self, person=None): """Irrevocably deletes the organiser and all related events and their data.""" from pretalx.common.models import ActivityLog ActivityLog.objects.create( person=person, action_type="pretalx.organiser.delete", content_object=self, is_orga_action=True, data=json.dumps( { "slug": self.slug, "name": str(self.name), } ), ) for event in self.events.all(): with scope(event=event): event.shred(person=person) # We keep our logged actions, even with the now-broken content type self.delete()
shred.alters_data = True
[docs] class Team(PretalxModel): """A team is a group of people working for the same organiser. Team members (of type :class:`~pretalx.person.models.user.User`) share permissions for one or several events of :class:`~pretalx.event.models.organiser.Organiser`. People can be in multiple Teams, and will have all permissions *any* of their teams has. """ organiser = models.ForeignKey( to=Organiser, related_name="teams", on_delete=models.CASCADE ) name = models.CharField(max_length=190, verbose_name=_("Team name")) members = models.ManyToManyField( to=User, related_name="teams", verbose_name=_("Team members") ) all_events = models.BooleanField( default=False, verbose_name=_( "Apply permissions to all events by this organiser (including newly created ones)" ), ) limit_events = models.ManyToManyField( to="Event", verbose_name=_("Limit permissions to these events"), blank=True ) limit_tracks = models.ManyToManyField( to="submission.Track", verbose_name=_("Limit to tracks"), blank=True ) can_create_events = models.BooleanField( default=False, verbose_name=_("Can create events") ) can_change_teams = models.BooleanField( default=False, verbose_name=_("Can change teams and permissions") ) can_change_organiser_settings = models.BooleanField( default=False, verbose_name=_("Can change organiser settings") ) can_change_event_settings = models.BooleanField( default=False, verbose_name=_("Can change event settings") ) can_change_submissions = models.BooleanField( default=False, verbose_name=_("Can work with and change proposals") ) is_reviewer = models.BooleanField(default=False, verbose_name=_("Is a reviewer")) force_hide_speaker_names = models.BooleanField( verbose_name=_("Always hide speaker names"), help_text=_( "Normally, anonymisation is configured in the event review settings. " "This setting will <b>override the event settings</b> and always hide speaker names for this team." ), default=False, ) objects = models.Manager() def __str__(self) -> str: """Help with debugging.""" return _("{name} on {orga}").format( name=str(self.name), orga=str(self.organiser) ) @cached_property def permission_set(self) -> set: """A set of all permissions this team has, as strings.""" attribs = dir(self) return { attr for attr in attribs if (attr.startswith("can_") or attr.startswith("is_")) and getattr(self, attr, False) is True } @cached_property def events(self): if self.all_events: return self.organiser.events.all() return self.limit_events.all() class orga_urls(EventUrls): base = "{self.organiser.orga_urls.teams}{self.pk}/" delete = "{base}delete"
def generate_invite_token(): return get_random_string( allowed_chars=string.ascii_lowercase + string.digits, length=32 ) class TeamInvite(PretalxModel): """A TeamInvite is someone who has been invited to a team but hasn't accept the invitation yet.""" team = models.ForeignKey(to=Team, related_name="invites", on_delete=models.CASCADE) email = models.EmailField(null=True, blank=True, verbose_name=_("Email")) token = models.CharField( default=generate_invite_token, max_length=64, null=True, blank=True ) objects = models.Manager() def __str__(self) -> str: """Help with debugging.""" return _("Invite to team {team} for {email}").format( team=str(self.team), email=self.email ) class urls(EventUrls): invitation = "/orga/invitation/{self.token}" def send(self): from pretalx.mail.models import QueuedMail invitation_link = build_absolute_uri( "orga:invitation.view", kwargs={"code": self.token} ) invitation_text = _( """Hi! You have been invited to the {name} event organiser team - Please click here to accept: {invitation_link} See you there, The {organiser} team""" ).format( name=str(self.team.name), invitation_link=invitation_link, organiser=str(self.team.organiser.name), ) invitation_subject = _("You have been invited to an organiser team") mail = QueuedMail.objects.create( to=self.email, subject=str(invitation_subject), text=str(invitation_text), locale=get_language(), ) mail.send() return mail send.alters_data = True