text: 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 gettext_lazy as _
  from django_scopes import scope, scopes_disabled
  from i18nfield.fields import I18nCharField
  
  from pretalx.common.mixins import LogMixin
  from pretalx.common.urls import EventUrls, build_absolute_uri
  from pretalx.person.models import User
  
  SLUG_CHARS = 'a-zA-Z0-9.-'
  
  
  class Organiser(LogMixin, models.Model):
      """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=f"^[{SLUG_CHARS}]+$",
                  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.'
          ),
      )
  
      def __str__(self) -> str:
          """Used in generated forms."""
          return str(self.name)
  
      class orga_urls(EventUrls):
          base = '/orga/organiser/{self.slug}/'
          delete = '{base}delete'
          teams = '{base}teams/'
          new_team = '{teams}new'
  
      @transaction.atomic
      def shred(self):
          """Irrevocably deletes the organiser and all related events and their
          data."""
          for event in self.events.all():
              with scope(event=event):
                  event.shred()
          with scopes_disabled():
              self.logged_actions().delete()
          self.delete()
      shred.alters_data = True
  
  
  class Team(LogMixin, models.Model):
      """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 submissions')
      )
      is_reviewer = models.BooleanField(default=False, verbose_name=_('Is a reviewer'))
      review_override_votes = models.PositiveIntegerField(
          default=0,
          verbose_name=_('Override votes'),
          help_text=_(
              'Each member of this team will have this amount of override votes per event to indicate an absolute positive or negative opinion of a submission.'
          ),
      )
  
      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 {
              a
              for a in attribs
              if (a.startswith('can_') or a.startswith('is_'))
              and getattr(self, a, False) is True
          }
  
      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(models.Model):
      """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
      )
  
      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, event):
          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 {event} team'''
          ).format(
              name=str(self.team.name),
              invitation_link=invitation_link,
              event=str(event.name) if event else str(self.team.organiser.name),
          )
          invitation_subject = _('You have been invited to an organiser team')
  
          mail = QueuedMail.objects.create(
              to=self.email,
              event=event,
              subject=str(invitation_subject),
              text=str(invitation_text),
          )
          if event:
              mail.save()
          else:
              mail.send()
          return mail
      send.alters_data = True
  