diff --git a/src/requirements.txt b/src/requirements.txt index 401637a530..8038937b1c 100644 --- a/src/requirements.txt +++ b/src/requirements.txt @@ -3,6 +3,7 @@ Django>=1.7 pytz django-bootstrap3 -e git+https://github.com/tixl/django-formset-js.git@master#egg=django-formset-js +-e git+https://github.com/tixl/cleanerversion.git@tixl#egg=cleanerversion # Deployment / static file compilation requirements django-compressor diff --git a/src/tixlbase/cache.py b/src/tixlbase/cache.py index 89ce3dc6be..c84369b256 100644 --- a/src/tixlbase/cache.py +++ b/src/tixlbase/cache.py @@ -22,7 +22,7 @@ class EventRelatedCache: def __init__(self, event: Event, cache: str='default'): self.cache = caches[cache] self.event = event - self.prefixkey = 'event:%d' % self.event.pk + self.prefixkey = 'event:%s' % self.event.pk def _prefix_key(self, original_key: str) -> str: # Race conditions can happen here, but should be very very rare. @@ -34,7 +34,7 @@ class EventRelatedCache: if prefix is None: prefix = int(time.time()) self.cache.set(self.prefixkey, prefix) - key = 'event:%d:%d:%s' % (self.event.pk, prefix, original_key) + key = 'event:%s:%d:%s' % (self.event.pk, prefix, original_key) if len(key) > 200: # Hash long keys, as memcached has a length limit # TODO: Use a more efficient, non-cryptographic hash algorithm key = hashlib.sha256(key.encode("UTF-8")).hexdigest() diff --git a/src/tixlbase/forms.py b/src/tixlbase/forms.py new file mode 100644 index 0000000000..ae46dc43da --- /dev/null +++ b/src/tixlbase/forms.py @@ -0,0 +1,15 @@ +from django.forms.models import ModelFormMetaclass, BaseModelForm +from django.utils import six +from versions.models import Versionable + + +class VersionedBaseModelForm(BaseModelForm): + def save(self, commit=True): + if self.instance.pk is not None and isinstance(self.instance, Versionable): + if self.has_changed(): + self.instance = self.instance.clone() + super().save(commit) + + +class VersionedModelForm(six.with_metaclass(ModelFormMetaclass, VersionedBaseModelForm)): + pass diff --git a/src/tixlbase/middleware.py b/src/tixlbase/middleware.py index c60c2ff3e5..40200069d5 100644 --- a/src/tixlbase/middleware.py +++ b/src/tixlbase/middleware.py @@ -30,7 +30,7 @@ class LocaleMiddleware(BaseLocaleMiddleware): url = resolve(request.path_info) if 'event' in url.kwargs and 'organizer' in url.kwargs: try: - request.event = Event.objects.get( + request.event = Event.objects.current.get( slug=url.kwargs['event'], organizer__slug=url.kwargs['organizer'], ) diff --git a/src/tixlbase/migrations/0001_initial.py b/src/tixlbase/migrations/0001_initial.py deleted file mode 100644 index d435f936d0..0000000000 --- a/src/tixlbase/migrations/0001_initial.py +++ /dev/null @@ -1,101 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import models, migrations -import django.utils.timezone -from django.conf import settings - - -class Migration(migrations.Migration): - - dependencies = [ - ('auth', '0001_initial'), - ] - - operations = [ - migrations.CreateModel( - name='User', - fields=[ - ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)), - ('password', models.CharField(verbose_name='password', max_length=128)), - ('last_login', models.DateTimeField(verbose_name='last login', default=django.utils.timezone.now)), - ('is_superuser', models.BooleanField(verbose_name='superuser status', default=False, help_text='Designates that this user has all permissions without explicitly assigning them.')), - ('identifier', models.CharField(unique=True, max_length=255)), - ('username', models.CharField(max_length=120)), - ('email', models.EmailField(blank=True, null=True, db_index=True, max_length=75)), - ('is_active', models.BooleanField(default=True)), - ('is_staff', models.BooleanField(default=False)), - ('date_joined', models.DateTimeField(auto_now_add=True)), - ], - options={ - }, - bases=(models.Model,), - ), - migrations.CreateModel( - name='Event', - fields=[ - ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)), - ('name', models.CharField(max_length=200)), - ('slug', models.CharField(db_index=True, max_length=50)), - ('locale', models.CharField(max_length=10)), - ('currency', models.CharField(max_length=10)), - ('date_from', models.DateTimeField()), - ('date_to', models.DateTimeField(blank=True, null=True)), - ('show_date_to', models.BooleanField(default=True)), - ('show_times', models.BooleanField(default=True)), - ('presale_end', models.DateTimeField(blank=True, null=True)), - ('presale_start', models.DateTimeField(blank=True, null=True)), - ('payment_term_days', models.IntegerField(default=14)), - ('payment_term_last', models.DateTimeField(blank=True, null=True)), - ], - options={ - 'ordering': ('date_from', 'name'), - }, - bases=(models.Model,), - ), - migrations.CreateModel( - name='Organizer', - fields=[ - ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)), - ('name', models.CharField(max_length=200)), - ('slug', models.CharField(unique=True, db_index=True, max_length=50)), - ('owner', models.ForeignKey(blank=True, null=True, to=settings.AUTH_USER_MODEL)), - ], - options={ - 'ordering': ('name',), - }, - bases=(models.Model,), - ), - migrations.AddField( - model_name='event', - name='organizer', - field=models.ForeignKey(to='tixlbase.Organizer', related_name='events'), - preserve_default=True, - ), - migrations.AlterUniqueTogether( - name='event', - unique_together=set([('organizer', 'slug')]), - ), - migrations.AddField( - model_name='user', - name='event', - field=models.ForeignKey(to='tixlbase.Event', blank=True, null=True, related_name='users'), - preserve_default=True, - ), - migrations.AddField( - model_name='user', - name='groups', - field=models.ManyToManyField(verbose_name='groups', related_name='user_set', related_query_name='user', blank=True, to='auth.Group', help_text='The groups this user belongs to. A user will get all permissions granted to each of his/her group.'), - preserve_default=True, - ), - migrations.AddField( - model_name='user', - name='user_permissions', - field=models.ManyToManyField(verbose_name='user permissions', related_name='user_set', related_query_name='user', blank=True, to='auth.Permission', help_text='Specific permissions for this user.'), - preserve_default=True, - ), - migrations.AlterUniqueTogether( - name='user', - unique_together=set([('event', 'username')]), - ), - ] diff --git a/src/tixlbase/migrations/0002_auto_20140910_1628.py b/src/tixlbase/migrations/0002_auto_20140910_1628.py deleted file mode 100644 index d08eaa8e26..0000000000 --- a/src/tixlbase/migrations/0002_auto_20140910_1628.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import models, migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('tixlbase', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='user', - name='familyname', - field=models.CharField(blank=True, max_length=255, null=True), - preserve_default=True, - ), - migrations.AddField( - model_name='user', - name='givenname', - field=models.CharField(blank=True, max_length=255, null=True), - preserve_default=True, - ), - ] diff --git a/src/tixlbase/migrations/0003_auto_20140910_1649.py b/src/tixlbase/migrations/0003_auto_20140910_1649.py deleted file mode 100644 index 826d1a1c9e..0000000000 --- a/src/tixlbase/migrations/0003_auto_20140910_1649.py +++ /dev/null @@ -1,19 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import models, migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('tixlbase', '0002_auto_20140910_1628'), - ] - - operations = [ - migrations.AlterField( - model_name='user', - name='username', - field=models.CharField(blank=True, max_length=120, null=True, help_text='Letters, digits and @/./+/-/_ only.'), - ), - ] diff --git a/src/tixlbase/migrations/0004_auto_20140911_2037.py b/src/tixlbase/migrations/0004_auto_20140911_2037.py deleted file mode 100644 index be2571dd55..0000000000 --- a/src/tixlbase/migrations/0004_auto_20140911_2037.py +++ /dev/null @@ -1,61 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import models, migrations -from django.conf import settings -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('tixlbase', '0003_auto_20140910_1649'), - ] - - operations = [ - migrations.CreateModel( - name='OrganizerPermission', - fields=[ - ('id', models.AutoField(auto_created=True, verbose_name='ID', serialize=False, primary_key=True)), - ('can_create_events', models.BooleanField(default=True)), - ('organizer', models.ForeignKey(to='tixlbase.Organizer', related_name='perms')), - ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL, related_name='organizer_perms')), - ], - options={ - }, - bases=(models.Model,), - ), - migrations.AlterUniqueTogether( - name='organizerpermission', - unique_together=set([('organizer', 'user')]), - ), - migrations.RemoveField( - model_name='organizer', - name='owner', - ), - migrations.AlterField( - model_name='event', - name='organizer', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='events', to='tixlbase.Organizer'), - ), - migrations.AlterField( - model_name='user', - name='email', - field=models.EmailField(null=True, blank=True, db_index=True, verbose_name='E-mail', max_length=75), - ), - migrations.AlterField( - model_name='user', - name='event', - field=models.ForeignKey(null=True, blank=True, on_delete=django.db.models.deletion.PROTECT, related_name='users', to='tixlbase.Event'), - ), - migrations.AlterField( - model_name='user', - name='familyname', - field=models.CharField(null=True, blank=True, verbose_name='Family name', max_length=255), - ), - migrations.AlterField( - model_name='user', - name='givenname', - field=models.CharField(null=True, blank=True, verbose_name='Given name', max_length=255), - ), - ] diff --git a/src/tixlbase/migrations/0005_auto_20140911_2052.py b/src/tixlbase/migrations/0005_auto_20140911_2052.py deleted file mode 100644 index e17580d85a..0000000000 --- a/src/tixlbase/migrations/0005_auto_20140911_2052.py +++ /dev/null @@ -1,48 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import models, migrations -from django.conf import settings - - -class Migration(migrations.Migration): - - dependencies = [ - ('tixlbase', '0004_auto_20140911_2037'), - ] - - operations = [ - migrations.CreateModel( - name='EventPermission', - fields=[ - ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), - ('can_change_settings', models.BooleanField(default=True)), - ('organizer', models.ForeignKey(to='tixlbase.Event')), - ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL, related_name='event_perms')), - ], - options={ - }, - bases=(models.Model,), - ), - migrations.AlterUniqueTogether( - name='eventpermission', - unique_together=set([('organizer', 'user')]), - ), - migrations.AddField( - model_name='event', - name='permitted', - field=models.ManyToManyField(to=settings.AUTH_USER_MODEL, related_name='events', through='tixlbase.EventPermission'), - preserve_default=True, - ), - migrations.AddField( - model_name='organizer', - name='permitted', - field=models.ManyToManyField(to=settings.AUTH_USER_MODEL, related_name='organizers', through='tixlbase.OrganizerPermission'), - preserve_default=True, - ), - migrations.AlterField( - model_name='organizerpermission', - name='organizer', - field=models.ForeignKey(to='tixlbase.Organizer'), - ), - ] diff --git a/src/tixlbase/migrations/0006_auto_20140912_1855.py b/src/tixlbase/migrations/0006_auto_20140912_1855.py deleted file mode 100644 index 8546e2fd72..0000000000 --- a/src/tixlbase/migrations/0006_auto_20140912_1855.py +++ /dev/null @@ -1,138 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import models, migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('tixlbase', '0005_auto_20140911_2052'), - ] - - operations = [ - migrations.AlterModelOptions( - name='event', - options={'verbose_name_plural': 'Events', 'verbose_name': 'Event', 'ordering': ('date_from', 'name')}, - ), - migrations.AlterModelOptions( - name='eventpermission', - options={'verbose_name_plural': 'Event permissions', 'verbose_name': 'Event permission'}, - ), - migrations.AlterModelOptions( - name='organizer', - options={'verbose_name_plural': 'Organizers', 'verbose_name': 'Organizer', 'ordering': ('name',)}, - ), - migrations.AlterModelOptions( - name='organizerpermission', - options={'verbose_name_plural': 'Organizer permissions', 'verbose_name': 'Organizer permission'}, - ), - migrations.AlterModelOptions( - name='user', - options={'verbose_name_plural': 'Users', 'verbose_name': 'User'}, - ), - migrations.RenameField( - model_name='eventpermission', - old_name='organizer', - new_name='event', - ), - migrations.AlterField( - model_name='event', - name='currency', - field=models.CharField(max_length=10, verbose_name='Default currency'), - ), - migrations.AlterField( - model_name='event', - name='date_from', - field=models.DateTimeField(verbose_name='Event start time'), - ), - migrations.AlterField( - model_name='event', - name='date_to', - field=models.DateTimeField(blank=True, null=True, verbose_name='Event end time'), - ), - migrations.AlterField( - model_name='event', - name='locale', - field=models.CharField(max_length=10, verbose_name='Default locale', choices=[('de', 'German'), ('en', 'English')]), - ), - migrations.AlterField( - model_name='event', - name='name', - field=models.CharField(max_length=200, verbose_name='Name'), - ), - migrations.AlterField( - model_name='event', - name='payment_term_days', - field=models.IntegerField(verbose_name='Payment term in days', default=14), - ), - migrations.AlterField( - model_name='event', - name='payment_term_last', - field=models.DateTimeField(blank=True, null=True, verbose_name='Last date of payments'), - ), - migrations.AlterField( - model_name='event', - name='presale_end', - field=models.DateTimeField(blank=True, null=True, verbose_name='End of presale'), - ), - migrations.AlterField( - model_name='event', - name='presale_start', - field=models.DateTimeField(blank=True, null=True, verbose_name='Start of presale'), - ), - migrations.AlterField( - model_name='event', - name='show_date_to', - field=models.BooleanField(verbose_name='Show event end date', default=True), - ), - migrations.AlterField( - model_name='event', - name='show_times', - field=models.BooleanField(verbose_name='Show dates with time', default=True), - ), - migrations.AlterField( - model_name='event', - name='slug', - field=models.CharField(db_index=True, max_length=50, verbose_name='Slug'), - ), - migrations.AlterField( - model_name='eventpermission', - name='can_change_settings', - field=models.BooleanField(verbose_name='Can change event settings', default=True), - ), - migrations.AlterField( - model_name='organizer', - name='name', - field=models.CharField(max_length=200, verbose_name='Name'), - ), - migrations.AlterField( - model_name='organizer', - name='slug', - field=models.CharField(db_index=True, max_length=50, verbose_name='Slug', unique=True), - ), - migrations.AlterField( - model_name='organizerpermission', - name='can_create_events', - field=models.BooleanField(verbose_name='Can create events', default=True), - ), - migrations.AlterField( - model_name='user', - name='date_joined', - field=models.DateTimeField(verbose_name='Date joined', auto_now_add=True), - ), - migrations.AlterField( - model_name='user', - name='is_active', - field=models.BooleanField(verbose_name='Is active', default=True), - ), - migrations.AlterField( - model_name='user', - name='is_staff', - field=models.BooleanField(verbose_name='Is site admin', default=False), - ), - migrations.AlterUniqueTogether( - name='eventpermission', - unique_together=set([('event', 'user')]), - ), - ] diff --git a/src/tixlbase/migrations/0007_auto_20140914_1301.py b/src/tixlbase/migrations/0007_auto_20140914_1301.py deleted file mode 100644 index 2f12faca70..0000000000 --- a/src/tixlbase/migrations/0007_auto_20140914_1301.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import models, migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('tixlbase', '0006_auto_20140912_1855'), - ] - - operations = [ - migrations.AddField( - model_name='user', - name='locale', - field=models.CharField(choices=[('de', 'German'), ('en', 'English')], max_length=50, default='en'), - preserve_default=True, - ), - migrations.AddField( - model_name='user', - name='timezone', - field=models.CharField(max_length=100, default='UTC'), - preserve_default=True, - ), - ] diff --git a/src/tixlbase/migrations/0008_auto_20140914_1304.py b/src/tixlbase/migrations/0008_auto_20140914_1304.py deleted file mode 100644 index 6f75d80519..0000000000 --- a/src/tixlbase/migrations/0008_auto_20140914_1304.py +++ /dev/null @@ -1,30 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import models, migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('tixlbase', '0007_auto_20140914_1301'), - ] - - operations = [ - migrations.AddField( - model_name='event', - name='timezone', - field=models.CharField(max_length=100, default='UTC', verbose_name='Default timezone'), - preserve_default=True, - ), - migrations.AlterField( - model_name='user', - name='locale', - field=models.CharField(max_length=50, verbose_name='Language', default='en', choices=[('de', 'German'), ('en', 'English')]), - ), - migrations.AlterField( - model_name='user', - name='timezone', - field=models.CharField(max_length=100, default='UTC', verbose_name='Timezone'), - ), - ] diff --git a/src/tixlbase/migrations/0009_auto_20140916_2120.py b/src/tixlbase/migrations/0009_auto_20140916_2120.py deleted file mode 100644 index 82612f4f1c..0000000000 --- a/src/tixlbase/migrations/0009_auto_20140916_2120.py +++ /dev/null @@ -1,144 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import models, migrations -import django.core.validators -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('tixlbase', '0008_auto_20140914_1304'), - ] - - operations = [ - migrations.CreateModel( - name='Item', - fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, auto_created=True, verbose_name='ID')), - ('name', models.CharField(max_length=255, verbose_name='Item name')), - ('active', models.BooleanField(default=True)), - ('deleted', models.BooleanField(default=False)), - ('short_description', models.TextField(help_text='This is shown below the item name in lists.', blank=True, null=True, verbose_name='Short description')), - ('long_description', models.TextField(blank=True, null=True, verbose_name='Long description')), - ('default_price', models.DecimalField(decimal_places=2, blank=True, max_digits=7, null=True, verbose_name='Default price')), - ('tax_rate', models.DecimalField(decimal_places=2, blank=True, max_digits=7, null=True, verbose_name='Included taxes in percent')), - ], - options={ - 'verbose_name_plural': 'Items', - 'verbose_name': 'Item', - }, - bases=(models.Model,), - ), - migrations.CreateModel( - name='ItemCategory', - fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, auto_created=True, verbose_name='ID')), - ('name', models.CharField(max_length=255, verbose_name='Category name')), - ('position', models.IntegerField(blank=True, null=True)), - ('event', models.ForeignKey(to='tixlbase.Event')), - ], - options={ - 'verbose_name_plural': 'Item categories', - 'ordering': ('position',), - 'verbose_name': 'Item category', - }, - bases=(models.Model,), - ), - migrations.CreateModel( - name='ItemFlavor', - fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, auto_created=True, verbose_name='ID')), - ('active', models.BooleanField(default=True)), - ('default_price', models.DecimalField(decimal_places=2, blank=True, max_digits=7, null=True, verbose_name='Default price')), - ('item', models.ForeignKey(to='tixlbase.Item', related_name='flavors')), - ], - options={ - }, - bases=(models.Model,), - ), - migrations.CreateModel( - name='Property', - fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, auto_created=True, verbose_name='ID')), - ('name', models.CharField(max_length=250, verbose_name='Property name')), - ('event', models.ForeignKey(to='tixlbase.Event')), - ], - options={ - 'verbose_name_plural': 'Item properties', - 'verbose_name': 'Item property', - }, - bases=(models.Model,), - ), - migrations.CreateModel( - name='PropertyValue', - fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, auto_created=True, verbose_name='ID')), - ('value', models.CharField(max_length=250, verbose_name='Value')), - ('prop', models.ForeignKey(to='tixlbase.Property', related_name='values')), - ], - options={ - }, - bases=(models.Model,), - ), - migrations.AddField( - model_name='itemflavor', - name='prop', - field=models.ManyToManyField(related_name='values', to='tixlbase.PropertyValue'), - preserve_default=True, - ), - migrations.AddField( - model_name='item', - name='category', - field=models.ForeignKey(blank=True, to='tixlbase.ItemCategory', on_delete=django.db.models.deletion.PROTECT, null=True), - preserve_default=True, - ), - migrations.AddField( - model_name='item', - name='event', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='tixlbase.Event'), - preserve_default=True, - ), - migrations.AddField( - model_name='item', - name='properties', - field=models.ManyToManyField(related_name='items', to='tixlbase.Property'), - preserve_default=True, - ), - migrations.AlterField( - model_name='event', - name='payment_term_days', - field=models.IntegerField(help_text='The number of days after placing an order the user has to pay to preserve his reservation.', default=14, verbose_name='Payment term in days'), - ), - migrations.AlterField( - model_name='event', - name='payment_term_last', - field=models.DateTimeField(help_text='The last date any payments are accepted. This has precedence over the number of days configured above.', blank=True, null=True, verbose_name='Last date of payments'), - ), - migrations.AlterField( - model_name='event', - name='presale_end', - field=models.DateTimeField(help_text='No items will be sold after this date.', blank=True, null=True, verbose_name='End of presale'), - ), - migrations.AlterField( - model_name='event', - name='presale_start', - field=models.DateTimeField(help_text='No items will be sold before this date.', blank=True, null=True, verbose_name='Start of presale'), - ), - migrations.AlterField( - model_name='event', - name='show_date_to', - field=models.BooleanField(help_text="If disabled, only event's start date will be displayed to the public.", default=True, verbose_name='Show event end date'), - ), - migrations.AlterField( - model_name='event', - name='show_times', - field=models.BooleanField(help_text="If disabled, the event's start and end date will be displayed without the time of day.", default=True, verbose_name='Show dates with time'), - ), - migrations.AlterField( - model_name='event', - name='slug', - field=models.CharField(db_index=True, help_text='Should be short, only contain lowercase letters and numbers, and must be unique among your events. This is being used in addresses and bank transfer references.', validators=[django.core.validators.RegexValidator(message='The slug may only contain letters, numbers, dots and dashes.', regex='^[a-zA-Z0-9.-]+$')], verbose_name='Slug', max_length=50), - ), - ] diff --git a/src/tixlbase/migrations/0010_auto_20140927_1006.py b/src/tixlbase/migrations/0010_auto_20140927_1006.py deleted file mode 100644 index e119fbdc3f..0000000000 --- a/src/tixlbase/migrations/0010_auto_20140927_1006.py +++ /dev/null @@ -1,30 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import models, migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('tixlbase', '0009_auto_20140916_2120'), - ] - - operations = [ - migrations.RemoveField( - model_name='itemflavor', - name='prop', - ), - migrations.AddField( - model_name='eventpermission', - name='can_change_items', - field=models.BooleanField(verbose_name='Can change item settings', default=True), - preserve_default=True, - ), - migrations.AddField( - model_name='itemflavor', - name='values', - field=models.ManyToManyField(to='tixlbase.PropertyValue', related_name='flavors'), - preserve_default=True, - ), - ] diff --git a/src/tixlbase/migrations/0011_auto_20140927_1013.py b/src/tixlbase/migrations/0011_auto_20140927_1013.py deleted file mode 100644 index 96b2a0d5b4..0000000000 --- a/src/tixlbase/migrations/0011_auto_20140927_1013.py +++ /dev/null @@ -1,49 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import models, migrations -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('tixlbase', '0010_auto_20140927_1006'), - ] - - operations = [ - migrations.CreateModel( - name='ItemVariation', - fields=[ - ('id', models.AutoField(serialize=False, primary_key=True, verbose_name='ID', auto_created=True)), - ('active', models.BooleanField(default=True)), - ('default_price', models.DecimalField(max_digits=7, decimal_places=2, blank=True, null=True, verbose_name='Default price')), - ('item', models.ForeignKey(related_name='variations', to='tixlbase.Item')), - ('values', models.ManyToManyField(related_name='variations', to='tixlbase.PropertyValue')), - ], - options={ - }, - bases=(models.Model,), - ), - migrations.RemoveField( - model_name='itemflavor', - name='item', - ), - migrations.RemoveField( - model_name='itemflavor', - name='values', - ), - migrations.DeleteModel( - name='ItemFlavor', - ), - migrations.AlterField( - model_name='item', - name='category', - field=models.ForeignKey(null=True, blank=True, on_delete=django.db.models.deletion.PROTECT, related_name='items', to='tixlbase.ItemCategory'), - ), - migrations.AlterField( - model_name='item', - name='event', - field=models.ForeignKey(to='tixlbase.Event', on_delete=django.db.models.deletion.PROTECT, related_name='items'), - ), - ] diff --git a/src/tixlbase/migrations/0012_auto_20140929_1935.py b/src/tixlbase/migrations/0012_auto_20140929_1935.py deleted file mode 100644 index 527a88195a..0000000000 --- a/src/tixlbase/migrations/0012_auto_20140929_1935.py +++ /dev/null @@ -1,66 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import models, migrations -import django.db.models.deletion - -def setposition(apps, schema_editor): - ItemCategory = apps.get_model("tixlbase", "ItemCategory") - for cat in ItemCategory.objects.all(): - cat.position = 0 - cat.save() - -class Migration(migrations.Migration): - - dependencies = [ - ('tixlbase', '0011_auto_20140927_1013'), - ] - - operations = [ - migrations.RunPython(setposition), - migrations.AlterField( - model_name='item', - name='active', - field=models.BooleanField(default=True, verbose_name='Active'), - ), - migrations.AlterField( - model_name='item', - name='category', - field=models.ForeignKey(blank=True, null=True, verbose_name='Category', related_name='items', to='tixlbase.ItemCategory', on_delete=django.db.models.deletion.PROTECT), - ), - migrations.AlterField( - model_name='item', - name='event', - field=models.ForeignKey(to='tixlbase.Event', related_name='items', verbose_name='Event', on_delete=django.db.models.deletion.PROTECT), - ), - migrations.AlterField( - model_name='item', - name='properties', - field=models.ManyToManyField(to='tixlbase.Property', help_text="The selected properties will be available for the user to select. After saving this field, move to the 'Variations' tab to configure the details.", blank=True, verbose_name='Properties', related_name='items'), - ), - migrations.AlterField( - model_name='item', - name='tax_rate', - field=models.DecimalField(max_digits=7, verbose_name='Taxes included in percent', blank=True, null=True, decimal_places=2), - ), - migrations.AlterField( - model_name='itemcategory', - name='event', - field=models.ForeignKey(related_name='categories', to='tixlbase.Event'), - ), - migrations.AlterField( - model_name='itemcategory', - name='position', - field=models.IntegerField(default=0), - ), - migrations.AlterField( - model_name='itemvariation', - name='active', - field=models.BooleanField(default=True, verbose_name='Active'), - ), - migrations.AlterField( - model_name='property', - name='event', - field=models.ForeignKey(related_name='properties', to='tixlbase.Event'), - ), - ] diff --git a/src/tixlbase/migrations/0013_propertyvalue_position.py b/src/tixlbase/migrations/0013_propertyvalue_position.py deleted file mode 100644 index 77b5091e9e..0000000000 --- a/src/tixlbase/migrations/0013_propertyvalue_position.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import models, migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('tixlbase', '0012_auto_20140929_1935'), - ] - - operations = [ - migrations.AddField( - model_name='propertyvalue', - name='position', - field=models.IntegerField(default=0), - preserve_default=True, - ), - ] diff --git a/src/tixlbase/migrations/0014_auto_20141005_1037.py b/src/tixlbase/migrations/0014_auto_20141005_1037.py deleted file mode 100644 index 98694c1867..0000000000 --- a/src/tixlbase/migrations/0014_auto_20141005_1037.py +++ /dev/null @@ -1,43 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import models, migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('tixlbase', '0013_propertyvalue_position'), - ] - - operations = [ - migrations.CreateModel( - name='Question', - fields=[ - ('id', models.AutoField(primary_key=True, auto_created=True, verbose_name='ID', serialize=False)), - ('question', models.TextField(verbose_name='Question')), - ('type', models.CharField(choices=[('N', 'Number'), ('S', 'Text (one line)'), ('T', 'Multiline text'), ('B', 'Yes/No')], max_length=5, verbose_name='Question type')), - ('required', models.BooleanField(default=False, verbose_name='Required question')), - ('event', models.ForeignKey(to='tixlbase.Event', related_name='events')), - ], - options={ - 'verbose_name': 'Question', - 'verbose_name_plural': 'Questions', - }, - bases=(models.Model,), - ), - migrations.AlterModelOptions( - name='itemvariation', - options={'verbose_name': 'Item variation', 'verbose_name_plural': 'Item variations'}, - ), - migrations.AlterModelOptions( - name='propertyvalue', - options={'ordering': ('position',), 'verbose_name': 'Property value', 'verbose_name_plural': 'Property values'}, - ), - migrations.AddField( - model_name='item', - name='questions', - field=models.ManyToManyField(to='tixlbase.Question', related_name='questions', blank=True, verbose_name='Questions', help_text='The user will be asked to fill in answers for the selected questions'), - preserve_default=True, - ), - ] diff --git a/src/tixlbase/migrations/0015_auto_20141006_2205.py b/src/tixlbase/migrations/0015_auto_20141006_2205.py deleted file mode 100644 index 5dee309cf0..0000000000 --- a/src/tixlbase/migrations/0015_auto_20141006_2205.py +++ /dev/null @@ -1,24 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import models, migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('tixlbase', '0014_auto_20141005_1037'), - ] - - operations = [ - migrations.AlterField( - model_name='item', - name='questions', - field=models.ManyToManyField(blank=True, related_name='items', verbose_name='Questions', help_text='The user will be asked to fill in answers for the selected questions', to='tixlbase.Question'), - ), - migrations.AlterField( - model_name='question', - name='event', - field=models.ForeignKey(to='tixlbase.Event', related_name='questions'), - ), - ] diff --git a/src/tixlbase/migrations/0016_event_plugins.py b/src/tixlbase/migrations/0016_event_plugins.py deleted file mode 100644 index 753cc19e39..0000000000 --- a/src/tixlbase/migrations/0016_event_plugins.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import models, migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('tixlbase', '0015_auto_20141006_2205'), - ] - - operations = [ - migrations.AddField( - model_name='event', - name='plugins', - field=models.TextField(blank=True, verbose_name='Plugins', null=True), - preserve_default=True, - ), - ] diff --git a/src/tixlbase/migrations/0017_auto_20141017_2148.py b/src/tixlbase/migrations/0017_auto_20141017_2148.py deleted file mode 100644 index 0e14a1bdb9..0000000000 --- a/src/tixlbase/migrations/0017_auto_20141017_2148.py +++ /dev/null @@ -1,125 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import models, migrations -import tixlbase.models -from django.conf import settings - - -class Migration(migrations.Migration): - - dependencies = [ - ('tixlbase', '0016_event_plugins'), - ] - - operations = [ - migrations.CreateModel( - name='CartPosition', - fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('session', models.CharField(null=True, max_length=255, blank=True, verbose_name='Session key')), - ('total', models.DecimalField(max_digits=10, verbose_name='Price', decimal_places=2)), - ('datetime', models.DateTimeField(verbose_name='Datetime')), - ('expires', models.DateTimeField(verbose_name='Expiration date')), - ('event', models.ForeignKey(to='tixlbase.Event', verbose_name='Event')), - ('item', models.ForeignKey(to='tixlbase.Item', verbose_name='Item')), - ('user', models.ForeignKey(null=True, blank=True, to=settings.AUTH_USER_MODEL, verbose_name='User')), - ('variation', models.ForeignKey(null=True, blank=True, to='tixlbase.ItemVariation', verbose_name='Variation')), - ], - options={ - 'verbose_name_plural': 'Cart positions', - 'verbose_name': 'Cart position', - }, - bases=(models.Model,), - ), - migrations.CreateModel( - name='Order', - fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('status', models.CharField(max_length=3, choices=[('p', 'pending'), ('n', 'paid'), ('e', 'expired'), ('c', 'cancelled')], verbose_name='Status')), - ('datetime', models.DateTimeField(auto_now_add=True, verbose_name='Date')), - ('expires', models.DateTimeField(verbose_name='Expiration date')), - ('payment_date', models.DateTimeField(verbose_name='Payment date')), - ('payment_info', models.TextField(verbose_name='Payment information')), - ('total', models.DecimalField(max_digits=10, verbose_name='Total amount', decimal_places=2)), - ('event', models.ForeignKey(to='tixlbase.Event', verbose_name='Event')), - ('user', models.ForeignKey(null=True, blank=True, to=settings.AUTH_USER_MODEL, verbose_name='User')), - ], - options={ - 'verbose_name_plural': 'Orders', - 'verbose_name': 'Order', - }, - bases=(models.Model,), - ), - migrations.CreateModel( - name='OrderPosition', - fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('price', models.DecimalField(max_digits=10, verbose_name='Price', decimal_places=2)), - ], - options={ - 'verbose_name_plural': 'Order positions', - 'verbose_name': 'Order position', - }, - bases=(models.Model,), - ), - migrations.CreateModel( - name='QuestionAnswer', - fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('answer', models.TextField()), - ('cartposition', models.ForeignKey(null=True, blank=True, to='tixlbase.CartPosition')), - ('orderposition', models.ForeignKey(null=True, blank=True, to='tixlbase.OrderPosition')), - ('question', models.ForeignKey(to='tixlbase.Question')), - ], - options={ - }, - bases=(models.Model,), - ), - migrations.CreateModel( - name='Quota', - fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('name', models.CharField(max_length=200, verbose_name='Name')), - ('size', models.PositiveIntegerField(verbose_name='Total capacity')), - ('items', models.ManyToManyField(to='tixlbase.Item', blank=True, verbose_name='Item')), - ('lock_cache', models.ManyToManyField(to='tixlbase.CartPosition', blank=True)), - ('order_cache', models.ManyToManyField(to='tixlbase.OrderPosition', blank=True)), - ('variations', tixlbase.models.VariationsField(to='tixlbase.ItemVariation', blank=True, verbose_name='Variations')), - ], - options={ - 'verbose_name_plural': 'Quotas', - 'verbose_name': 'Quota', - }, - bases=(models.Model,), - ), - migrations.AddField( - model_name='orderposition', - name='answers', - field=models.ManyToManyField(to='tixlbase.Question', through='tixlbase.QuestionAnswer', verbose_name='Answers'), - preserve_default=True, - ), - migrations.AddField( - model_name='orderposition', - name='item', - field=models.ForeignKey(to='tixlbase.Item', verbose_name='Item'), - preserve_default=True, - ), - migrations.AddField( - model_name='orderposition', - name='order', - field=models.ForeignKey(to='tixlbase.Order', verbose_name='Order'), - preserve_default=True, - ), - migrations.AddField( - model_name='orderposition', - name='variation', - field=models.ForeignKey(null=True, blank=True, to='tixlbase.ItemVariation', verbose_name='Variation'), - preserve_default=True, - ), - migrations.AlterField( - model_name='event', - name='payment_term_days', - field=models.PositiveIntegerField(verbose_name='Payment term in days', default=14, help_text='The number of days after placing an order the user has to pay to preserve his reservation.'), - ), - ] diff --git a/src/tixlbase/migrations/0018_auto_20141025_0908.py b/src/tixlbase/migrations/0018_auto_20141025_0908.py deleted file mode 100644 index 8220529200..0000000000 --- a/src/tixlbase/migrations/0018_auto_20141025_0908.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import models, migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('tixlbase', '0017_auto_20141017_2148'), - ] - - operations = [ - migrations.AddField( - model_name='quota', - name='event', - field=models.ForeignKey(to='tixlbase.Event', default=1, verbose_name='Event', related_name='quotas'), - preserve_default=False, - ), - migrations.AlterField( - model_name='cartposition', - name='datetime', - field=models.DateTimeField(verbose_name='Date'), - ), - ] diff --git a/src/tixlbase/migrations/__init__.py b/src/tixlbase/migrations/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/tixlbase/models.py b/src/tixlbase/models.py index 111509c1df..b09b570d94 100644 --- a/src/tixlbase/models.py +++ b/src/tixlbase/models.py @@ -6,6 +6,7 @@ from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, Permis from django.utils.translation import ugettext_lazy as _ from django.template.defaultfilters import date as _date from django.core.validators import RegexValidator +from versions.models import Versionable, VersionedForeignKey, VersionedManyToManyField from tixlbase.types import VariationDict @@ -141,7 +142,7 @@ class User(AbstractBaseUser, PermissionsMixin): return self.username -class Organizer(models.Model): +class Organizer(Versionable): """ This model represents an entity organizing events, like a company. Any organizer has a unique slug, which is a short name (alphanumeric, @@ -165,13 +166,13 @@ class Organizer(models.Model): return self.name -class OrganizerPermission(models.Model): +class OrganizerPermission(Versionable): """ The relation between an Organizer and an User who has permissions to access an organizer profile. """ - organizer = models.ForeignKey(Organizer) + organizer = VersionedForeignKey(Organizer) user = models.ForeignKey(User, related_name="organizer_perms") can_create_events = models.BooleanField( default=True, @@ -181,7 +182,6 @@ class OrganizerPermission(models.Model): class Meta: verbose_name = _("Organizer permission") verbose_name_plural = _("Organizer permissions") - unique_together = (("organizer", "user"),) def __str__(self): return _("%(name)s on %(object)s") % { @@ -190,7 +190,7 @@ class OrganizerPermission(models.Model): } -class Event(models.Model): +class Event(Versionable): """ 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, @@ -216,8 +216,8 @@ class Event(models.Model): matter when they were ordered (and thus, ignoring payment_term_days). """ - organizer = models.ForeignKey(Organizer, related_name="events", - on_delete=models.PROTECT) + organizer = VersionedForeignKey(Organizer, related_name="events", + on_delete=models.PROTECT) name = models.CharField(max_length=200, verbose_name=_("Name")) slug = models.CharField( @@ -284,7 +284,7 @@ class Event(models.Model): class Meta: verbose_name = _("Event") verbose_name_plural = _("Events") - unique_together = (("organizer", "slug"),) + # unique_together = (("organizer", "slug"),) # TODO: Enforce manually ordering = ("date_from", "name") def __str__(self): @@ -319,13 +319,13 @@ class Event(models.Model): return EventRelatedCache(self) -class EventPermission(models.Model): +class EventPermission(Versionable): """ The relation between an Event and an User who has permissions to access an event. """ - event = models.ForeignKey(Event) + event = VersionedForeignKey(Event) user = models.ForeignKey(User, related_name="event_perms") can_change_settings = models.BooleanField( default=True, @@ -339,7 +339,6 @@ class EventPermission(models.Model): class Meta: verbose_name = _("Event permission") verbose_name_plural = _("Event permissions") - unique_together = (("event", "user"),) def __str__(self): return _("%(name)s on %(object)s") % { @@ -348,11 +347,11 @@ class EventPermission(models.Model): } -class ItemCategory(models.Model): +class ItemCategory(Versionable): """ Items can be sorted into categories """ - event = models.ForeignKey( + event = VersionedForeignKey( Event, on_delete=models.CASCADE, related_name='categories', @@ -373,20 +372,25 @@ class ItemCategory(models.Model): def __str__(self): return self.name - def save(self, *args, **kwargs): + 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() - return super().save(*args, **kwargs) -class Property(models.Model): +class Property(Versionable): """ A property is a modifier which can be applied to an Item. For example 'Size' would be a property associated with the item 'T-Shirt'. """ - event = models.ForeignKey( + event = VersionedForeignKey( Event, related_name="properties", ) @@ -402,19 +406,24 @@ class Property(models.Model): def __str__(self): return self.name - def save(self, *args, **kwargs): + 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() - return super().save(*args, **kwargs) -class PropertyValue(models.Model): +class PropertyValue(Versionable): """ A value of a property. If the property would be 'T-Shirt size', this could be 'M' or 'L' """ - prop = models.ForeignKey( + prop = VersionedForeignKey( Property, on_delete=models.CASCADE, related_name="values" @@ -435,13 +444,18 @@ class PropertyValue(models.Model): def __str__(self): return "%s: %s" % (self.prop.name, self.value) - def save(self, *args, **kwargs): + def delete(self, *args, **kwargs): + super().delete(*args, **kwargs) + if self.prop: + self.prop.event.get_cache().clear() + + def save(self, *args, **kwargs): + super().save(*args, **kwargs) if self.prop: self.prop.event.get_cache().clear() - return super().save(*args, **kwargs) -class Question(models.Model): +class Question(Versionable): """ A question is an input field that can be used to extend a ticket by custom information, e.g. "Attendee name" or "Attendee age". @@ -457,7 +471,7 @@ class Question(models.Model): (TYPE_BOOLEAN, _("Yes/No")), ) - event = models.ForeignKey( + event = VersionedForeignKey( Event, related_name="questions", ) @@ -481,13 +495,18 @@ class Question(models.Model): def __str__(self): return self.question - def save(self, *args, **kwargs): + 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() - return super().save(*args, **kwargs) -class Item(models.Model): +class Item(Versionable): """ An item is a thing which can be sold. It belongs to an event and may or may not belong to a category. @@ -499,13 +518,13 @@ class Item(models.Model): inconsistencies. Instead, they have an attribute "deleted". Deleted items will not be shown anywhere. """ - event = models.ForeignKey( + event = VersionedForeignKey( Event, on_delete=models.PROTECT, related_name="items", verbose_name=_("Event"), ) - category = models.ForeignKey( + category = VersionedForeignKey( ItemCategory, on_delete=models.PROTECT, related_name="items", @@ -540,7 +559,7 @@ class Item(models.Model): verbose_name=_("Taxes included in percent"), max_digits=7, decimal_places=2 ) - properties = models.ManyToManyField( + properties = VersionedManyToManyField( Property, related_name='items', verbose_name=_("Properties"), @@ -551,7 +570,7 @@ class Item(models.Model): + '\'Variations\' tab to configure the details.' ) ) - questions = models.ManyToManyField( + questions = VersionedManyToManyField( Question, related_name='items', verbose_name=_("Questions"), @@ -570,14 +589,16 @@ class Item(models.Model): return self.name def save(self, *args, **kwargs): + super().save(*args, **kwargs) if self.event: self.event.get_cache().clear() - return super().save(*args, **kwargs) def delete(self): self.deleted = True self.active = False - return super().save() + super().save() + if self.event: + self.event.get_cache().clear() def get_all_variations(self, use_cache: bool=False) -> "list[VariationDict]": """ @@ -593,26 +614,26 @@ class Item(models.Model): if use_cache and hasattr(self, '_get_all_variations_cache'): return self._get_all_variations_cache - all_variations = self.variations.all().prefetch_related("values") - all_properties = self.properties.all().prefetch_related("values") + all_variations = self.variations.current.all().prefetch_related("values") + all_properties = self.properties.current.all().prefetch_related("values") variations_cache = {} for var in all_variations: key = [] - for v in var.values.all(): - key.append((v.prop_id, v.pk)) + for v in var.values.current.all(): + key.append((v.prop_id, v.identity)) key = tuple(sorted(key)) variations_cache[key] = var result = [] - for comb in product(*[prop.values.all() for prop in all_properties]): + for comb in product(*[prop.values.current.all() for prop in all_properties]): if len(comb) == 0: result.append(VariationDict()) continue key = [] var = VariationDict() for v in comb: - key.append((v.prop.pk, v.pk)) - var[v.prop.pk] = v + key.append((v.prop.identity, v.identity)) + var[v.prop.identity] = v key = tuple(sorted(key)) if key in variations_cache: var['variation'] = variations_cache[key] @@ -622,7 +643,7 @@ class Item(models.Model): return result -class ItemVariation(models.Model): +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' @@ -641,11 +662,11 @@ class ItemVariation(models.Model): Restrictions can be not only set to items but also directly to variations. """ - item = models.ForeignKey( + item = VersionedForeignKey( Item, related_name='variations' ) - values = models.ManyToManyField( + values = VersionedManyToManyField( PropertyValue, related_name='variations', ) @@ -663,13 +684,18 @@ class ItemVariation(models.Model): verbose_name = _("Item variation") verbose_name_plural = _("Item variations") - def save(self, *args, **kwargs): + 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() - return super().save(*args, **kwargs) -class VariationsField(models.ManyToManyField): +class VariationsField(VersionedManyToManyField): """ This is a ManyToManyField using the tixlcontrol.views.forms.VariationsField form field by default. @@ -690,31 +716,31 @@ class VariationsField(models.ManyToManyField): initial = defaults['initial'] if callable(initial): initial = initial() - defaults['initial'] = [i.pk for i in initial] + defaults['initial'] = [i.identity for i in initial] # Skip ManyToManyField in dependency chain return super(RelatedField, self).formfield(**defaults) -class BaseRestriction(models.Model): +class BaseRestriction(Versionable): """ A restriction is the abstract concept of a rule that limits the availability of Items or ItemVariations. This model is just an abstract base class to be extended by restriction plugins. """ - event = models.ForeignKey( + event = VersionedForeignKey( Event, on_delete=models.CASCADE, related_name="restrictions_%(app_label)s_%(class)s", verbose_name=_("Event"), ) - item = models.ForeignKey( + item = VersionedForeignKey( Item, blank=True, null=True, verbose_name=_("Item"), related_name="restrictions_%(app_label)s_%(class)s", ) variations = VariationsField( - ItemVariation, + 'tixlbase.ItemVariation', blank=True, verbose_name=_("Variations"), related_name="restrictions_%(app_label)s_%(class)s", @@ -725,13 +751,18 @@ class BaseRestriction(models.Model): verbose_name = _("Restriction") verbose_name_plural = _("Restrictions") - def save(self, *args, **kwargs): + 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() - return super().save(*args, **kwargs) -class Quota(models.Model): +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 @@ -750,7 +781,7 @@ class Quota(models.Model): implementation specific and are considered private. It is planned that they are being used as a fallback solution if redis is not available. """ - event = models.ForeignKey( + event = VersionedForeignKey( Event, on_delete=models.CASCADE, related_name="quotas", @@ -763,7 +794,7 @@ class Quota(models.Model): size = models.PositiveIntegerField( verbose_name=_("Total capacity") ) - items = models.ManyToManyField( + items = VersionedManyToManyField( Item, verbose_name=_("Item"), blank=True @@ -790,7 +821,7 @@ class Quota(models.Model): return self.name -class Order(models.Model): +class Order(Versionable): """ An order is created when a user clicks 'buy' on his cart. It holds several OrderPositions and is connected to an user. It has an @@ -817,7 +848,7 @@ class Order(models.Model): choices=STATUS_CHOICE, verbose_name=_("Status") ) - event = models.ForeignKey( + event = VersionedForeignKey( Event, verbose_name=_("Event") ) @@ -848,13 +879,13 @@ class Order(models.Model): verbose_name_plural = _("Orders") -class QuestionAnswer(models.Model): +class QuestionAnswer(Versionable): """ The answer to a Question, connected to an OrderPosition or CartPosition """ orderposition = models.ForeignKey('OrderPosition', null=True, blank=True) cartposition = models.ForeignKey('CartPosition', null=True, blank=True) - question = models.ForeignKey(Question) + question = VersionedForeignKey(Question) answer = models.TextField() @@ -866,15 +897,15 @@ class OrderPosition(models.Model): Important: An OrderPosition holds its total monetary value, as an order is a piece of 'history' and must not change due to a change in item prices. """ - order = models.ForeignKey( + order = VersionedForeignKey( Order, verbose_name=_("Order") ) - item = models.ForeignKey( + item = VersionedForeignKey( Item, verbose_name=_("Item") ) - variation = models.ForeignKey( + variation = VersionedForeignKey( ItemVariation, null=True, blank=True, verbose_name=_("Variation") @@ -883,7 +914,7 @@ class OrderPosition(models.Model): decimal_places=2, max_digits=10, verbose_name=_("Price") ) - answers = models.ManyToManyField( + answers = VersionedManyToManyField( Question, through=QuestionAnswer, verbose_name=_("Answers") @@ -903,7 +934,7 @@ class CartPosition(models.Model): as we do not want to throw out users while they're clicking through the checkout process. """ - event = models.ForeignKey( + event = VersionedForeignKey( Event, verbose_name=_("Event") ) @@ -915,11 +946,11 @@ class CartPosition(models.Model): max_length=255, null=True, blank=True, verbose_name=_("Session key") ) - item = models.ForeignKey( + item = VersionedForeignKey( Item, verbose_name=_("Item") ) - variation = models.ForeignKey( + variation = VersionedForeignKey( ItemVariation, null=True, blank=True, verbose_name=_("Variation") diff --git a/src/tixlbase/types.py b/src/tixlbase/types.py index e2e4df23ab..6b229831c5 100644 --- a/src/tixlbase/types.py +++ b/src/tixlbase/types.py @@ -5,17 +5,16 @@ class VariationDict(dict): returned by ``Item.get_all_variations()`` to avoid duplicate code in the code calling this method. """ + IGNORE_KEYS = ('variation', 'key') - def relevant_items(self) -> "list[(int, PropertyValue)]": + def relevant_items(self) -> "list[(str, PropertyValue)]": """ Iterate over all items with numeric keys. This is in use because the variation dictionaries use property ids as key and have some special keys like 'variation'. """ - for i in self.items(): - if type(i[0]) is int: - yield i + return (i for i in self.items() if i[0] not in self.IGNORE_KEYS) def relevant_values(self) -> "list[PropertyValue]": """ @@ -24,9 +23,7 @@ class VariationDict(dict): This is in use because the variation dictionaries use property ids as key and have some special keys like 'variation'. """ - for i in self.items(): - if type(i[0]) is int: - yield i[1] + return (i[1] for i in self.items() if i[0] not in self.IGNORE_KEYS) def identify(self) -> str: """ diff --git a/src/tixlcontrol/middleware.py b/src/tixlcontrol/middleware.py index 4f0f39f67b..b38fa2fd59 100644 --- a/src/tixlcontrol/middleware.py +++ b/src/tixlcontrol/middleware.py @@ -46,11 +46,11 @@ class PermissionMiddleware: return redirect_to_login( path, resolved_login_url, REDIRECT_FIELD_NAME) - request.user.events_cache = request.user.events.order_by( + request.user.events_cache = request.user.events.current.order_by( "organizer", "date_from").prefetch_related("organizer") if 'event.' in url_name and 'event' in url.kwargs: try: - request.event = Event.objects.get( + request.event = Event.objects.current.get( slug=url.kwargs['event'], permitted__id__exact=request.user.id, organizer__slug=url.kwargs['organizer'], diff --git a/src/tixlcontrol/templates/tixlcontrol/item/base.html b/src/tixlcontrol/templates/tixlcontrol/item/base.html index df9e136eb3..7a7aa2c3d9 100644 --- a/src/tixlcontrol/templates/tixlcontrol/item/base.html +++ b/src/tixlcontrol/templates/tixlcontrol/item/base.html @@ -4,9 +4,9 @@ {% block content %}