Example #1
0
class Pupil(Versionable):
    name = CharField(max_length=200)
    phone_number = CharField(max_length=200)
    language_teachers = VersionedManyToManyField(
        'Teacher', related_name='language_students')
    science_teachers = VersionedManyToManyField(
        'Teacher', related_name='science_students')
Example #2
0
class Student(Versionable):
    name = CharField(max_length=200)
    professors = VersionedManyToManyField("Professor", related_name='students')
    classrooms = VersionedManyToManyField("Classroom", related_name='students')

    def __str__(self):
        return self.name
Example #3
0
 class Subject(Versionable):
     name = CharField(max_length=200)
     observers = VersionedManyToManyField('Observer',
                                          related_name='subjects')
Example #4
0
class C2(Versionable):
    name = CharField(max_length=50)
    c3s = VersionedManyToManyField("C3", related_name='c2s')
Example #5
0
class Award(Versionable):
    name = CharField(max_length=200)
    players = VersionedManyToManyField(Player, related_name='awards')
Example #6
0
class C1(Versionable):
    name = CharField(max_length=50)
    c2s = VersionedManyToManyField("C2", related_name='c1s')
Example #7
0
class C2(Versionable):
    name = CharField(max_length=50)
    c3s = VersionedManyToManyField("C3", related_name='c2s')

    __str__ = versionable_description
Example #8
0
class Person(Versionable):
    name = CharField(max_length=200)
    children = VersionedManyToManyField('self',
                                        symmetrical=False,
                                        null=True,
                                        related_name='parents')
Example #9
0
class Student(Versionable):
    name = CharField(max_length=200)
    professors = VersionedManyToManyField("Professor", related_name='students')
    classrooms = VersionedManyToManyField("Classroom", related_name='students')

    __str__ = versionable_description
Example #10
0
class SecondModel(Versionable):
    g = models.TextField()

    other = VersionedManyToManyField(FirstModel)
Example #11
0
class Quota(Versionable):
    """
    A quota is a "pool of tickets". It is there to limit the number of items
    of a certain type to be sold. For example, you could have a quota of 500
    applied to all your items (because you only have that much space in your
    building), and also a quota of 100 applied to the VIP tickets for
    exclusivity. In this case, no more than 500 tickets will be sold in total
    and no more than 100 of them will be VIP tickets (but 450 normal and 50
    VIP tickets will be fine).

    As always, a quota can not only be tied to an item, but also to specific
    variations.

    Please read the documentation section on quotas carefully before doing
    anything with quotas. This might confuse you otherwise.
    http://docs.pretix.eu/en/latest/development/concepts.html#restriction-by-number

    The AVAILABILITY_* constants represent various states of an quota allowing
    its items/variations being for sale.

    AVAILABILITY_OK
        This item is available for sale.

    AVAILABILITY_RESERVED
        This item is currently not available for sale, because all available
        items are in people's shopping carts. It might become available
        again if those people do not proceed with checkout.

    AVAILABILITY_ORDERED
        This item is currently not availalbe for sale, because all available
        items are ordered. It might become available again if those people
        do not pay.

    AVAILABILITY_GONE
        This item is completely sold out.

    :param event: The event this belongs to
    :type event: Event
    :param name: This quota's name
    :type str:
    :param size: The number of items in this quota
    :type size: int
    :param items: The set of :py:class:`Item` objects this quota applies to
    :param variations: The set of :py:class:`ItemVariation` objects this quota applies to
    """

    AVAILABILITY_GONE = 0
    AVAILABILITY_ORDERED = 10
    AVAILABILITY_RESERVED = 20
    AVAILABILITY_OK = 100

    event = VersionedForeignKey(
        Event,
        on_delete=models.CASCADE,
        related_name="quotas",
        verbose_name=_("Event"),
    )
    name = models.CharField(max_length=200, verbose_name=_("Name"))
    size = models.PositiveIntegerField(
        verbose_name=_("Total capacity"),
        null=True,
        blank=True,
        help_text=_("Leave empty for an unlimited number of tickets."))
    items = VersionedManyToManyField(Item,
                                     verbose_name=_("Item"),
                                     related_name="quotas",
                                     blank=True)
    variations = VariationsField(ItemVariation,
                                 related_name="quotas",
                                 blank=True,
                                 verbose_name=_("Variations"))

    class Meta:
        verbose_name = _("Quota")
        verbose_name_plural = _("Quotas")

    def __str__(self):
        return self.name

    def delete(self, *args, **kwargs):
        super().delete(*args, **kwargs)
        if self.event:
            self.event.get_cache().clear()

    def save(self, *args, **kwargs):
        super().save(*args, **kwargs)
        if self.event:
            self.event.get_cache().clear()

    def availability(self):
        """
        This method is used to determine whether Items or ItemVariations belonging
        to this quota should currently be available for sale.

        :returns: a tuple where the first entry is one of the ``Quota.AVAILABILITY_`` constants
                  and the second is the number of available tickets.
        """
        size_left = self.size
        if size_left is None:
            return Quota.AVAILABILITY_OK, None

        # TODO: Test for interference with old versions of Item-Quota-relations, etc.
        # TODO: Prevent corner-cases like people having ordered an item before it got
        # its first variationsadde
        orders = self.count_orders()

        size_left -= orders['paid']
        if size_left <= 0:
            return Quota.AVAILABILITY_GONE, 0

        size_left -= orders['pending']
        if size_left <= 0:
            return Quota.AVAILABILITY_ORDERED, 0

        size_left -= self.count_in_cart()
        if size_left <= 0:
            return Quota.AVAILABILITY_RESERVED, 0

        return Quota.AVAILABILITY_OK, size_left

    def count_in_cart(self) -> int:
        from pretix.base.models import CartPosition

        return CartPosition.objects.current.filter(
            Q(expires__gte=now())
            & self._position_lookup).count()

    def count_orders(self) -> dict:
        from pretix.base.models import Order, OrderPosition

        o = OrderPosition.objects.current.filter(
            self._position_lookup).aggregate(
                paid=Sum(
                    Case(When(order__status=Order.STATUS_PAID, then=1),
                         output_field=models.IntegerField())),
                pending=Sum(
                    Case(When(Q(order__status=Order.STATUS_PENDING)
                              & Q(order__expires__gte=now()),
                              then=1),
                         output_field=models.IntegerField())))
        for k, v in o.items():
            if v is None:
                o[k] = 0
        return o

    @cached_property
    def _position_lookup(self):
        return ((  # Orders for items which do not have any variations
            Q(variation__isnull=True)
            & Q(item__quotas__in=[self])) |
                (  # Orders for items which do have any variations
                    Q(variation__quotas__in=[self])))

    class QuotaExceededException(Exception):
        pass
Example #12
0
class Question(Versionable):
    """
    A question is an input field that can be used to extend a ticket
    by custom information, e.g. "Attendee age". A question can allow one o several
    input types, currently:

    * a number (``TYPE_NUMBER``)
    * a one-line string (``TYPE_STRING``)
    * a multi-line string (``TYPE_TEXT``)
    * a boolean (``TYPE_BOOLEAN``)

    :param event: The event this question belongs to
    :type event: Event
    :param question: The question text. This will be displayed next to the input field.
    :type question: str
    :param type: One of the above types
    :param required: Whether answering this question is required for submiting an order including
                     items associated with this question.
    :type required: bool
    :param items: A set of ``Items`` objects that this question should be applied to
    """
    TYPE_NUMBER = "N"
    TYPE_STRING = "S"
    TYPE_TEXT = "T"
    TYPE_BOOLEAN = "B"
    TYPE_CHOICES = (
        (TYPE_NUMBER, _("Number")),
        (TYPE_STRING, _("Text (one line)")),
        (TYPE_TEXT, _("Multiline text")),
        (TYPE_BOOLEAN, _("Yes/No")),
    )

    event = VersionedForeignKey(Event, related_name="questions")
    question = I18nTextField(verbose_name=_("Question"))
    type = models.CharField(max_length=5,
                            choices=TYPE_CHOICES,
                            verbose_name=_("Question type"))
    required = models.BooleanField(default=False,
                                   verbose_name=_("Required question"))
    items = VersionedManyToManyField(
        Item,
        related_name='questions',
        verbose_name=_("Products"),
        blank=True,
        help_text=_(
            'This question will be asked to buyers of the selected products'))

    class Meta:
        verbose_name = _("Question")
        verbose_name_plural = _("Questions")

    def __str__(self):
        return str(self.question)

    def delete(self, *args, **kwargs):
        super().delete(*args, **kwargs)
        if self.event:
            self.event.get_cache().clear()

    def save(self, *args, **kwargs):
        super().save(*args, **kwargs)
        if self.event:
            self.event.get_cache().clear()
Example #13
0
class ItemVariation(Versionable):
    """
    A variation is an item combined with values for all properties
    associated with the item. For example, if your item is 'T-Shirt'
    and your properties are 'Size' and 'Color', then an example for an
    variation would be 'T-Shirt XL read'.

    Attention: _ALL_ combinations of PropertyValues _ALWAYS_ exist,
    even if there is no ItemVariation object for them! ItemVariation objects
    do NOT prove existance, they are only available to make it possible
    to override default values (like the price) for certain combinations
    of property values. However, appropriate ItemVariation objects will be
    created as soon as you add your variations to a quota.

    They also allow to explicitly EXCLUDE certain combinations of property
    values by creating an ItemVariation object for them with active set to
    False.

    Restrictions can be not only set to items but also directly to variations.

    :param item: The item this variation belongs to
    :type item: Item
    :param values: A set of ``PropertyValue`` objects defining this variation
    :param active: Whether this value is to be sold.
    :type active: bool
    :param default_price: This variation's default price
    :type default_price: decimal.Decimal
    """
    item = VersionedForeignKey(Item, related_name='variations')
    values = VersionedManyToManyField(
        PropertyValue,
        related_name='variations',
    )
    active = models.BooleanField(
        default=True,
        verbose_name=_("Active"),
    )
    default_price = models.DecimalField(
        decimal_places=2,
        max_digits=7,
        null=True,
        blank=True,
        verbose_name=_("Default price"),
    )

    class Meta:
        verbose_name = _("Product variation")
        verbose_name_plural = _("Product variations")

    def __str__(self):
        return str(self.to_variation_dict())

    def delete(self, *args, **kwargs):
        super().delete(*args, **kwargs)
        if self.item:
            self.item.event.get_cache().clear()

    def save(self, *args, **kwargs):
        super().save(*args, **kwargs)
        if self.item:
            self.item.event.get_cache().clear()

    def check_quotas(self):
        """
        This method is used to determine whether this ItemVariation is currently
        available for sale in terms of quotas.

        :returns: any of the return codes of :py:meth:`Quota.availability()`.
        """
        return min([q.availability() for q in self.quotas.all()],
                   key=lambda s: (s[0], s[1]
                                  if s[1] is not None else sys.maxsize))

    def to_variation_dict(self):
        """
        :return: a :py:class:`VariationDict` representing this variation.
        """
        vd = VariationDict()
        for v in self.values.all():
            vd[v.prop.identity] = v
        vd['variation'] = self
        return vd

    def check_restrictions(self):
        """
        This method is used to determine whether this ItemVariation is restricted
        in sale by any restriction plugins.

        :returns:

            * ``False``, if the item is unavailable
            * the item's price, otherwise
        """
        from pretix.base.signals import determine_availability

        responses = determine_availability.send(
            self.item.event,
            item=self.item,
            variations=[self.to_variation_dict()],
            context=None,
            cache=self.item.event.get_cache())
        price = self.default_price if self.default_price is not None else self.item.default_price
        for rec, response in responses:
            if 'available' in response[0] and not response[0]['available']:
                return False
            elif 'price' in response[0] and response[0][
                    'price'] is not None and response[0]['price'] < price:
                price = response[0]['price']
        return price

    def add_values_from_string(self, pk):
        """
        Add values to this ItemVariation using a serialized string of the form
        ``property-id:value-id,á¹—roperty-id:value-id``
        """
        for pair in pk.split(","):
            prop, value = pair.split(":")
            self.values.add(
                PropertyValue.objects.current.get(identity=value,
                                                  prop_id=prop))