/ticketshop/tickets/models.py
Python | 220 lines | 195 code | 6 blank | 19 comment | 1 complexity | 18cedded44cd2087f1281e54288fffaf MD5 | raw file
- # -*- coding: utf-8 -*-
- from __future__ import unicode_literals
- from datetime import datetime
- import uuid
- from django.core.validators import RegexValidator
- from django.db import models
- from django.db.models import Count, Q, F
- from django.dispatch import Signal
- from .mails import (
- send_payment_confirmation_email,
- send_cancellation_email)
- from .utils import randrefnumber
- class TicketTypeManager(models.Manager):
- """
- Create a custom manager with an extra method
- that only returns available ticket types
- """
- def available(self):
- """
- Custom method that return a queryset with only
- available tickets
- """
- return super(TicketTypeManager, self).get_query_set() \
- .annotate(num_sold=Count('ticket')) \
- .filter(Q(quantity=None) | Q(num_sold__lt=F('quantity')))
- class TicketType(models.Model):
- """
- A type of ticket is for instance "regular", "early bird", etc.
- """
- name = models.CharField(
- max_length=200,
- help_text="How do you call this kind of ticket? E.g. \"Early bird\"")
- description = models.TextField(
- blank=True,
- help_text="What does this ticket gives access to and who can by it.")
- price = models.IntegerField(help_text="Price in SEK.")
- sales_end = models.DateField(
- help_text="After this, it won't be possible to buy this ticket type.")
- quantity = models.IntegerField(
- null=True,
- blank=True,
- help_text="Maximum number of ticket of this type.")
- # Add our custom manager
- objects = TicketTypeManager()
- def __unicode__(self):
- return u"%s" % (self.name)
- def remaining(self):
- """ Return the number of tickets remaining of this type """
- return self.quantity
- def available(self, n=1):
- if self.quantity is None:
- return True
- else:
- return self.ticket_set.count() + n <= self.quantity
- class Meta:
- ordering = ['-price']
- def default_ticket_type():
- Q = TicketType.objects.order_by('-price')
- if Q.count() > 0:
- return Q[0]
- else:
- return None
- class Ticket(models.Model):
- ticket_type = models.ForeignKey(TicketType, default=default_ticket_type)
- name_on_badge = models.CharField(max_length=200)
- # Gender distribution, as required by the manifesto.
- # For a discussion on more inclusive gender question, see
- # http://itspronouncedmetrosexual.com/2012/06/how-can-i-make-the-gender-question-on-an-application-form-more-inclusive/
- gender = models.CharField(
- max_length=1,
- blank=True,
- verbose_name="I identify my gender as…",
- help_text="Why are we asking this? We ask you for your gender in \
- order to evaluate our effort in reaching a fair and \
- reasonable, within 60/40, gender distribution among \
- participants.",
- choices=(('F', 'Woman'), ('M', 'Man'), ('T', 'Trans*'),
- ('?', 'Prefer not to disclose'))
- )
- returning_visitor = models.NullBooleanField(
- blank=True,
- verbose_name="I have been to FSCONS before",
- choices=((True, 'Yes'), (False, 'No')),
- )
- purchase = models.ForeignKey("TicketPurchase", related_name="tickets")
- class Meta:
- permissions = (("view_reportk", "Can see the ticket report"),)
- def __unicode__(self):
- return u"{0.name_on_badge} ({0.ticket_type})".format(self)
- class PreBooking(models.Model):
- """
- A pre-booking is a time-limited hold on a set of tickets.
- When a user start a new registration by selecting how many tickets they
- want to buy, we create a new pre-booking to hold those tickets for a given
- number of minutes. When the registration is completed and a new
- TicketPurchase object is created in the database, the pre-booking should be
- deleted. Alternatively, after the decided amount of time, the pre-booking
- is considered expired and the held-up tickets available again.
- Note that the natural key is the combination of session_id and ticket_type
- but since django doesn't support compound keys, there is also a surrogate
- key.
- """
- session = models.CharField(max_length=40)
- ticket_type = models.ForeignKey(TicketType)
- quantity = models.IntegerField()
- creation_date = models.DateTimeField(auto_now_add=True)
- expiration_date = models.DateTimeField()
- class Meta:
- unique_together = (('session', 'ticket_type'),)
- def __unicode__(self):
- return "{0.quantity} x {0.ticket_type}, {0.session}".format(self)
- class TicketPurchase(models.Model):
- id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
- # Contact details
- contact_first_name = models.CharField(
- verbose_name="First name",
- max_length=200)
- contact_surname = models.CharField(
- verbose_name="Surname",
- max_length=200)
- contact_email = models.EmailField(
- verbose_name="E-mail")
- contact_subscribe = models.BooleanField(
- default=False,
- verbose_name="I would like to get emails about future conferences")
- additional_information = models.TextField(
- blank=True,
- help_text="Do not hesitate to let us know if you have specific \
- requirements or comments about the registration.")
- paid = models.BooleanField(default=False, editable=False)
- # Administrativia
- creation_date = models.DateTimeField(editable=False, auto_now_add=True)
- ref = models.CharField(max_length=10, unique=True,
- default=randrefnumber,
- validators=[RegexValidator(regex=r"^\d{10}$")])
- cancelled = models.BooleanField(default=False, editable=False)
- cancelled_by = models.CharField(
- max_length=5, choices=(('USER', 'User'), ('ADMIN', 'Admin')),
- null=True, blank=True, editable=False)
- cancellation_date = models.DateTimeField(editable=False, null=True)
- def __unicode__(self):
- return u"%s %s (%d ticket(s))" % (
- self.contact_first_name,
- self.contact_surname,
- self.number_of_tickets())
- def price(self):
- p = 0
- for ticket in self.tickets.all():
- p += ticket.ticket_type.price
- return p
- def contact_full_name(self):
- return u"%s %s" % (self.contact_first_name, self.contact_surname)
- def get_contact_mailbox(self):
- """ Get the contact 'mailbox', in the form
- First Last <Address>
- suitable to use when sending emails.
- """
- return (u"{} <{}>"
- .format(self.contact_full_name(), self.contact_email))
- def number_of_tickets(self):
- return self.tickets.count()
- def mark_as_paid(self):
- """
- Mark an unpaid purchase as paid and send a purchase_paid signal
- """
- if not self.paid:
- self.paid = True
- self.save(update_fields=['paid'])
- purchase_paid.send(sender=self, purchase=self)
- def cancel(self, by):
- assert by in ['USER', 'ADMIN']
- self.cancelled = True
- self.cancellation_date = datetime.now()
- self.cancelled_by = by
- if by == "USER":
- send_cancellation_email(self)
- # ~~~ Custom signals ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- purchase_paid = Signal(providing_args=["purchase"])
- # ~~~ Signal handlers ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- def send_mail_on_paiement(sender, purchase, **kwargs):
- send_payment_confirmation_email(purchase)
- purchase_paid.connect(send_mail_on_paiement)