class Organizer(LoggedModel): """ This model represents an entity organizing events, e.g. a company, institution, charity, person, … :param name: The organizer's name :type name: str :param slug: A globally unique, short name for this organizer, to be used in URLs and similar places. :type slug: str """ settings_namespace = 'organizer' name = models.CharField(max_length=200, verbose_name=_("Name")) slug = models.SlugField( max_length=50, db_index=True, help_text= _("Should be short, only contain lowercase letters, numbers, dots, and dashes. Every slug can only be used " "once. This is being used in URLs to refer to your organizer accounts and your events." ), validators=[ RegexValidator( regex="^[a-zA-Z0-9][a-zA-Z0-9.-]+$", message= _("The slug may only contain letters, numbers, dots and dashes." )), OrganizerSlugBanlistValidator() ], verbose_name=_("Short form"), unique=True) class Meta: verbose_name = _("Organizer") verbose_name_plural = _("Organizers") ordering = ("name", ) def __str__(self) -> str: return self.name def save(self, *args, **kwargs): obj = super().save(*args, **kwargs) self.get_cache().clear() return obj def get_cache(self): """ Returns an :py:class:`ObjectRelatedCache` object. This behaves equivalent to Django's built-in cache backends, but puts you into an isolated environment for this organizer, so you don't have to prefix your cache keys. In addition, the cache is being cleared every time the organizer changes. .. deprecated:: 1.9 Use the property ``cache`` instead. """ return self.cache @cached_property def cache(self): """ Returns an :py:class:`ObjectRelatedCache` object. This behaves equivalent to Django's built-in cache backends, but puts you into an isolated environment for this organizer, so you don't have to prefix your cache keys. In addition, the cache is being cleared every time the organizer changes. """ from pretix.base.cache import ObjectRelatedCache return ObjectRelatedCache(self) @property def has_gift_cards(self): return self.cache.get_or_set( key='has_gift_cards', timeout=15, default=lambda: self.issued_gift_cards.exists( ) or self.gift_card_issuer_acceptance.exists()) @property def accepted_gift_cards(self): from .giftcards import GiftCard, GiftCardAcceptance return GiftCard.objects.annotate(accepted=Exists( GiftCardAcceptance.objects.filter( issuer=OuterRef('issuer'), collector=self))).filter( Q(issuer=self) | Q(accepted=True)) def allow_delete(self): from . import Order, Invoice return (not Order.objects.filter(event__organizer=self).exists() and not Invoice.objects.filter(event__organizer=self).exists() and not self.devices.exists()) def delete_sub_objects(self): for e in self.events.all(): e.delete_sub_objects() e.delete() self.teams.all().delete()
class Organizer(LoggedModel): """ This model represents an entity organizing events, e.g. a company, institution, charity, person, … :param name: The organizer's name :type name: str :param slug: A globally unique, short name for this organizer, to be used in URLs and similar places. :type slug: str """ settings_namespace = 'organizer' name = models.CharField(max_length=200, verbose_name=_("Name")) slug = models.CharField( max_length=50, db_index=True, help_text=_( "Should be short, only contain lowercase letters, numbers, dots, and dashes. Every slug can only be used " "once. This is being used in URLs to refer to your organizer accounts and your events."), validators=[ MinLengthValidator( limit_value=2, ), RegexValidator( regex="^[a-zA-Z0-9][a-zA-Z0-9.-]*[a-zA-Z0-9]$", message=_("The slug may only contain letters, numbers, dots and dashes.") ), OrganizerSlugBanlistValidator() ], verbose_name=_("Short form"), unique=True ) class Meta: verbose_name = _("Organizer") verbose_name_plural = _("Organizers") ordering = ("name",) def __str__(self) -> str: return self.name def save(self, *args, **kwargs): is_new = not self.pk obj = super().save(*args, **kwargs) if is_new: self.set_defaults() else: self.get_cache().clear() return obj def set_defaults(self): """ This will be called after organizer creation. This way, we can use this to introduce new default settings to pretix that do not affect existing organizers. """ self.settings.cookie_consent = True def get_cache(self): """ Returns an :py:class:`ObjectRelatedCache` object. This behaves equivalent to Django's built-in cache backends, but puts you into an isolated environment for this organizer, so you don't have to prefix your cache keys. In addition, the cache is being cleared every time the organizer changes. .. deprecated:: 1.9 Use the property ``cache`` instead. """ return self.cache @cached_property def cache(self): """ Returns an :py:class:`ObjectRelatedCache` object. This behaves equivalent to Django's built-in cache backends, but puts you into an isolated environment for this organizer, so you don't have to prefix your cache keys. In addition, the cache is being cleared every time the organizer changes. """ from pretix.base.cache import ObjectRelatedCache return ObjectRelatedCache(self) @property def timezone(self): return pytz.timezone(self.settings.timezone) @cached_property def all_logentries_link(self): return reverse( 'control:organizer.log', kwargs={ 'organizer': self.slug, } ) @property def has_gift_cards(self): return self.cache.get_or_set( key='has_gift_cards', timeout=15, default=lambda: self.issued_gift_cards.exists() or self.gift_card_issuer_acceptance.exists() ) @property def accepted_gift_cards(self): from .giftcards import GiftCard, GiftCardAcceptance return GiftCard.objects.annotate( accepted=Exists(GiftCardAcceptance.objects.filter(issuer=OuterRef('issuer'), collector=self)) ).filter( Q(issuer=self) | Q(accepted=True) ) @property def default_gift_card_expiry(self): if self.settings.giftcard_expiry_years is not None: tz = get_current_timezone() return make_aware(datetime.combine( date(now().astimezone(tz).year + self.settings.get('giftcard_expiry_years', as_type=int), 12, 31), time(hour=23, minute=59, second=59) ), tz) def allow_delete(self): from . import Invoice, Order return ( not Order.objects.filter(event__organizer=self).exists() and not Invoice.objects.filter(event__organizer=self).exists() and not self.devices.exists() ) def delete_sub_objects(self): for e in self.events.all(): e.delete_sub_objects() e.delete() self.teams.all().delete() def get_mail_backend(self, timeout=None): """ Returns an email server connection, either by using the system-wide connection or by returning a custom one based on the organizer's settings. """ if self.settings.smtp_use_custom: return get_connection(backend=settings.EMAIL_CUSTOM_SMTP_BACKEND, host=self.settings.smtp_host, port=self.settings.smtp_port, username=self.settings.smtp_username, password=self.settings.smtp_password, use_tls=self.settings.smtp_use_tls, use_ssl=self.settings.smtp_use_ssl, fail_silently=False, timeout=timeout) else: return get_connection(fail_silently=False)