Rename tixl to pretix

This commit is contained in:
Raphael Michel
2015-01-19 01:10:35 +01:00
parent fd93dcae3c
commit 6648e7ed03
107 changed files with 524 additions and 529 deletions

View File

@@ -0,0 +1,2 @@
from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)

View File

@@ -0,0 +1,17 @@
from django.apps import AppConfig
from pretixbase.plugins import PluginType
class TestDummyApp(AppConfig):
name = 'pretixplugins.testdummy'
verbose_name = '.testdummy'
class TixlPluginMeta:
type = PluginType.RESTRICTION
name = '.testdummy'
version = '1.0.0'
def ready(self):
from . import signals # NOQA
default_app_config = 'pretixplugins.testdummy.TestDummyApp'

View File

View File

@@ -0,0 +1,9 @@
from django.dispatch import receiver
from pretixbase.signals import determine_availability
@receiver(determine_availability)
def availability_handler(sender, **kwargs):
kwargs['sender'] = sender
return kwargs

View File

@@ -0,0 +1,22 @@
from django.apps import AppConfig
from django.utils.translation import ugettext_lazy as _
from pretixbase.plugins import PluginType
class TimeRestrictionApp(AppConfig):
name = 'pretixplugins.timerestriction'
verbose_name = _("Time restriction")
class TixlPluginMeta:
type = PluginType.RESTRICTION
name = _("Restriction by time")
author = _("the pretix team")
version = '1.0.0'
description = _("This plugin adds the possibility to restrict the sale " +
"of a given item or variation to a certain timeframe " +
"or change its price during a certain period.")
def ready(self):
from . import signals # NOQA
default_app_config = 'pretixplugins.timerestriction.TimeRestrictionApp'

View File

@@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import pretixbase.models
import versions.models
class Migration(migrations.Migration):
dependencies = [
('pretixbase', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='TimeRestriction',
fields=[
('id', models.CharField(serialize=False, primary_key=True, max_length=36)),
('identity', models.CharField(max_length=36)),
('version_start_date', models.DateTimeField()),
('version_end_date', models.DateTimeField(null=True, blank=True, default=None)),
('version_birth_date', models.DateTimeField()),
('timeframe_from', models.DateTimeField(verbose_name='Start of time frame')),
('timeframe_to', models.DateTimeField(verbose_name='End of time frame')),
('price', models.DecimalField(null=True, blank=True, verbose_name='Price in time frame', max_digits=7, decimal_places=2)),
('event', versions.models.VersionedForeignKey(to='pretixbase.Event', related_name='restrictions_timerestriction_timerestriction', verbose_name='Event')),
('item', versions.models.VersionedForeignKey(to='pretixbase.Item', blank=True, null=True, related_name='restrictions_timerestriction_timerestriction', verbose_name='Item')),
('variations', pretixbase.models.VariationsField(to='pretixbase.ItemVariation', blank=True, verbose_name='Variations', related_name='restrictions_timerestriction_timerestriction')),
],
options={
'verbose_name': 'Restriction',
'verbose_name_plural': 'Restrictions',
'abstract': False,
},
bases=(models.Model,),
),
]

View File

@@ -0,0 +1,24 @@
from django.db import models
from django.utils.translation import ugettext_lazy as _
from pretixbase.models import BaseRestriction
class TimeRestriction(BaseRestriction):
"""
This restriction makes an item or variation only available
within a given time frame. The price of the item can be modified
during this time frame.
"""
timeframe_from = models.DateTimeField(
verbose_name=_("Start of time frame"),
)
timeframe_to = models.DateTimeField(
verbose_name=_("End of time frame"),
)
price = models.DecimalField(
null=True, blank=True,
max_digits=7, decimal_places=2,
verbose_name=_("Price in time frame"),
)

View File

@@ -0,0 +1,146 @@
from django.dispatch import receiver
from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _
from django.forms.models import inlineformset_factory
from pretixbase.signals import determine_availability
from pretixbase.models import Item
from pretixcontrol.views.forms import VariationsField, RestrictionInlineFormset, RestrictionForm
from pretixcontrol.signals import restriction_formset
from .models import TimeRestriction
@receiver(determine_availability)
def availability_handler(sender, **kwargs):
# Handle the signal's input arguments
item = kwargs['item']
variations = kwargs['variations']
cache = kwargs['cache']
context = kwargs['context'] # NOQA
# Fetch all restriction objects applied to this item
restrictions = list(TimeRestriction.objects.current.filter(
item=item,
).prefetch_related('variations'))
# If we do not know anything about this item, we are done here.
if len(restrictions) == 0:
return variations
# IMPORTANT:
# We need to make a two-level deep copy of the variations list before we
# modify it, becuase we need to to copy the dictionaries. Otherwise, we'll
# interfere with other plugins.
variations = [d.copy() for d in variations]
# The maximum validity of our cached values is the next date, one of our
# timeframe_from or tiemframe_to actions happens
def timediff(restrictions):
for r in restrictions:
if r.timeframe_from >= now():
yield (r.timeframe_from - now()).total_seconds()
if r.timeframe_to >= now():
yield (r.timeframe_to - now()).total_seconds()
try:
cache_validity = min(timediff(restrictions))
except ValueError:
# empty sequence
# If we get here, there are restrictions available but nothing will
# change about them any more. If it were not for the case of no
# restriction for the base item but restrictions for special
# variations, we could quit here with 'item not available'.
cache_validity = 3600
# Walk through all variations we are asked for
for v in variations:
# If this point is reached, there ARE time restrictions for this item
# Therefore, it is only available inside one of the timeframes, but not
# without any timeframe
available = False
price = None
# Make up some unique key for this variation
cachekey = 'timerestriction:%s:%s' % (
item.identity,
v.identify(),
)
# Fetch from cache, if available
cached = cache.get(cachekey)
if cached is not None:
v['available'] = (cached.split(":")[0] == 'True')
try:
v['price'] = float(cached.split(":")[1])
except ValueError:
v['price'] = None
continue
# Walk through all restriction objects applied to this item
for restriction in restrictions:
applied_to = list(restriction.variations.current.all())
# Only take this restriction into consideration if it
# is directly applied to this variation or if the item
# has no variations
if len(v) != 0 and ('variation' not in v or v['variation'] not in applied_to):
continue
if restriction.timeframe_from <= now() <= restriction.timeframe_to:
# Selling this item is currently possible
available = True
# If multiple time frames are currently active, make sure to
# get the cheapest price:
if (restriction.price is not None
and (price is None or restriction.price < price)):
price = restriction.price
v['available'] = available
v['price'] = price
cache.set(
cachekey,
'%s:%s' % (
'True' if available else 'False',
str(price) if price else ''
),
cache_validity
)
return variations
class TimeRestrictionForm(RestrictionForm):
class Meta:
model = TimeRestriction
localized_fields = '__all__'
fields = [
'variations',
'timeframe_from',
'timeframe_to',
'price',
]
@receiver(restriction_formset)
def formset_handler(sender, **kwargs):
formset = inlineformset_factory(
Item,
TimeRestriction,
formset=RestrictionInlineFormset,
form=TimeRestrictionForm,
can_order=False,
can_delete=True,
extra=0,
)
return {
'title': _('Restriction by time'),
'formsetclass': formset,
'prefix': 'timerestriction',
'description': 'If you use this restriction type, the system will only sell variations, which are covered '
'by at least one of the timeframes you define below. You can also change the price of '
'variations for within the given timeframe. Please note, that if you change the price of '
'variations here, this will overrule the price set in the "Variations" section.'
}

View File

@@ -0,0 +1,286 @@
from datetime import timedelta
from django.test import TestCase
from django.utils.timezone import now
from pretixbase.models import (
Event, Organizer, Item, Property, PropertyValue, ItemVariation
)
# Do NOT use relative imports here
from pretixplugins.timerestriction import signals
from pretixplugins.timerestriction.models import TimeRestriction
class TimeRestrictionTest(TestCase):
"""
This test case tests the various aspects of the time restriction
plugin
"""
def setUp(self):
o = Organizer.objects.create(name='Dummy', slug='dummy')
self.event = Event.objects.create(
organizer=o, name='Dummy', slug='dummy',
date_from=now(),
)
self.item = Item.objects.create(event=self.event, name='Dummy', default_price=14)
self.property = Property.objects.create(event=self.event, name='Size')
self.value1 = PropertyValue.objects.create(prop=self.property, value='S')
self.value2 = PropertyValue.objects.create(prop=self.property, value='M')
self.value3 = PropertyValue.objects.create(prop=self.property, value='L')
self.variation1 = ItemVariation.objects.create(item=self.item)
self.variation1.values.add(self.value1)
self.variation2 = ItemVariation.objects.create(item=self.item)
self.variation2.values.add(self.value2)
self.variation3 = ItemVariation.objects.create(item=self.item)
self.variation3.values.add(self.value3)
def test_nothing(self):
result = signals.availability_handler(
None, item=self.item,
variations=self.item.get_all_variations(),
context=None, cache=self.event.get_cache()
)
self.assertEqual(len(result), 1)
self.assertTrue('available' not in result[0] or result[0]['available'] is True)
def test_simple_case_available(self):
r = TimeRestriction.objects.create(
timeframe_from=now() - timedelta(days=3),
timeframe_to=now() + timedelta(days=3),
event=self.event,
price=12
)
r.item = self.item
r.save()
result = signals.availability_handler(
self.event, item=self.item,
variations=self.item.get_all_variations(),
context=None, cache=self.event.get_cache()
)
self.assertEqual(len(result), 1)
self.assertIn('available', result[0])
self.assertTrue(result[0]['available'])
self.assertEqual(result[0]['price'], 12)
def test_cached_result(self):
r = TimeRestriction.objects.create(
timeframe_from=now() - timedelta(days=3),
timeframe_to=now() + timedelta(days=3),
event=self.event,
price=12
)
r.item = self.item
r.save()
result = signals.availability_handler(
self.event, item=self.item,
variations=self.item.get_all_variations(),
context=None, cache=self.event.get_cache()
)
self.assertEqual(len(result), 1)
self.assertIn('available', result[0])
self.assertTrue(result[0]['available'])
self.assertEqual(result[0]['price'], 12)
result = signals.availability_handler(
self.event, item=self.item,
variations=self.item.get_all_variations(),
context=None, cache=self.event.get_cache()
)
self.assertEqual(len(result), 1)
self.assertIn('available', result[0])
self.assertTrue(result[0]['available'])
self.assertEqual(result[0]['price'], 12)
def test_simple_case_unavailable(self):
r = TimeRestriction.objects.create(
timeframe_from=now() - timedelta(days=5),
timeframe_to=now() - timedelta(days=3),
event=self.event,
price=12
)
r.item = self.item
r.save()
result = signals.availability_handler(
self.event, item=self.item,
variations=self.item.get_all_variations(),
context=None, cache=self.event.get_cache()
)
self.assertEqual(len(result), 1)
self.assertIn('available', result[0])
self.assertFalse(result[0]['available'])
def test_multiple_overlapping_now(self):
r1 = TimeRestriction.objects.create(
timeframe_from=now() - timedelta(days=5),
timeframe_to=now() + timedelta(days=3),
event=self.event,
price=12
)
r1.item = self.item
r1.save()
r2 = TimeRestriction.objects.create(
timeframe_from=now() - timedelta(days=3),
timeframe_to=now() + timedelta(days=5),
event=self.event,
price=8
)
r2.item = self.item
r2.save()
result = signals.availability_handler(
self.event, item=self.item,
variations=self.item.get_all_variations(),
context=None, cache=self.event.get_cache()
)
self.assertEqual(len(result), 1)
self.assertIn('available', result[0])
self.assertTrue(result[0]['available'])
self.assertEqual(result[0]['price'], 8)
def test_multiple_overlapping_tomorrow(self):
r1 = TimeRestriction.objects.create(
timeframe_from=now() - timedelta(days=5),
timeframe_to=now() + timedelta(days=5),
event=self.event,
price=12
)
r1.item = self.item
r1.save()
r2 = TimeRestriction.objects.create(
timeframe_from=now() + timedelta(days=1),
timeframe_to=now() + timedelta(days=7),
event=self.event,
price=8
)
r2.item = self.item
r2.save()
result = signals.availability_handler(
self.event, item=self.item,
variations=self.item.get_all_variations(),
context=None, cache=self.event.get_cache()
)
self.assertEqual(len(result), 1)
self.assertIn('available', result[0])
self.assertTrue(result[0]['available'])
self.assertEqual(result[0]['price'], 12)
def test_multiple_distinct_available(self):
r1 = TimeRestriction.objects.create(
timeframe_from=now() - timedelta(days=5),
timeframe_to=now() + timedelta(days=2),
event=self.event,
price=12
)
r1.item = self.item
r1.save()
r2 = TimeRestriction.objects.create(
timeframe_from=now() + timedelta(days=4),
timeframe_to=now() + timedelta(days=7),
event=self.event,
price=8
)
r2.item = self.item
r2.save()
result = signals.availability_handler(
self.event, item=self.item,
variations=self.item.get_all_variations(),
context=None, cache=self.event.get_cache()
)
self.assertEqual(len(result), 1)
self.assertIn('available', result[0])
self.assertTrue(result[0]['available'])
self.assertEqual(result[0]['price'], 12)
def test_multiple_distinct_unavailable(self):
r1 = TimeRestriction.objects.create(
timeframe_from=now() - timedelta(days=5),
timeframe_to=now() - timedelta(days=1),
event=self.event,
price=12
)
r1.item = self.item
r1.save()
r2 = TimeRestriction.objects.create(
timeframe_from=now() + timedelta(days=4),
timeframe_to=now() + timedelta(days=7),
event=self.event,
price=8
)
r2.item = self.item
r2.save()
result = signals.availability_handler(
self.event, item=self.item,
variations=self.item.get_all_variations(),
context=None, cache=self.event.get_cache()
)
self.assertEqual(len(result), 1)
self.assertIn('available', result[0])
self.assertFalse(result[0]['available'])
def test_variation_specific(self):
self.item.properties.add(self.property)
r1 = TimeRestriction.objects.create(
timeframe_from=now() - timedelta(days=5),
timeframe_to=now() + timedelta(days=1),
event=self.event,
price=12
)
r1.item = self.item
r1.save()
r1.variations.add(self.variation1)
result = signals.availability_handler(
self.event, item=self.item,
variations=self.item.get_all_variations(),
context=None, cache=self.event.get_cache()
)
self.assertEqual(len(result), 3)
for v in result:
if 'variation' in v and v['variation'].pk == self.variation1.pk:
self.assertTrue(v['available'])
self.assertEqual(v['price'], 12)
else:
self.assertFalse(v['available'])
def test_variation_specifics(self):
self.item.properties.add(self.property)
r1 = TimeRestriction.objects.create(
timeframe_from=now() - timedelta(days=5),
timeframe_to=now() + timedelta(days=1),
event=self.event,
price=12
)
r1.item = self.item
r1.save()
r1.variations.add(self.variation1)
r2 = TimeRestriction.objects.create(
timeframe_from=now() - timedelta(days=5),
timeframe_to=now() + timedelta(days=1),
event=self.event,
price=8
)
r2.item = self.item
r2.save()
r2.variations.add(self.variation1)
r3 = TimeRestriction.objects.create(
timeframe_from=now() - timedelta(days=5),
timeframe_to=now() - timedelta(days=1),
event=self.event,
price=8
)
r3.item = self.item
r3.save()
r3.variations.add(self.variation3)
result = signals.availability_handler(
self.event, item=self.item,
variations=self.item.get_all_variations(),
context=None, cache=self.event.get_cache()
)
self.assertEqual(len(result), 3)
for v in result:
if 'variation' in v and v['variation'].pk == self.variation1.pk:
self.assertTrue(v['available'])
self.assertEqual(v['price'], 8)
else:
self.assertFalse(v['available'])