from django.db import models from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin from django.utils.translation import ugettext as _ class UserManager(BaseUserManager): """ This is the user manager for our custom user model. See the User model documentation to see what's so special about our user model. """ def create_user(self, identifier, username, password=None): user = self.model(identifier=identifier) user.set_password(user) user.save() return user def create_superuser(self, identifier, username, password=None): if password is None: raise Exception("You must provide a password") user = self.model(identifier=identifier, username=username) user.is_staff = True user.is_superuser = True user.set_password(user) user.save() return user class User(AbstractBaseUser, PermissionsMixin): """ This is the user model used by tixl for authentication. Handling users is somehow complicated, as we try to have two classes of users in one system: (1) We want global users who can just login into tixl and buy tickets for multiple events -- we also need those global users for event organizers who should not need multiple users for managing multiple events. (2) We want local users who exist only in the scope of a certain event The hard part is to find a primary key to identify all of these users. Letting the users choose usernames is a bad idea, as the primary key needs to be unique and there is no reason for a local user to block a name for all time. Using e-mail addresses is not a good idea either, for two reasons: First, a user might have multiple local users (so they are not unique), and second, it should be possible to create anonymous users without having to supply an e-mail address. Therefore, we use an abstract "identifier" field as the primary key. The identifier is: (1) the e-mail address for global users. An e-mail address is and should be required for them and global users use their e-mail address for login. (2) "{username}@{event.id}.event.tixl" for local users, who use their username to login on the event page. The model's save() method automatically fills the identifier field according to this scheme when it is empty. The __str__() method returns the identifier. The is_staff field is only True for system operators. """ identifier = models.CharField(max_length=255, unique=True) username = models.CharField(max_length=120, blank=True, null=True, help_text=_('Letters, digits and @/./+/-/_ only.')) event = models.ForeignKey('Event', related_name="users", null=True, blank=True, on_delete=models.PROTECT) email = models.EmailField(unique=False, db_index=True, null=True, blank=True, verbose_name=_('E-mail')) givenname = models.CharField(max_length=255, blank=True, null=True, verbose_name=_('Given name')) familyname = models.CharField(max_length=255, blank=True, null=True, verbose_name=_('Family name')) is_active = models.BooleanField(default=True) is_staff = models.BooleanField(default=False) date_joined = models.DateTimeField(auto_now_add=True) objects = UserManager() def __str__(self): return self.identifier def get_short_name(self): if self.givenname: return self.givenname elif self.familyname: return self.familyname else: return self.username def get_full_name(self): if self.givenname and not self.familyname: return self.givenname elif not self.givenname and self.familyname: return self.familyname elif self.familyname and self.givenname: return '%(family)s, %(given)s' % { 'family': self.familyname, 'given': self.givenname } else: return self.username def save(self, *args, **kwargs): if self.identifier is None: if self.event is None: self.identifier = self.email.lower() else: self.identifier = "%s@%d.event.tixl" % (self.username.lower(), self.event.id) if not self.pk: self.identifier = self.identifier.lower() super().save(*args, **kwargs) USERNAME_FIELD = 'identifier' REQUIRED_FIELDS = ['username'] class Meta: unique_together = (("event", "username"),) class Organizer(models.Model): """ This model represents an entity organizing events, like a company, an organization or a person. It has one user as owner (who has registered it) and can have any number of users with admin authorization. Any organizer has a unique slug, which is a short name (alphanumeric, all lowercase) being used in URLs. """ name = models.CharField(max_length=200) slug = models.CharField(max_length=50, unique=True, db_index=True) owner = models.ForeignKey(User, null=True, blank=True, on_delete=models.PROTECT) class Meta: ordering = ("name",) class Event(models.Model): """ This model represents an event. An event is anything you can buy tickets for. It belongs to one orgnaizer and has a name and a slug, the latter being a short, alphanumeric, all-lowercase name being used in URLs. The slug has to be unique among the events of the same organizer. An event can hold several properties, such as a default locale and currency. The event has date_from and date_to field which mark the actual datetime of the event itself. The show_date_to and show_times fields are used to control the display of these dates. (Without show_times only days are shown, now times.) The presale_start and presale_end fields mark the time frame in which tickets are sold for this event. These two dates override every other restrictions to ticket sale if set. The payment_term_days field holds the number of days after submitting a ticket order, in which the ticket has to be paid. The payment_term_last is the day all orders must be paid by, no matter when they were ordered (and thus, ignoring payment_term_days). """ organizer = models.ForeignKey(Organizer, related_name="events", on_delete=models.PROTECT) name = models.CharField(max_length=200) slug = models.CharField(max_length=50, db_index=True) locale = models.CharField(max_length=10) currency = models.CharField(max_length=10) date_from = models.DateTimeField() date_to = models.DateTimeField(null=True, blank=True) show_date_to = models.BooleanField(default=True) show_times = models.BooleanField(default=True) presale_end = models.DateTimeField(null=True, blank=True) presale_start = models.DateTimeField(null=True, blank=True) payment_term_days = models.IntegerField(default=14) payment_term_last = models.DateTimeField(null=True, blank=True) class Meta: unique_together = (("organizer", "slug"),) ordering = ("date_from", "name")