Moved property to the inside of items

This commit is contained in:
Raphael Michel
2015-10-08 12:45:19 +02:00
parent b99f541adf
commit 974c5cee79
25 changed files with 649 additions and 603 deletions
+12 -13
View File
@@ -39,18 +39,6 @@ cat_tickets = ItemCategory.objects.create(
cat_merch = ItemCategory.objects.create( cat_merch = ItemCategory.objects.create(
event=event, name='Merchandise' event=event, name='Merchandise'
) )
size_prop = Property.objects.create(
event=event, name='Size'
)
size_s = PropertyValue.objects.create(
prop=size_prop, value='S'
)
size_l = PropertyValue.objects.create(
prop=size_prop, value='L'
)
size_m = PropertyValue.objects.create(
prop=size_prop, value='M'
)
question = Question.objects.create( question = Question.objects.create(
event=event, question='Age', event=event, question='Age',
type=Question.TYPE_NUMBER, required=False type=Question.TYPE_NUMBER, required=False
@@ -64,7 +52,18 @@ item_shirt = Item.objects.create(
event=event, category=cat_merch, name='T-Shirt', event=event, category=cat_merch, name='T-Shirt',
default_price=15, tax_rate=19 default_price=15, tax_rate=19
) )
item_shirt.properties.add(size_prop) size_prop = Property.objects.create(
event=event, name='Size', item=item_shirt
)
size_s = PropertyValue.objects.create(
prop=size_prop, value='S'
)
size_l = PropertyValue.objects.create(
prop=size_prop, value='L'
)
size_m = PropertyValue.objects.create(
prop=size_prop, value='M'
)
var_s = ItemVariation.objects.create(item=item_shirt) var_s = ItemVariation.objects.create(item=item_shirt)
var_s.values.add(size_s) var_s.values.add(size_s)
var_m = ItemVariation.objects.create(item=item_shirt) var_m = ItemVariation.objects.create(item=item_shirt)
+114 -118
View File
@@ -23,41 +23,41 @@ class Migration(migrations.Migration):
migrations.CreateModel( migrations.CreateModel(
name='User', name='User',
fields=[ fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('id', models.AutoField(serialize=False, verbose_name='ID', primary_key=True, auto_created=True)),
('password', models.CharField(verbose_name='password', max_length=128)), ('password', models.CharField(verbose_name='password', max_length=128)),
('last_login', models.DateTimeField(verbose_name='last login', blank=True, null=True)), ('last_login', models.DateTimeField(verbose_name='last login', null=True, blank=True)),
('is_superuser', models.BooleanField(verbose_name='superuser status', help_text='Designates that this user has all permissions without explicitly assigning them.', default=False)), ('is_superuser', models.BooleanField(default=False, verbose_name='superuser status', help_text='Designates that this user has all permissions without explicitly assigning them.')),
('email', models.EmailField(verbose_name='E-mail', null=True, unique=True, db_index=True, max_length=254, blank=True)), ('email', models.EmailField(unique=True, verbose_name='E-mail', blank=True, db_index=True, max_length=254, null=True)),
('givenname', models.CharField(verbose_name='Given name', blank=True, null=True, max_length=255)), ('givenname', models.CharField(verbose_name='Given name', null=True, max_length=255, blank=True)),
('familyname', models.CharField(verbose_name='Family name', blank=True, null=True, max_length=255)), ('familyname', models.CharField(verbose_name='Family name', null=True, max_length=255, blank=True)),
('is_active', models.BooleanField(verbose_name='Is active', default=True)), ('is_active', models.BooleanField(default=True, verbose_name='Is active')),
('is_staff', models.BooleanField(verbose_name='Is site admin', default=False)), ('is_staff', models.BooleanField(default=False, verbose_name='Is site admin')),
('date_joined', models.DateTimeField(verbose_name='Date joined', auto_now_add=True)), ('date_joined', models.DateTimeField(verbose_name='Date joined', auto_now_add=True)),
('locale', models.CharField(verbose_name='Language', choices=[('en', 'English'), ('de', 'German'), ('de-informal', 'German (informal)')], default='en', max_length=50)), ('locale', models.CharField(default='en', verbose_name='Language', choices=[('en', 'English'), ('de', 'German'), ('de-informal', 'German (informal)')], max_length=50)),
('timezone', models.CharField(verbose_name='Timezone', default='UTC', max_length=100)), ('timezone', models.CharField(default='UTC', verbose_name='Timezone', max_length=100)),
('groups', models.ManyToManyField(verbose_name='groups', related_query_name='user', help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', to='auth.Group', blank=True)), ('groups', models.ManyToManyField(related_query_name='user', verbose_name='groups', help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', blank=True, related_name='user_set', to='auth.Group')),
('user_permissions', models.ManyToManyField(verbose_name='user permissions', related_query_name='user', help_text='Specific permissions for this user.', related_name='user_set', to='auth.Permission', blank=True)), ('user_permissions', models.ManyToManyField(related_query_name='user', verbose_name='user permissions', help_text='Specific permissions for this user.', blank=True, related_name='user_set', to='auth.Permission')),
], ],
options={ options={
'verbose_name_plural': 'Users',
'verbose_name': 'User', 'verbose_name': 'User',
'verbose_name_plural': 'Users',
}, },
), ),
migrations.CreateModel( migrations.CreateModel(
name='CachedFile', name='CachedFile',
fields=[ fields=[
('id', models.UUIDField(primary_key=True, default=uuid.uuid4, serialize=False)), ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('expires', models.DateTimeField(blank=True, null=True)), ('expires', models.DateTimeField(null=True, blank=True)),
('date', models.DateTimeField(blank=True, null=True)), ('date', models.DateTimeField(null=True, blank=True)),
('filename', models.CharField(max_length=255)), ('filename', models.CharField(max_length=255)),
('type', models.CharField(max_length=255)), ('type', models.CharField(max_length=255)),
('file', models.FileField(upload_to=pretix.base.models.cachedfile_name, blank=True, null=True)), ('file', models.FileField(upload_to=pretix.base.models.cachedfile_name, null=True, blank=True)),
], ],
), ),
migrations.CreateModel( migrations.CreateModel(
name='CachedTicket', name='CachedTicket',
fields=[ fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('id', models.AutoField(serialize=False, verbose_name='ID', primary_key=True, auto_created=True)),
('provider', models.CharField(max_length=255)), ('provider', models.CharField(max_length=255)),
('cachedfile', models.ForeignKey(to='pretixbase.CachedFile')), ('cachedfile', models.ForeignKey(to='pretixbase.CachedFile')),
], ],
@@ -65,50 +65,50 @@ class Migration(migrations.Migration):
migrations.CreateModel( migrations.CreateModel(
name='CartPosition', name='CartPosition',
fields=[ fields=[
('id', models.CharField(primary_key=True, serialize=False, max_length=36)), ('id', models.CharField(serialize=False, max_length=36, primary_key=True)),
('identity', models.CharField(max_length=36)), ('identity', models.CharField(max_length=36)),
('version_start_date', models.DateTimeField()), ('version_start_date', models.DateTimeField()),
('version_end_date', models.DateTimeField(blank=True, default=None, null=True)), ('version_end_date', models.DateTimeField(default=None, null=True, blank=True)),
('version_birth_date', models.DateTimeField()), ('version_birth_date', models.DateTimeField()),
('session', models.CharField(verbose_name='Session', blank=True, null=True, max_length=255)), ('session', models.CharField(verbose_name='Session', null=True, max_length=255, blank=True)),
('price', models.DecimalField(verbose_name='Price', max_digits=10, decimal_places=2)), ('price', models.DecimalField(decimal_places=2, verbose_name='Price', max_digits=10)),
('datetime', models.DateTimeField(verbose_name='Date', auto_now_add=True)), ('datetime', models.DateTimeField(verbose_name='Date', auto_now_add=True)),
('expires', models.DateTimeField(verbose_name='Expiration date')), ('expires', models.DateTimeField(verbose_name='Expiration date')),
('attendee_name', models.CharField(verbose_name='Attendee name', blank=True, help_text='Empty, if this product is not an admission ticket', null=True, max_length=255)), ('attendee_name', models.CharField(verbose_name='Attendee name', null=True, help_text='Empty, if this product is not an admission ticket', max_length=255, blank=True)),
], ],
options={ options={
'verbose_name_plural': 'Cart positions',
'verbose_name': 'Cart position', 'verbose_name': 'Cart position',
'verbose_name_plural': 'Cart positions',
}, },
bases=(pretix.base.models.ObjectWithAnswers, models.Model), bases=(pretix.base.models.ObjectWithAnswers, models.Model),
), ),
migrations.CreateModel( migrations.CreateModel(
name='Event', name='Event',
fields=[ fields=[
('id', models.CharField(primary_key=True, serialize=False, max_length=36)), ('id', models.CharField(serialize=False, max_length=36, primary_key=True)),
('identity', models.CharField(max_length=36)), ('identity', models.CharField(max_length=36)),
('version_start_date', models.DateTimeField()), ('version_start_date', models.DateTimeField()),
('version_end_date', models.DateTimeField(blank=True, default=None, null=True)), ('version_end_date', models.DateTimeField(default=None, null=True, blank=True)),
('version_birth_date', models.DateTimeField()), ('version_birth_date', models.DateTimeField()),
('name', pretix.base.i18n.I18nCharField(verbose_name='Name', max_length=200)), ('name', pretix.base.i18n.I18nCharField(verbose_name='Name', max_length=200)),
('slug', models.SlugField(verbose_name='Slug', 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.-]+$')])), ('slug', models.SlugField(verbose_name='Slug', 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(regex='^[a-zA-Z0-9.-]+$', message='The slug may only contain letters, numbers, dots and dashes.')])),
('currency', models.CharField(verbose_name='Default currency', default='EUR', max_length=10)), ('currency', models.CharField(default='EUR', verbose_name='Default currency', max_length=10)),
('date_from', models.DateTimeField(verbose_name='Event start time')), ('date_from', models.DateTimeField(verbose_name='Event start time')),
('date_to', models.DateTimeField(verbose_name='Event end time', blank=True, null=True)), ('date_to', models.DateTimeField(verbose_name='Event end time', null=True, blank=True)),
('presale_end', models.DateTimeField(verbose_name='End of presale', blank=True, help_text='No products will be sold after this date.', null=True)), ('presale_end', models.DateTimeField(verbose_name='End of presale', help_text='No products will be sold after this date.', null=True, blank=True)),
('presale_start', models.DateTimeField(verbose_name='Start of presale', blank=True, help_text='No products will be sold before this date.', null=True)), ('presale_start', models.DateTimeField(verbose_name='Start of presale', help_text='No products will be sold before this date.', null=True, blank=True)),
('plugins', models.TextField(verbose_name='Plugins', blank=True, null=True)), ('plugins', models.TextField(verbose_name='Plugins', null=True, blank=True)),
], ],
options={ options={
'verbose_name_plural': 'Events',
'verbose_name': 'Event', 'verbose_name': 'Event',
'verbose_name_plural': 'Events',
'ordering': ('date_from', 'name'), 'ordering': ('date_from', 'name'),
}, },
), ),
migrations.CreateModel( migrations.CreateModel(
name='EventLock', name='EventLock',
fields=[ fields=[
('event', models.CharField(primary_key=True, serialize=False, max_length=36)), ('event', models.CharField(serialize=False, max_length=36, primary_key=True)),
('date', models.DateTimeField(auto_now=True)), ('date', models.DateTimeField(auto_now=True)),
('token', models.UUIDField(default=uuid.uuid4)), ('token', models.UUIDField(default=uuid.uuid4)),
], ],
@@ -116,31 +116,31 @@ class Migration(migrations.Migration):
migrations.CreateModel( migrations.CreateModel(
name='EventPermission', name='EventPermission',
fields=[ fields=[
('id', models.CharField(primary_key=True, serialize=False, max_length=36)), ('id', models.CharField(serialize=False, max_length=36, primary_key=True)),
('identity', models.CharField(max_length=36)), ('identity', models.CharField(max_length=36)),
('version_start_date', models.DateTimeField()), ('version_start_date', models.DateTimeField()),
('version_end_date', models.DateTimeField(blank=True, default=None, null=True)), ('version_end_date', models.DateTimeField(default=None, null=True, blank=True)),
('version_birth_date', models.DateTimeField()), ('version_birth_date', models.DateTimeField()),
('can_change_settings', models.BooleanField(verbose_name='Can change event settings', default=True)), ('can_change_settings', models.BooleanField(default=True, verbose_name='Can change event settings')),
('can_change_items', models.BooleanField(verbose_name='Can change product settings', default=True)), ('can_change_items', models.BooleanField(default=True, verbose_name='Can change product settings')),
('can_view_orders', models.BooleanField(verbose_name='Can view orders', default=True)), ('can_view_orders', models.BooleanField(default=True, verbose_name='Can view orders')),
('can_change_permissions', models.BooleanField(verbose_name='Can change permissions', default=True)), ('can_change_permissions', models.BooleanField(default=True, verbose_name='Can change permissions')),
('can_change_orders', models.BooleanField(verbose_name='Can change orders', default=True)), ('can_change_orders', models.BooleanField(default=True, verbose_name='Can change orders')),
('event', versions.models.VersionedForeignKey(to='pretixbase.Event')), ('event', versions.models.VersionedForeignKey(to='pretixbase.Event')),
('user', models.ForeignKey(related_name='event_perms', to=settings.AUTH_USER_MODEL)), ('user', models.ForeignKey(related_name='event_perms', to=settings.AUTH_USER_MODEL)),
], ],
options={ options={
'verbose_name_plural': 'Event permissions',
'verbose_name': 'Event permission', 'verbose_name': 'Event permission',
'verbose_name_plural': 'Event permissions',
}, },
), ),
migrations.CreateModel( migrations.CreateModel(
name='EventSetting', name='EventSetting',
fields=[ fields=[
('id', models.CharField(primary_key=True, serialize=False, max_length=36)), ('id', models.CharField(serialize=False, max_length=36, primary_key=True)),
('identity', models.CharField(max_length=36)), ('identity', models.CharField(max_length=36)),
('version_start_date', models.DateTimeField()), ('version_start_date', models.DateTimeField()),
('version_end_date', models.DateTimeField(blank=True, default=None, null=True)), ('version_end_date', models.DateTimeField(default=None, null=True, blank=True)),
('version_birth_date', models.DateTimeField()), ('version_birth_date', models.DateTimeField()),
('key', models.CharField(max_length=255)), ('key', models.CharField(max_length=255)),
('value', models.TextField()), ('value', models.TextField()),
@@ -153,151 +153,151 @@ class Migration(migrations.Migration):
migrations.CreateModel( migrations.CreateModel(
name='Item', name='Item',
fields=[ fields=[
('id', models.CharField(primary_key=True, serialize=False, max_length=36)), ('id', models.CharField(serialize=False, max_length=36, primary_key=True)),
('identity', models.CharField(max_length=36)), ('identity', models.CharField(max_length=36)),
('version_start_date', models.DateTimeField()), ('version_start_date', models.DateTimeField()),
('version_end_date', models.DateTimeField(blank=True, default=None, null=True)), ('version_end_date', models.DateTimeField(default=None, null=True, blank=True)),
('version_birth_date', models.DateTimeField()), ('version_birth_date', models.DateTimeField()),
('name', pretix.base.i18n.I18nCharField(verbose_name='Item name', max_length=255)), ('name', pretix.base.i18n.I18nCharField(verbose_name='Item name', max_length=255)),
('active', models.BooleanField(verbose_name='Active', default=True)), ('active', models.BooleanField(default=True, verbose_name='Active')),
('description', pretix.base.i18n.I18nTextField(verbose_name='Description', blank=True, help_text='This is shown below the product name in lists.', null=True)), ('description', pretix.base.i18n.I18nTextField(verbose_name='Description', help_text='This is shown below the product name in lists.', null=True, blank=True)),
('default_price', models.DecimalField(verbose_name='Default price', max_digits=7, null=True, decimal_places=2)), ('default_price', models.DecimalField(decimal_places=2, verbose_name='Default price', max_digits=7, null=True)),
('tax_rate', models.DecimalField(verbose_name='Taxes included in percent', blank=True, max_digits=7, null=True, decimal_places=2)), ('tax_rate', models.DecimalField(decimal_places=2, verbose_name='Taxes included in percent', max_digits=7, null=True, blank=True)),
('admission', models.BooleanField(verbose_name='Is an admission ticket', help_text='Whether or not buying this product allows a person to enter your event', default=False)), ('admission', models.BooleanField(default=False, verbose_name='Is an admission ticket', help_text='Whether or not buying this product allows a person to enter your event')),
('position', models.IntegerField(default=0)), ('position', models.IntegerField(default=0)),
('picture', models.ImageField(upload_to=pretix.base.models.itempicture_upload_to, verbose_name='Product picture', blank=True, null=True)), ('picture', models.ImageField(upload_to=pretix.base.models.itempicture_upload_to, verbose_name='Product picture', null=True, blank=True)),
], ],
options={ options={
'verbose_name_plural': 'Products',
'verbose_name': 'Product', 'verbose_name': 'Product',
'verbose_name_plural': 'Products',
'ordering': ('category__position', 'category', 'position'), 'ordering': ('category__position', 'category', 'position'),
}, },
), ),
migrations.CreateModel( migrations.CreateModel(
name='ItemCategory', name='ItemCategory',
fields=[ fields=[
('id', models.CharField(primary_key=True, serialize=False, max_length=36)), ('id', models.CharField(serialize=False, max_length=36, primary_key=True)),
('identity', models.CharField(max_length=36)), ('identity', models.CharField(max_length=36)),
('version_start_date', models.DateTimeField()), ('version_start_date', models.DateTimeField()),
('version_end_date', models.DateTimeField(blank=True, default=None, null=True)), ('version_end_date', models.DateTimeField(default=None, null=True, blank=True)),
('version_birth_date', models.DateTimeField()), ('version_birth_date', models.DateTimeField()),
('name', pretix.base.i18n.I18nCharField(verbose_name='Category name', max_length=255)), ('name', pretix.base.i18n.I18nCharField(verbose_name='Category name', max_length=255)),
('position', models.IntegerField(default=0)), ('position', models.IntegerField(default=0)),
('event', versions.models.VersionedForeignKey(related_name='categories', to='pretixbase.Event')), ('event', versions.models.VersionedForeignKey(related_name='categories', to='pretixbase.Event')),
], ],
options={ options={
'verbose_name_plural': 'Product categories',
'verbose_name': 'Product category', 'verbose_name': 'Product category',
'verbose_name_plural': 'Product categories',
'ordering': ('position', 'version_birth_date'), 'ordering': ('position', 'version_birth_date'),
}, },
), ),
migrations.CreateModel( migrations.CreateModel(
name='ItemVariation', name='ItemVariation',
fields=[ fields=[
('id', models.CharField(primary_key=True, serialize=False, max_length=36)), ('id', models.CharField(serialize=False, max_length=36, primary_key=True)),
('identity', models.CharField(max_length=36)), ('identity', models.CharField(max_length=36)),
('version_start_date', models.DateTimeField()), ('version_start_date', models.DateTimeField()),
('version_end_date', models.DateTimeField(blank=True, default=None, null=True)), ('version_end_date', models.DateTimeField(default=None, null=True, blank=True)),
('version_birth_date', models.DateTimeField()), ('version_birth_date', models.DateTimeField()),
('active', models.BooleanField(verbose_name='Active', default=True)), ('active', models.BooleanField(default=True, verbose_name='Active')),
('default_price', models.DecimalField(verbose_name='Default price', blank=True, max_digits=7, null=True, decimal_places=2)), ('default_price', models.DecimalField(decimal_places=2, verbose_name='Default price', max_digits=7, null=True, blank=True)),
('item', versions.models.VersionedForeignKey(related_name='variations', to='pretixbase.Item')), ('item', versions.models.VersionedForeignKey(related_name='variations', to='pretixbase.Item')),
], ],
options={ options={
'verbose_name_plural': 'Product variations',
'verbose_name': 'Product variation', 'verbose_name': 'Product variation',
'verbose_name_plural': 'Product variations',
}, },
), ),
migrations.CreateModel( migrations.CreateModel(
name='Order', name='Order',
fields=[ fields=[
('id', models.CharField(primary_key=True, serialize=False, max_length=36)), ('id', models.CharField(serialize=False, max_length=36, primary_key=True)),
('identity', models.CharField(max_length=36)), ('identity', models.CharField(max_length=36)),
('version_start_date', models.DateTimeField()), ('version_start_date', models.DateTimeField()),
('version_end_date', models.DateTimeField(blank=True, default=None, null=True)), ('version_end_date', models.DateTimeField(default=None, null=True, blank=True)),
('version_birth_date', models.DateTimeField()), ('version_birth_date', models.DateTimeField()),
('code', models.CharField(verbose_name='Order code', max_length=16)), ('code', models.CharField(verbose_name='Order code', max_length=16)),
('status', models.CharField(verbose_name='Status', choices=[('n', 'pending'), ('p', 'paid'), ('e', 'expired'), ('c', 'cancelled'), ('r', 'refunded')], max_length=3)), ('status', models.CharField(verbose_name='Status', choices=[('n', 'pending'), ('p', 'paid'), ('e', 'expired'), ('c', 'cancelled'), ('r', 'refunded')], max_length=3)),
('email', models.EmailField(verbose_name='E-mail', blank=True, null=True, max_length=254)), ('email', models.EmailField(verbose_name='E-mail', null=True, max_length=254, blank=True)),
('locale', models.CharField(verbose_name='Locale', blank=True, null=True, max_length=32)), ('locale', models.CharField(verbose_name='Locale', null=True, max_length=32, blank=True)),
('secret', models.CharField(max_length=32, default=pretix.base.models.generate_secret)), ('secret', models.CharField(default=pretix.base.models.generate_secret, max_length=32)),
('datetime', models.DateTimeField(verbose_name='Date')), ('datetime', models.DateTimeField(verbose_name='Date')),
('expires', models.DateTimeField(verbose_name='Expiration date')), ('expires', models.DateTimeField(verbose_name='Expiration date')),
('payment_date', models.DateTimeField(verbose_name='Payment date', blank=True, null=True)), ('payment_date', models.DateTimeField(verbose_name='Payment date', null=True, blank=True)),
('payment_provider', models.CharField(verbose_name='Payment provider', blank=True, null=True, max_length=255)), ('payment_provider', models.CharField(verbose_name='Payment provider', null=True, max_length=255, blank=True)),
('payment_fee', models.DecimalField(verbose_name='Payment method fee', max_digits=10, default=0, decimal_places=2)), ('payment_fee', models.DecimalField(decimal_places=2, default=0, verbose_name='Payment method fee', max_digits=10)),
('payment_info', models.TextField(verbose_name='Payment information', blank=True, null=True)), ('payment_info', models.TextField(verbose_name='Payment information', null=True, blank=True)),
('payment_manual', models.BooleanField(verbose_name='Payment state was manually modified', default=False)), ('payment_manual', models.BooleanField(default=False, verbose_name='Payment state was manually modified')),
('total', models.DecimalField(verbose_name='Total amount', max_digits=10, decimal_places=2)), ('total', models.DecimalField(decimal_places=2, verbose_name='Total amount', max_digits=10)),
('event', versions.models.VersionedForeignKey(verbose_name='Event', related_name='orders', to='pretixbase.Event')), ('event', versions.models.VersionedForeignKey(verbose_name='Event', related_name='orders', to='pretixbase.Event')),
], ],
options={ options={
'verbose_name_plural': 'Orders',
'verbose_name': 'Order', 'verbose_name': 'Order',
'verbose_name_plural': 'Orders',
'ordering': ('-datetime',), 'ordering': ('-datetime',),
}, },
), ),
migrations.CreateModel( migrations.CreateModel(
name='OrderPosition', name='OrderPosition',
fields=[ fields=[
('id', models.CharField(primary_key=True, serialize=False, max_length=36)), ('id', models.CharField(serialize=False, max_length=36, primary_key=True)),
('identity', models.CharField(max_length=36)), ('identity', models.CharField(max_length=36)),
('version_start_date', models.DateTimeField()), ('version_start_date', models.DateTimeField()),
('version_end_date', models.DateTimeField(blank=True, default=None, null=True)), ('version_end_date', models.DateTimeField(default=None, null=True, blank=True)),
('version_birth_date', models.DateTimeField()), ('version_birth_date', models.DateTimeField()),
('price', models.DecimalField(verbose_name='Price', max_digits=10, decimal_places=2)), ('price', models.DecimalField(decimal_places=2, verbose_name='Price', max_digits=10)),
('attendee_name', models.CharField(verbose_name='Attendee name', blank=True, help_text='Empty, if this product is not an admission ticket', null=True, max_length=255)), ('attendee_name', models.CharField(verbose_name='Attendee name', null=True, help_text='Empty, if this product is not an admission ticket', max_length=255, blank=True)),
('item', versions.models.VersionedForeignKey(verbose_name='Item', related_name='positions', to='pretixbase.Item')), ('item', versions.models.VersionedForeignKey(verbose_name='Item', related_name='positions', to='pretixbase.Item')),
('order', versions.models.VersionedForeignKey(verbose_name='Order', related_name='positions', to='pretixbase.Order')), ('order', versions.models.VersionedForeignKey(verbose_name='Order', related_name='positions', to='pretixbase.Order')),
('variation', versions.models.VersionedForeignKey(verbose_name='Variation', to='pretixbase.ItemVariation', null=True, blank=True)), ('variation', versions.models.VersionedForeignKey(verbose_name='Variation', blank=True, null=True, to='pretixbase.ItemVariation')),
], ],
options={ options={
'verbose_name_plural': 'Order positions',
'verbose_name': 'Order position', 'verbose_name': 'Order position',
'verbose_name_plural': 'Order positions',
}, },
bases=(pretix.base.models.ObjectWithAnswers, models.Model), bases=(pretix.base.models.ObjectWithAnswers, models.Model),
), ),
migrations.CreateModel( migrations.CreateModel(
name='Organizer', name='Organizer',
fields=[ fields=[
('id', models.CharField(primary_key=True, serialize=False, max_length=36)), ('id', models.CharField(serialize=False, max_length=36, primary_key=True)),
('identity', models.CharField(max_length=36)), ('identity', models.CharField(max_length=36)),
('version_start_date', models.DateTimeField()), ('version_start_date', models.DateTimeField()),
('version_end_date', models.DateTimeField(blank=True, default=None, null=True)), ('version_end_date', models.DateTimeField(default=None, null=True, blank=True)),
('version_birth_date', models.DateTimeField()), ('version_birth_date', models.DateTimeField()),
('name', models.CharField(verbose_name='Name', max_length=200)), ('name', models.CharField(verbose_name='Name', max_length=200)),
('slug', models.SlugField(verbose_name='Slug', 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.-]+$')])), ('slug', models.SlugField(verbose_name='Slug', 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(regex='^[a-zA-Z0-9.-]+$', message='The slug may only contain letters, numbers, dots and dashes.')])),
], ],
options={ options={
'verbose_name_plural': 'Organizers',
'verbose_name': 'Organizer', 'verbose_name': 'Organizer',
'verbose_name_plural': 'Organizers',
'ordering': ('name',), 'ordering': ('name',),
}, },
), ),
migrations.CreateModel( migrations.CreateModel(
name='OrganizerPermission', name='OrganizerPermission',
fields=[ fields=[
('id', models.CharField(primary_key=True, serialize=False, max_length=36)), ('id', models.CharField(serialize=False, max_length=36, primary_key=True)),
('identity', models.CharField(max_length=36)), ('identity', models.CharField(max_length=36)),
('version_start_date', models.DateTimeField()), ('version_start_date', models.DateTimeField()),
('version_end_date', models.DateTimeField(blank=True, default=None, null=True)), ('version_end_date', models.DateTimeField(default=None, null=True, blank=True)),
('version_birth_date', models.DateTimeField()), ('version_birth_date', models.DateTimeField()),
('can_create_events', models.BooleanField(verbose_name='Can create events', default=True)), ('can_create_events', models.BooleanField(default=True, verbose_name='Can create events')),
('organizer', versions.models.VersionedForeignKey(to='pretixbase.Organizer')), ('organizer', versions.models.VersionedForeignKey(to='pretixbase.Organizer')),
('user', models.ForeignKey(related_name='organizer_perms', to=settings.AUTH_USER_MODEL)), ('user', models.ForeignKey(related_name='organizer_perms', to=settings.AUTH_USER_MODEL)),
], ],
options={ options={
'verbose_name_plural': 'Organizer permissions',
'verbose_name': 'Organizer permission', 'verbose_name': 'Organizer permission',
'verbose_name_plural': 'Organizer permissions',
}, },
), ),
migrations.CreateModel( migrations.CreateModel(
name='OrganizerSetting', name='OrganizerSetting',
fields=[ fields=[
('id', models.CharField(primary_key=True, serialize=False, max_length=36)), ('id', models.CharField(serialize=False, max_length=36, primary_key=True)),
('identity', models.CharField(max_length=36)), ('identity', models.CharField(max_length=36)),
('version_start_date', models.DateTimeField()), ('version_start_date', models.DateTimeField()),
('version_end_date', models.DateTimeField(blank=True, default=None, null=True)), ('version_end_date', models.DateTimeField(default=None, null=True, blank=True)),
('version_birth_date', models.DateTimeField()), ('version_birth_date', models.DateTimeField()),
('key', models.CharField(max_length=255)), ('key', models.CharField(max_length=255)),
('value', models.TextField()), ('value', models.TextField()),
@@ -310,67 +310,68 @@ class Migration(migrations.Migration):
migrations.CreateModel( migrations.CreateModel(
name='Property', name='Property',
fields=[ fields=[
('id', models.CharField(primary_key=True, serialize=False, max_length=36)), ('id', models.CharField(serialize=False, max_length=36, primary_key=True)),
('identity', models.CharField(max_length=36)), ('identity', models.CharField(max_length=36)),
('version_start_date', models.DateTimeField()), ('version_start_date', models.DateTimeField()),
('version_end_date', models.DateTimeField(blank=True, default=None, null=True)), ('version_end_date', models.DateTimeField(default=None, null=True, blank=True)),
('version_birth_date', models.DateTimeField()), ('version_birth_date', models.DateTimeField()),
('name', pretix.base.i18n.I18nCharField(verbose_name='Property name', max_length=250)), ('name', pretix.base.i18n.I18nCharField(verbose_name='Property name', max_length=250)),
('event', versions.models.VersionedForeignKey(related_name='properties', to='pretixbase.Event')), ('event', versions.models.VersionedForeignKey(related_name='properties', to='pretixbase.Event')),
('item', versions.models.VersionedForeignKey(blank=True, related_name='properties', null=True, to='pretixbase.Item')),
], ],
options={ options={
'verbose_name_plural': 'Product properties',
'verbose_name': 'Product property', 'verbose_name': 'Product property',
'verbose_name_plural': 'Product properties',
}, },
), ),
migrations.CreateModel( migrations.CreateModel(
name='PropertyValue', name='PropertyValue',
fields=[ fields=[
('id', models.CharField(primary_key=True, serialize=False, max_length=36)), ('id', models.CharField(serialize=False, max_length=36, primary_key=True)),
('identity', models.CharField(max_length=36)), ('identity', models.CharField(max_length=36)),
('version_start_date', models.DateTimeField()), ('version_start_date', models.DateTimeField()),
('version_end_date', models.DateTimeField(blank=True, default=None, null=True)), ('version_end_date', models.DateTimeField(default=None, null=True, blank=True)),
('version_birth_date', models.DateTimeField()), ('version_birth_date', models.DateTimeField()),
('value', pretix.base.i18n.I18nCharField(verbose_name='Value', max_length=250)), ('value', pretix.base.i18n.I18nCharField(verbose_name='Value', max_length=250)),
('position', models.IntegerField(default=0)), ('position', models.IntegerField(default=0)),
('prop', versions.models.VersionedForeignKey(related_name='values', to='pretixbase.Property')), ('prop', versions.models.VersionedForeignKey(related_name='values', to='pretixbase.Property')),
], ],
options={ options={
'verbose_name_plural': 'Property values',
'verbose_name': 'Property value', 'verbose_name': 'Property value',
'verbose_name_plural': 'Property values',
'ordering': ('position', 'version_birth_date'), 'ordering': ('position', 'version_birth_date'),
}, },
), ),
migrations.CreateModel( migrations.CreateModel(
name='Question', name='Question',
fields=[ fields=[
('id', models.CharField(primary_key=True, serialize=False, max_length=36)), ('id', models.CharField(serialize=False, max_length=36, primary_key=True)),
('identity', models.CharField(max_length=36)), ('identity', models.CharField(max_length=36)),
('version_start_date', models.DateTimeField()), ('version_start_date', models.DateTimeField()),
('version_end_date', models.DateTimeField(blank=True, default=None, null=True)), ('version_end_date', models.DateTimeField(default=None, null=True, blank=True)),
('version_birth_date', models.DateTimeField()), ('version_birth_date', models.DateTimeField()),
('question', pretix.base.i18n.I18nTextField(verbose_name='Question')), ('question', pretix.base.i18n.I18nTextField(verbose_name='Question')),
('type', models.CharField(verbose_name='Question type', choices=[('N', 'Number'), ('S', 'Text (one line)'), ('T', 'Multiline text'), ('B', 'Yes/No')], max_length=5)), ('type', models.CharField(verbose_name='Question type', choices=[('N', 'Number'), ('S', 'Text (one line)'), ('T', 'Multiline text'), ('B', 'Yes/No')], max_length=5)),
('required', models.BooleanField(verbose_name='Required question', default=False)), ('required', models.BooleanField(default=False, verbose_name='Required question')),
('event', versions.models.VersionedForeignKey(related_name='questions', to='pretixbase.Event')), ('event', versions.models.VersionedForeignKey(related_name='questions', to='pretixbase.Event')),
('items', versions.models.VersionedManyToManyField(verbose_name='Products', blank=True, help_text='This question will be asked to buyers of the selected products', related_name='questions', to='pretixbase.Item')), ('items', versions.models.VersionedManyToManyField(to='pretixbase.Item', verbose_name='Products', help_text='This question will be asked to buyers of the selected products', related_name='questions', blank=True)),
], ],
options={ options={
'verbose_name_plural': 'Questions',
'verbose_name': 'Question', 'verbose_name': 'Question',
'verbose_name_plural': 'Questions',
}, },
), ),
migrations.CreateModel( migrations.CreateModel(
name='QuestionAnswer', name='QuestionAnswer',
fields=[ fields=[
('id', models.CharField(primary_key=True, serialize=False, max_length=36)), ('id', models.CharField(serialize=False, max_length=36, primary_key=True)),
('identity', models.CharField(max_length=36)), ('identity', models.CharField(max_length=36)),
('version_start_date', models.DateTimeField()), ('version_start_date', models.DateTimeField()),
('version_end_date', models.DateTimeField(blank=True, default=None, null=True)), ('version_end_date', models.DateTimeField(default=None, null=True, blank=True)),
('version_birth_date', models.DateTimeField()), ('version_birth_date', models.DateTimeField()),
('answer', models.TextField()), ('answer', models.TextField()),
('cartposition', models.ForeignKey(related_name='answers', to='pretixbase.CartPosition', null=True, blank=True)), ('cartposition', models.ForeignKey(blank=True, related_name='answers', null=True, to='pretixbase.CartPosition')),
('orderposition', models.ForeignKey(related_name='answers', to='pretixbase.OrderPosition', null=True, blank=True)), ('orderposition', models.ForeignKey(blank=True, related_name='answers', null=True, to='pretixbase.OrderPosition')),
('question', versions.models.VersionedForeignKey(related_name='answers', to='pretixbase.Question')), ('question', versions.models.VersionedForeignKey(related_name='answers', to='pretixbase.Question')),
], ],
options={ options={
@@ -380,26 +381,26 @@ class Migration(migrations.Migration):
migrations.CreateModel( migrations.CreateModel(
name='Quota', name='Quota',
fields=[ fields=[
('id', models.CharField(primary_key=True, serialize=False, max_length=36)), ('id', models.CharField(serialize=False, max_length=36, primary_key=True)),
('identity', models.CharField(max_length=36)), ('identity', models.CharField(max_length=36)),
('version_start_date', models.DateTimeField()), ('version_start_date', models.DateTimeField()),
('version_end_date', models.DateTimeField(blank=True, default=None, null=True)), ('version_end_date', models.DateTimeField(default=None, null=True, blank=True)),
('version_birth_date', models.DateTimeField()), ('version_birth_date', models.DateTimeField()),
('name', models.CharField(verbose_name='Name', max_length=200)), ('name', models.CharField(verbose_name='Name', max_length=200)),
('size', models.PositiveIntegerField(verbose_name='Total capacity')), ('size', models.PositiveIntegerField(verbose_name='Total capacity')),
('event', versions.models.VersionedForeignKey(verbose_name='Event', related_name='quotas', to='pretixbase.Event')), ('event', versions.models.VersionedForeignKey(verbose_name='Event', related_name='quotas', to='pretixbase.Event')),
('items', versions.models.VersionedManyToManyField(verbose_name='Item', blank=True, related_name='quotas', to='pretixbase.Item')), ('items', versions.models.VersionedManyToManyField(to='pretixbase.Item', verbose_name='Item', related_name='quotas', blank=True)),
('variations', pretix.base.models.VariationsField(verbose_name='Variations', blank=True, related_name='quotas', to='pretixbase.ItemVariation')), ('variations', pretix.base.models.VariationsField(to='pretixbase.ItemVariation', verbose_name='Variations', related_name='quotas', blank=True)),
], ],
options={ options={
'verbose_name_plural': 'Quotas',
'verbose_name': 'Quota', 'verbose_name': 'Quota',
'verbose_name_plural': 'Quotas',
}, },
), ),
migrations.AddField( migrations.AddField(
model_name='organizer', model_name='organizer',
name='permitted', name='permitted',
field=models.ManyToManyField(through='pretixbase.OrganizerPermission', related_name='organizers', to=settings.AUTH_USER_MODEL), field=models.ManyToManyField(related_name='organizers', to=settings.AUTH_USER_MODEL, through='pretixbase.OrganizerPermission'),
), ),
migrations.AddField( migrations.AddField(
model_name='itemvariation', model_name='itemvariation',
@@ -409,18 +410,13 @@ class Migration(migrations.Migration):
migrations.AddField( migrations.AddField(
model_name='item', model_name='item',
name='category', name='category',
field=versions.models.VersionedForeignKey(verbose_name='Category', related_name='items', to='pretixbase.ItemCategory', null=True, blank=True, on_delete=django.db.models.deletion.PROTECT), field=versions.models.VersionedForeignKey(verbose_name='Category', blank=True, to='pretixbase.ItemCategory', related_name='items', null=True, on_delete=django.db.models.deletion.PROTECT),
), ),
migrations.AddField( migrations.AddField(
model_name='item', model_name='item',
name='event', name='event',
field=versions.models.VersionedForeignKey(verbose_name='Event', related_name='items', to='pretixbase.Event', on_delete=django.db.models.deletion.PROTECT), field=versions.models.VersionedForeignKey(verbose_name='Event', related_name='items', to='pretixbase.Event', on_delete=django.db.models.deletion.PROTECT),
), ),
migrations.AddField(
model_name='item',
name='properties',
field=versions.models.VersionedManyToManyField(verbose_name='Properties', blank=True, 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.", related_name='items', to='pretixbase.Property'),
),
migrations.AddField( migrations.AddField(
model_name='event', model_name='event',
name='organizer', name='organizer',
@@ -429,7 +425,7 @@ class Migration(migrations.Migration):
migrations.AddField( migrations.AddField(
model_name='event', model_name='event',
name='permitted', name='permitted',
field=models.ManyToManyField(through='pretixbase.EventPermission', related_name='events', to=settings.AUTH_USER_MODEL), field=models.ManyToManyField(related_name='events', to=settings.AUTH_USER_MODEL, through='pretixbase.EventPermission'),
), ),
migrations.AddField( migrations.AddField(
model_name='cartposition', model_name='cartposition',
@@ -444,7 +440,7 @@ class Migration(migrations.Migration):
migrations.AddField( migrations.AddField(
model_name='cartposition', model_name='cartposition',
name='variation', name='variation',
field=versions.models.VersionedForeignKey(verbose_name='Variation', to='pretixbase.ItemVariation', null=True, blank=True), field=versions.models.VersionedForeignKey(verbose_name='Variation', blank=True, null=True, to='pretixbase.ItemVariation'),
), ),
migrations.AddField( migrations.AddField(
model_name='cachedticket', model_name='cachedticket',
+93 -102
View File
@@ -557,96 +557,6 @@ class ItemCategory(Versionable):
return self.sortkey < other.sortkey return self.sortkey < other.sortkey
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'.
:param event: The event this belongs to
:type event: Event
:param name: The name of this property.
:type name: str
"""
event = VersionedForeignKey(
Event,
related_name="properties",
)
name = I18nCharField(
max_length=250,
verbose_name=_("Property name"),
)
class Meta:
verbose_name = _("Product property")
verbose_name_plural = _("Product properties")
def __str__(self):
return str(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()
class PropertyValue(Versionable):
"""
A value of a property. If the property would be 'T-Shirt size',
this could be 'M' or 'L'.
:param prop: The property this value is a valid option for.
:type prop: Property
:param value: The value, as a human-readable string
:type value: str
:param position: An integer, used for sorting
:type position: int
"""
prop = VersionedForeignKey(
Property,
on_delete=models.CASCADE,
related_name="values"
)
value = I18nCharField(
max_length=250,
verbose_name=_("Value"),
)
position = models.IntegerField(
default=0
)
class Meta:
verbose_name = _("Property value")
verbose_name_plural = _("Property values")
ordering = ("position", "version_birth_date")
def __str__(self):
return "%s: %s" % (self.prop.name, self.value)
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()
@property
def sortkey(self):
return self.position, self.version_birth_date
def __lt__(self, other):
return self.sortkey < other.sortkey
def itempicture_upload_to(instance, filename): def itempicture_upload_to(instance, filename):
return '%s/%s/item-%s.%s' % ( return '%s/%s/item-%s.%s' % (
instance.event.organizer.slug, instance.event.slug, instance.identity, instance.event.organizer.slug, instance.event.slug, instance.identity,
@@ -675,7 +585,6 @@ class Item(Versionable):
:type default_price: decimal.Decimal :type default_price: decimal.Decimal
:param tax_rate: The VAT tax that is included in this item's price (in %) :param tax_rate: The VAT tax that is included in this item's price (in %)
:type tax_rate: decimal.Decimal :type tax_rate: decimal.Decimal
:param properties: A set of ``Property`` objects that should be applied to this item
:param admission: ``True``, if this item allows persons to enter the event (as opposed to e.g. merchandise) :param admission: ``True``, if this item allows persons to enter the event (as opposed to e.g. merchandise)
:type admission: bool :type admission: bool
:param picture: A product picture to be shown next to the product description. :param picture: A product picture to be shown next to the product description.
@@ -718,17 +627,6 @@ class Item(Versionable):
verbose_name=_("Taxes included in percent"), verbose_name=_("Taxes included in percent"),
max_digits=7, decimal_places=2 max_digits=7, decimal_places=2
) )
properties = VersionedManyToManyField(
Property,
related_name='items',
verbose_name=_("Properties"),
blank=True,
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.'
)
)
admission = models.BooleanField( admission = models.BooleanField(
verbose_name=_("Is an admission ticket"), verbose_name=_("Is an admission ticket"),
help_text=_( help_text=_(
@@ -945,6 +843,99 @@ class Item(Versionable):
return price return price
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'.
:param event: The event this belongs to
:type event: Event
:param name: The name of this property.
:type name: str
"""
event = VersionedForeignKey(
Event,
related_name="properties"
)
item = VersionedForeignKey(
Item, related_name='properties', null=True, blank=True
)
name = I18nCharField(
max_length=250,
verbose_name=_("Property name")
)
class Meta:
verbose_name = _("Product property")
verbose_name_plural = _("Product properties")
def __str__(self):
return str(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()
class PropertyValue(Versionable):
"""
A value of a property. If the property would be 'T-Shirt size',
this could be 'M' or 'L'.
:param prop: The property this value is a valid option for.
:type prop: Property
:param value: The value, as a human-readable string
:type value: str
:param position: An integer, used for sorting
:type position: int
"""
prop = VersionedForeignKey(
Property,
on_delete=models.CASCADE,
related_name="values"
)
value = I18nCharField(
max_length=250,
verbose_name=_("Value"),
)
position = models.IntegerField(
default=0
)
class Meta:
verbose_name = _("Property value")
verbose_name_plural = _("Property values")
ordering = ("position", "version_birth_date")
def __str__(self):
return "%s: %s" % (self.prop.name, self.value)
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()
@property
def sortkey(self):
return self.position, self.version_birth_date
def __lt__(self, other):
return self.sortkey < other.sortkey
class ItemVariation(Versionable): class ItemVariation(Versionable):
""" """
A variation is an item combined with values for all properties A variation is an item combined with values for all properties
+135 -2
View File
@@ -5,7 +5,9 @@ from itertools import product
from django import forms from django import forms
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db import transaction from django.db import transaction
from django.forms import BaseInlineFormSet from django.forms import (
BaseInlineFormSet, BaseModelFormSet, ModelForm, modelformset_factory,
)
from django.forms.widgets import flatatt from django.forms.widgets import flatatt
from django.utils.encoding import force_text from django.utils.encoding import force_text
from django.utils.html import format_html from django.utils.html import format_html
@@ -21,6 +23,7 @@ class I18nInlineFormSet(BaseInlineFormSet):
This is equivalent to a normal BaseInlineFormset, but cares for the special needs This is equivalent to a normal BaseInlineFormset, but cares for the special needs
of I18nForms (see there for more information). of I18nForms (see there for more information).
""" """
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.event = kwargs.pop('event', None) self.event = kwargs.pop('event', None)
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@@ -30,6 +33,32 @@ class I18nInlineFormSet(BaseInlineFormSet):
return super()._construct_form(i, **kwargs) return super()._construct_form(i, **kwargs)
class I18nFormSet(BaseModelFormSet):
"""
This is equivalent to a normal BaseModelFormset, but cares for the special needs
of I18nForms (see there for more information).
"""
def __init__(self, *args, **kwargs):
self.event = kwargs.pop('event', None)
super().__init__(*args, **kwargs)
def _construct_form(self, i, **kwargs):
kwargs['event'] = self.event
return super()._construct_form(i, **kwargs)
@property
def empty_form(self):
form = self.form(
auto_id=self.auto_id,
prefix=self.add_prefix('__prefix__'),
empty_permitted=True,
event=self.event
)
self.add_fields(form, None)
return form
class TolerantFormsetModelForm(VersionedModelForm): class TolerantFormsetModelForm(VersionedModelForm):
""" """
This is equivalent to a normal VersionedModelForm, but works around a problem that This is equivalent to a normal VersionedModelForm, but works around a problem that
@@ -407,7 +436,6 @@ class VariationsField(forms.ModelMultipleChoiceField):
class ExtFileField(forms.FileField): class ExtFileField(forms.FileField):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
ext_whitelist = kwargs.pop("ext_whitelist") ext_whitelist = kwargs.pop("ext_whitelist")
self.ext_whitelist = [i.lower() for i in ext_whitelist] self.ext_whitelist = [i.lower() for i in ext_whitelist]
@@ -422,3 +450,108 @@ class ExtFileField(forms.FileField):
if ext not in self.ext_whitelist: if ext not in self.ext_whitelist:
raise forms.ValidationError(_("Filetype not allowed!")) raise forms.ValidationError(_("Filetype not allowed!"))
return data return data
class BaseNestedFormset(I18nFormSet):
def add_fields(self, form, index):
# allow the super class to create the fields as usual
super().add_fields(form, index)
form.nested = []
for f in self.nested_formset_class:
inner_formset = f(
instance=form.instance,
data=form.data if form.is_bound else None,
prefix='%s-%s' % (form.prefix, f.get_default_prefix()),
queryset=form.instance.values.all(),
event=self.event
)
form.nested.append(inner_formset)
def is_valid(self):
result = super(BaseNestedFormset, self).is_valid()
if self.is_bound:
# look at any nested formsets, as well
for form in self.forms:
if not self._should_delete_form(form):
for n in form.nested:
result = result and n.is_valid()
return result
def save(self, commit=True):
result = super(BaseNestedFormset, self).save(commit=commit)
for form in self.forms:
if not self._should_delete_form(form):
for n in form.nested:
n.save(commit=commit)
return result
def nestedformset_factory(model, nested_formset, form=ModelForm,
formset=BaseNestedFormset, fk_name=None, fields=None,
exclude=None, extra=3, can_order=False,
can_delete=True, max_num=None,
formfield_callback=None, widgets=None,
validate_max=False, localized_fields=None,
labels=None, help_texts=None, error_messages=None):
kwargs = {
'form': form,
'formfield_callback': formfield_callback,
'formset': formset,
'extra': extra,
'can_delete': can_delete,
'can_order': can_order,
'fields': fields,
'exclude': exclude,
'max_num': max_num,
'widgets': widgets,
'validate_max': validate_max,
'localized_fields': localized_fields,
'labels': labels,
'help_texts': help_texts,
'error_messages': error_messages,
}
nfs_class = modelformset_factory(model, **kwargs)
nfs_class.nested_formset_class = []
for f in nested_formset:
nfs_class.nested_formset_class.append(f)
return nfs_class
class NestedInnerI18nInlineFormSet(I18nFormSet):
"""A formset for child objects related to a parent."""
def __init__(self, data=None, files=None, instance=None,
save_as_new=False, prefix=None, queryset=None, **kwargs):
if instance is None:
self.instance = self.fk.rel.to()
else:
self.instance = instance
self.save_as_new = save_as_new
if queryset is None:
if self.instance is not None:
queryset = getattr(self.instance, self.fk.related_query_name()).all()
else:
queryset = self.model._default_manager
if self.instance.pk is not None:
qs = queryset
else:
qs = self.model._default_manager.none()
super().__init__(data, files, prefix=prefix, queryset=qs, **kwargs)
@property
def empty_form(self):
form = self.form(
auto_id=self.auto_id,
prefix=self.add_prefix('__inner_prefix__'),
empty_permitted=True,
event=self.event
)
self.add_fields(form, None)
return form
-2
View File
@@ -124,7 +124,6 @@ class ItemFormGeneral(VersionedModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.fields['category'].queryset = self.instance.event.categories.current.all() self.fields['category'].queryset = self.instance.event.categories.current.all()
self.fields['properties'].queryset = self.instance.event.properties.current.all()
class Meta: class Meta:
model = Item model = Item
@@ -138,7 +137,6 @@ class ItemFormGeneral(VersionedModelForm):
'picture', 'picture',
'default_price', 'default_price',
'tax_rate', 'tax_rate',
'properties',
] ]
@@ -79,12 +79,6 @@
{% trans "Categories" %} {% trans "Categories" %}
</a> </a>
</li> </li>
<li>
<a href="{% url 'control:event.items.properties' organizer=request.event.organizer.slug event=request.event.slug %}"
{% if "event.items.properties" in url_name %}class="active"{% endif %}>
{% trans "Properties" %}
</a>
</li>
<li> <li>
<a href="{% url 'control:event.items.questions' organizer=request.event.organizer.slug event=request.event.slug %}" <a href="{% url 'control:event.items.questions' organizer=request.event.organizer.slug event=request.event.slug %}"
{% if "event.items.questions" in url_name %}class="active"{% endif %}> {% if "event.items.questions" in url_name %}class="active"{% endif %}>
@@ -1,13 +1,14 @@
{% extends "pretixcontrol/event/base.html" %} {% extends "pretixcontrol/event/base.html" %}
{% load i18n %} {% load i18n %}
{% block title %}{{ item.name }} :: {% trans "Product" %}{% endblock %} {% block title %}{{ object.name }} :: {% trans "Product" %}{% endblock %}
{% block content %} {% block content %}
{% if item.identity %} {% if object.identity %}
<h1>{% trans "Modify product:" %} {{ item.name }}</h1> <h1>{% trans "Modify product:" %} {{ object.name }}</h1>
<ul class="nav nav-pills"> <ul class="nav nav-pills">
<li {% if "event.item" == url_name %}class="active"{% endif %}><a href="{% url 'control:event.item' organizer=request.event.organizer.slug event=request.event.slug item=item.identity %}">{% trans "General information" %}</a></li> <li {% if "event.item" == url_name %}class="active"{% endif %}><a href="{% url 'control:event.item' organizer=request.event.organizer.slug event=request.event.slug item=object.identity %}">{% trans "General information" %}</a></li>
<li {% if "event.item.variations" == url_name %}class="active"{% endif %}><a href="{% url 'control:event.item.variations' organizer=request.event.organizer.slug event=request.event.slug item=item.identity %}">{% trans "Variations" %}</a></li> <li {% if "event.item.properties" == url_name %}class="active"{% endif %}><a href="{% url 'control:event.item.properties' organizer=request.event.organizer.slug event=request.event.slug item=object.identity %}">{% trans "Properties" %}</a></li>
<li {% if "event.item.restrictions" == url_name %}class="active"{% endif %}><a href="{% url 'control:event.item.restrictions' organizer=request.event.organizer.slug event=request.event.slug item=item.identity %}">{% trans "Restrictions" %}</a></li> <li {% if "event.item.variations" == url_name %}class="active"{% endif %}><a href="{% url 'control:event.item.variations' organizer=request.event.organizer.slug event=request.event.slug item=object.identity %}">{% trans "Variations" %}</a></li>
<li {% if "event.item.restrictions" == url_name %}class="active"{% endif %}><a href="{% url 'control:event.item.restrictions' organizer=request.event.organizer.slug event=request.event.slug item=object.identity %}">{% trans "Restrictions" %}</a></li>
</ul> </ul>
{% else %} {% else %}
<h1>{% trans "Create product" %}</h1> <h1>{% trans "Create product" %}</h1>
@@ -15,7 +16,7 @@
You will be able to adjust further settings in the next step. You will be able to adjust further settings in the next step.
{% endblocktrans %}</p> {% endblocktrans %}</p>
{% endif %} {% endif %}
{% if item.identity and not item.quotas.exists %} {% if object.identity and not object.quotas.exists %}
<div class="alert alert-warning"> <div class="alert alert-warning">
{% blocktrans trimmed %} {% blocktrans trimmed %}
Please note, that your product will <strong>not</strong> be available for sale until you added your Please note, that your product will <strong>not</strong> be available for sale until you added your
@@ -18,10 +18,6 @@
{% bootstrap_field form.default_price layout="horizontal" %} {% bootstrap_field form.default_price layout="horizontal" %}
{% bootstrap_field form.tax_rate layout="horizontal" %} {% bootstrap_field form.tax_rate layout="horizontal" %}
</fieldset> </fieldset>
<fieldset>
<legend>{% trans "Advanced settings" %}</legend>
{% bootstrap_field form.properties layout="horizontal" %}
</fieldset>
<div class="form-group submit-group"> <div class="form-group submit-group">
<button type="submit" class="btn btn-primary btn-save"> <button type="submit" class="btn btn-primary btn-save">
{% trans "Save" %} {% trans "Save" %}
@@ -0,0 +1,147 @@
{% extends "pretixcontrol/item/base.html" %}
{% load i18n %}
{% load bootstrap3 %}
{% load formset_tags %}
{% block inside %}
<form class="form-horizontal branches" method="post" action="">
{% csrf_token %}
{% if state %}
<div class="alert alert-{{ state.class }}">{{ state.message|safe }}</div>
{% endif %}
<div class="formset" data-formset data-formset-prefix="{{ formset.prefix }}">
{{ formset.management_form }}
<div data-formset-body>
{% for form in formset %}
<div class="panel panel-default" data-formset-form>
<div class="sr-only">
{{ form.id }}
{% bootstrap_field form.DELETE form_group_class="" layout="inline" %}
</div>
<div class="panel-heading">
<h4 class="panel-title">
{% bootstrap_field form.name layout='inline' form_group_class="" %}
</h4>
</div>
<div class="panel-body">
<div class="nested-formset" data-formset-prefix="{{ form.nested.0.prefix }}">
<div data-nested-formset-body>
{{ form.nested.0.management_form }}
{% for f in form.nested.0 %}
<div class="form-group" data-nested-formset-form>
{{ f.id }}
<div class="col-sm-9">
{% bootstrap_field f.value form_group_class="" layout="inline" %}
</div>
<div class="sr-only">
{% bootstrap_field f.ORDER form_group_class="" layout="inline" %}
{% bootstrap_field f.DELETE form_group_class="" layout="inline" %}
</div>
<div class="col-sm-3 text-right">
<button type="button" class="btn btn-default"
data-nested-formset-move-up-button>
<i class="fa fa-arrow-up"></i></button>
<button type="button" class="btn btn-default"
data-nested-formset-move-down-button>
<i class="fa fa-arrow-down"></i></button>
<button type="button" class="btn btn-danger" data-nested-formset-delete-button>
<i class="fa fa-trash"></i></button>
</div>
</div>
{% endfor %}
</div>
<script type="form-template" data-nested-formset-empty-form>
{% escapescript %}
<div class="form-group" data-nested-formset-form>
{{ form.nested.0.empty_form.id }}
<div class="col-sm-9">
{% bootstrap_field form.nested.0.empty_form.value form_group_class="" layout="inline" %}
</div>
<div class="sr-only">
{% bootstrap_field form.nested.0.empty_form.ORDER form_group_class="" layout="inline" %}
{% bootstrap_field form.nested.0.empty_form.DELETE form_group_class="" layout="inline" %}
</div>
<div class="col-sm-3 text-right">
<button type="button" class="btn btn-default"
data-nested-formset-move-up-button>
<i class="fa fa-arrow-up"></i></button>
<button type="button" class="btn btn-default"
data-nested-formset-move-down-button>
<i class="fa fa-arrow-down"></i></button>
<button type="button" class="btn btn-danger" data-nested-formset-delete-button>
<i class="fa fa-trash"></i></button>
</div>
</div>
{% endescapescript %}
</script>
<p style="margin-top: 10px">
<button type="button" class="btn btn-default" data-nested-formset-add>
<i class="fa fa-plus"></i> {% trans "Add a new value" %}</button>
</p>
</div>
</div>
</div>
{% endfor %}
</div>
<script type="form-template" data-formset-empty-form>
{% escapescript %}
<div class="panel panel-default" data-formset-form>
<div class="sr-only">
{{ formset.empty_form.id }}
{% bootstrap_field formset.empty_form.DELETE form_group_class="" layout="inline" %}
</div>
<div class="panel-heading">
<h4 class="panel-title">
{% bootstrap_field formset.empty_form.name layout='inline' form_group_class="" %}
</h4>
</div>
<div class="panel-body">
<div class="nested-formset" data-formset-prefix="{{ formset.empty_form.nested.0.prefix }}">
<div data-nested-formset-body>
{{ formset.empty_form.nested.0.management_form }}
</div>
{{ '<script type="form-template" data-nested-formset-empty-form>' }}
<div class="form-group" data-nested-formset-form>
{{ formset.empty_form.nested.0.empty_form.id }}
<div class="col-sm-9">
{% bootstrap_field formset.empty_form.nested.0.empty_form.value form_group_class="" layout="inline" %}
</div>
<div class="sr-only">
{% bootstrap_field formset.empty_form.nested.0.empty_form.ORDER form_group_class="" layout="inline" %}
{% bootstrap_field formset.empty_form.nested.0.empty_form.DELETE form_group_class="" layout="inline" %}
</div>
<div class="col-sm-3 text-right">
<button type="button" class="btn btn-default"
data-nested-formset-move-up-button>
<i class="fa fa-arrow-up"></i></button>
<button type="button" class="btn btn-default"
data-nested-formset-move-down-button>
<i class="fa fa-arrow-down"></i></button>
<button type="button" class="btn btn-danger" data-nested-formset-delete-button>
<i class="fa fa-trash"></i></button>
</div>
</div>
{{ '</script>' }}
<p style="margin-top: 10px">
<button type="button" class="btn btn-default" data-nested-formset-add>
<i class="fa fa-plus"></i> {% trans "Add a new value" %}</button>
</p>
</div>
</div>
</div>
{% endescapescript %}
</script>
<p>
<button type="button" class="btn btn-default" data-formset-add>
<i class="fa fa-plus"></i> {% trans "Add a new property" %}</button>
</p>
</div>
<div class="form-group submit-group">
<button type="submit" class="btn btn-primary btn-save">
{% trans "Save" %}
</button>
</div>
</form>
{% endblock %}
@@ -1,30 +0,0 @@
{% extends "pretixcontrol/items/base.html" %}
{% load i18n %}
{% block title %}{% trans "Product properties" %}{% endblock %}
{% block inside %}
<h1>{% trans "Product properties" %}</h1>
<p>
<a href="{% url "control:event.items.properties.add" organizer=request.event.organizer.slug event=request.event.slug %}" class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create new property" %}</a>
</p>
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>{% trans "Product properties" %}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for p in properties %}
<tr>
<td><strong><a href="
{% url "control:event.items.properties.edit" organizer=request.event.organizer.slug event=request.event.slug property=p.identity %}">{{ p.name }}</a></strong></td>
<td class="text-right"><a href="
{% url "control:event.items.properties.delete" organizer=request.event.organizer.slug event=request.event.slug property=p.identity %}" class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% include "pretixcontrol/pagination.html" %}
{% endblock %}
@@ -1,65 +0,0 @@
{% extends "pretixcontrol/items/base.html" %}
{% load i18n %}
{% load bootstrap3 %}
{% load formset_tags %}
{% block title %}{% trans "Product property" %}{% endblock %}
{% block inside %}
<h1>{% trans "Product property" %}</h1>
<form action="" method="post" class="form-horizontal">
{% csrf_token %}
<fieldset>
<legend>{% trans "General information" %}</legend>
{% bootstrap_field form.name layout="horizontal" %}
</fieldset>
<fieldset>
<legend>{% trans "Values" %}</legend>
<div data-formset data-formset-prefix="{{ formset.prefix }}">
<div data-formset-body>
{{ formset.management_form }}
{% for f in formset %}
<div class="form-group" data-formset-form>
{{ f.id }}
<div class="col-sm-9">
{% bootstrap_field f.value form_group_class="" layout="inline" %}
</div>
<div class="sr-only">
{% bootstrap_field f.ORDER form_group_class="" layout="inline" %}
{% bootstrap_field f.DELETE form_group_class="" layout="inline" %}
</div>
<div class="col-sm-3 text-right">
<button type="button" class="btn btn-default" data-formset-move-up-button><i class="fa fa-arrow-up"></i></button>
<button type="button" class="btn btn-default" data-formset-move-down-button><i class="fa fa-arrow-down"></i></button>
<button type="button" class="btn btn-danger" data-formset-delete-button><i class="fa fa-trash"></i></button>
</div>
</div>
{% endfor %}
</div>
<script type="form-template" data-formset-empty-form>
{% escapescript %}
<div class="form-group" data-formset-form>
{{ formset.empty_form.id }}
<div class="col-sm-9">
{% bootstrap_field formset.empty_form.value form_group_class="" layout="inline" %}
</div>
<div class="sr-only">
{% bootstrap_field formset.empty_form.ORDER form_group_class="" layout="inline" %}
{% bootstrap_field formset.empty_form.DELETE form_group_class="" layout="inline" %}
</div>
<div class="col-sm-3 text-right">
<button type="button" class="btn btn-default" data-formset-move-up-button><i class="fa fa-arrow-up"></i></button>
<button type="button" class="btn btn-default" data-formset-move-down-button><i class="fa fa-arrow-down"></i></button>
<button type="button" class="btn btn-danger" data-formset-delete-button><i class="fa fa-trash"></i></button>
</div>
</div>
{% endescapescript %}
</script>
<button type="button" class="btn btn-default" data-formset-add><i class="fa fa-plus"></i> {% trans "Add a new value" %}</button>
</div>
</fieldset>
<div class="form-group submit-group">
<button type="submit" class="btn btn-primary btn-save">
{% trans "Save" %}
</button>
</div>
</form>
{% endblock %}
@@ -1,28 +0,0 @@
{% extends "pretixcontrol/items/base.html" %}
{% load i18n %}
{% load bootstrap3 %}
{% block title %}{% trans "Delete product property" %}{% endblock %}
{% block inside %}
<h1>{% trans "Delete product property" %}</h1>
{% if not possible %}
<p>{% blocktrans %}You can not delete the property <strong>{{ property }}</strong> as long as the following products use it:{% endblocktrans %}</p>
<ul>
{% for item in dependent %}
<li><a href="{% url "control:event.item" organizer=request.event.organizer.slug event=request.event.slug item=item.pk %}">{{ item.name }}</a></li>
{% endfor %}
</ul>
{% else %}
<form action="" method="post" class="form-horizontal">
{% csrf_token %}
<p>{% blocktrans %}Are you sure you want to delete the property <strong>{{ property }}</strong>?{% endblocktrans %}</p>
<div class="form-group submit-group">
<a href="{% url "control:event.items.properties" organizer=request.event.organizer.slug event=request.event.slug %}" class="btn btn-default btn-cancel">
{% trans "Cancel" %}
</a>
<button type="submit" class="btn btn-danger btn-save">
{% trans "Delete" %}
</button>
</div>
</form>
{% endif %}
{% endblock %}
+2 -6
View File
@@ -32,6 +32,8 @@ urlpatterns = [
name='event.item.variations'), name='event.item.variations'),
url(r'^items/(?P<item>[0-9a-f-]+)/restrictions$', item.ItemRestrictions.as_view(), url(r'^items/(?P<item>[0-9a-f-]+)/restrictions$', item.ItemRestrictions.as_view(),
name='event.item.restrictions'), name='event.item.restrictions'),
url(r'^items/(?P<item>[0-9a-f-]+)/properties$', item.ItemProperties.as_view(),
name='event.item.properties'),
url(r'^items/(?P<item>[0-9a-f-]+)/up$', item.item_move_up, name='event.items.up'), url(r'^items/(?P<item>[0-9a-f-]+)/up$', item.item_move_up, name='event.items.up'),
url(r'^items/(?P<item>[0-9a-f-]+)/down$', item.item_move_down, name='event.items.down'), url(r'^items/(?P<item>[0-9a-f-]+)/down$', item.item_move_down, name='event.items.down'),
url(r'^items/(?P<item>[0-9a-f-]+)/delete$', item.ItemDelete.as_view(), name='event.items.delete'), url(r'^items/(?P<item>[0-9a-f-]+)/delete$', item.ItemDelete.as_view(), name='event.items.delete'),
@@ -50,12 +52,6 @@ urlpatterns = [
url(r'^questions/(?P<question>[0-9a-f-]+)/$', item.QuestionUpdate.as_view(), url(r'^questions/(?P<question>[0-9a-f-]+)/$', item.QuestionUpdate.as_view(),
name='event.items.questions.edit'), name='event.items.questions.edit'),
url(r'^questions/add$', item.QuestionCreate.as_view(), name='event.items.questions.add'), url(r'^questions/add$', item.QuestionCreate.as_view(), name='event.items.questions.add'),
url(r'^properties/$', item.PropertyList.as_view(), name='event.items.properties'),
url(r'^properties/(?P<property>[0-9a-f-]+)/$', item.PropertyUpdate.as_view(),
name='event.items.properties.edit'),
url(r'^properties/(?P<property>[0-9a-f-]+)/delete$', item.PropertyDelete.as_view(),
name='event.items.properties.delete'),
url(r'^properties/add$', item.PropertyCreate.as_view(), name='event.items.properties.add'),
url(r'^quotas/$', item.QuotaList.as_view(), name='event.items.quotas'), url(r'^quotas/$', item.QuotaList.as_view(), name='event.items.quotas'),
url(r'^quotas/(?P<quota>[0-9a-f-]+)/$', item.QuotaUpdate.as_view(), name='event.items.quotas.edit'), url(r'^quotas/(?P<quota>[0-9a-f-]+)/$', item.QuotaUpdate.as_view(), name='event.items.quotas.edit'),
url(r'^quotas/(?P<quota>[0-9a-f-]+)/delete$', item.QuotaDelete.as_view(), url(r'^quotas/(?P<quota>[0-9a-f-]+)/delete$', item.QuotaDelete.as_view(),
+74 -173
View File
@@ -17,7 +17,10 @@ from pretix.base.models import (
Item, ItemCategory, ItemVariation, Property, PropertyValue, Question, Item, ItemCategory, ItemVariation, Property, PropertyValue, Question,
Quota, Quota,
) )
from pretix.control.forms import I18nInlineFormSet, VariationsField from pretix.control.forms import (
I18nInlineFormSet, NestedInnerI18nInlineFormSet, VariationsField,
nestedformset_factory,
)
from pretix.control.forms.item import ( from pretix.control.forms.item import (
CategoryForm, ItemFormGeneral, ItemVariationForm, PropertyForm, CategoryForm, ItemFormGeneral, ItemVariationForm, PropertyForm,
PropertyValueForm, QuestionForm, QuotaForm, PropertyValueForm, QuestionForm, QuotaForm,
@@ -219,177 +222,6 @@ def category_move_down(request, organizer, event, category):
event=request.event.slug) event=request.event.slug)
class PropertyList(ListView):
model = Property
context_object_name = 'properties'
paginate_by = 30
template_name = 'pretixcontrol/items/properties.html'
def get_queryset(self):
return Property.objects.current.filter(
event=self.request.event
)
class PropertyUpdate(EventPermissionRequiredMixin, UpdateView):
model = Property
form_class = PropertyForm
template_name = 'pretixcontrol/items/property.html'
permission = 'can_change_items'
context_object_name = 'property'
def get_object(self, queryset=None) -> Property:
try:
return self.request.event.properties.current.get(
identity=self.kwargs['property']
)
except Property.DoesNotExist:
raise Http404(_("The requested property does not exist."))
def get_success_url(self) -> str:
return reverse('control:event.items.properties.edit', kwargs={
'organizer': self.request.event.organizer.slug,
'event': self.request.event.slug,
'property': self.kwargs['property']
})
def get_formset(self):
formsetclass = inlineformset_factory(
Property, PropertyValue,
form=PropertyValueForm,
formset=I18nInlineFormSet,
can_order=True,
extra=0,
)
kwargs = self.get_form_kwargs()
kwargs['queryset'] = self.object.values.current.all()
formset = formsetclass(**kwargs)
return formset
def get_context_data(self, *args, **kwargs) -> dict:
context = super().get_context_data(*args, **kwargs)
context['formset'] = self.get_formset()
return context
@transaction.atomic()
def form_valid(self, form, formset):
for f in formset.deleted_forms:
f.instance.delete()
f.instance.pk = None
for i, f in enumerate(formset.ordered_forms):
f.save(commit=False)
f.instance.position = i
f.instance.save()
messages.success(self.request, _('Your changes have been saved.'))
return super().form_valid(form)
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form_class = self.get_form_class()
form = self.get_form(form_class)
formset = self.get_formset()
if form.is_valid() and formset.is_valid():
return self.form_valid(form, formset)
else:
return self.form_invalid(form)
class PropertyCreate(EventPermissionRequiredMixin, CreateView):
model = Property
form_class = PropertyForm
template_name = 'pretixcontrol/items/property.html'
permission = 'can_change_items'
context_object_name = 'property'
def get_success_url(self) -> str:
return reverse('control:event.items.properties', kwargs={
'organizer': self.request.event.organizer.slug,
'event': self.request.event.slug,
})
def get_formset(self):
formsetclass = inlineformset_factory(
Property, PropertyValue,
form=PropertyValueForm,
formset=I18nInlineFormSet,
can_order=True,
extra=3,
)
formset = formsetclass(**self.get_form_kwargs())
return formset
def get_context_data(self, *args, **kwargs) -> dict:
self.object = None
context = super().get_context_data(*args, **kwargs)
context['formset'] = self.get_formset()
return context
@transaction.atomic()
def form_valid(self, form, formset):
form.instance.event = self.request.event
resp = super().form_valid(form)
for i, f in enumerate(formset.ordered_forms):
f.instance.position = i
f.instance.prop = form.instance
f.instance.save()
messages.success(self.request, _('The new property has been created.'))
return resp
def post(self, request, *args, **kwargs):
form_class = self.get_form_class()
form = self.get_form(form_class)
formset = self.get_formset()
if form.is_valid() and formset.is_valid():
return self.form_valid(form, formset)
else:
return self.form_invalid(form)
class PropertyDelete(EventPermissionRequiredMixin, DeleteView):
model = Property
form_class = PropertyForm
template_name = 'pretixcontrol/items/property_delete.html'
permission = 'can_change_items'
context_object_name = 'property'
def get_context_data(self, *args, **kwargs) -> dict:
context = super().get_context_data(*args, **kwargs)
context['dependent'] = self.get_object().items.current.all()
context['possible'] = self.is_allowed()
return context
def is_allowed(self) -> bool:
return self.get_object().items.current.count() == 0
def get_object(self, queryset=None) -> Property:
if not hasattr(self, 'object') or not self.object:
try:
self.object = self.request.event.properties.current.get(
identity=self.kwargs['property']
)
except Property.DoesNotExist:
raise Http404(_("The requested property does not exist."))
return self.object
def delete(self, request, *args, **kwargs):
if self.is_allowed():
success_url = self.get_success_url()
self.get_object().delete()
messages.success(request, _('The selected property has been deleted.'))
return HttpResponseRedirect(success_url)
else:
messages.error(request, _('The selected property can not be deleted.'))
return HttpResponseRedirect(self.get_success_url())
def get_success_url(self) -> str:
return reverse('control:event.items.properties', kwargs={
'organizer': self.request.event.organizer.slug,
'event': self.request.event.slug,
})
class QuestionList(ListView): class QuestionList(ListView):
model = Question model = Question
context_object_name = 'questions' context_object_name = 'questions'
@@ -676,6 +508,75 @@ class ItemUpdateGeneral(ItemDetailMixin, EventPermissionRequiredMixin, UpdateVie
return super().form_valid(form) return super().form_valid(form)
class ItemProperties(ItemDetailMixin, EventPermissionRequiredMixin, TemplateView):
permission = 'can_change_items'
template_name = 'pretixcontrol/item/properties.html'
def get_success_url(self) -> str:
return reverse('control:event.item.properties', kwargs={
'organizer': self.request.event.organizer.slug,
'event': self.request.event.slug,
'item': self.get_object().identity,
})
def get_inner_formset_class(self):
formsetclass = inlineformset_factory(
Property, PropertyValue,
form=PropertyValueForm,
formset=NestedInnerI18nInlineFormSet,
can_order=True, extra=0
)
return formsetclass
def get_outer_formset(self):
formsetclass = nestedformset_factory(
Property, [self.get_inner_formset_class()],
form=PropertyForm, can_order=False, can_delete=True, extra=0
)
formset = formsetclass(self.request.POST if self.request.method == "POST" else None,
queryset=Property.objects.current.filter(item=self.object).prefetch_related('values'),
event=self.request.event)
return formset
def get_context_data(self, **kwargs):
self.object = self.get_object()
ctx = super().get_context_data(**kwargs)
ctx['formset'] = self.get_outer_formset()
return ctx
@transaction.atomic()
def form_valid(self, formset):
for f in formset:
f.instance.event = self.request.event
f.instance.item = self.get_object()
f.instance.save()
print(f.instance)
for n in f.nested:
print(n.deleted_forms, n.ordered_forms, n.extra_forms)
for fn in n.deleted_forms:
fn.instance.delete()
fn.instance.pk = None
for i, fn in enumerate(n.ordered_forms + [ef for ef in n.extra_forms if ef not in n.ordered_forms]):
fn.instance.position = i
fn.instance.prop = f.instance
fn.save()
n.save_new_objects()
messages.success(self.request, _('Your changes have been saved.'))
return redirect(self.get_success_url())
def post(self, request, *args, **kwargs):
self.object = self.get_object()
formset = self.get_outer_formset()
if formset.is_valid():
return self.form_valid(formset)
else:
return self.get(request, *args, **kwargs)
class ItemVariations(ItemDetailMixin, EventPermissionRequiredMixin, TemplateView): class ItemVariations(ItemDetailMixin, EventPermissionRequiredMixin, TemplateView):
permission = 'can_change_items' permission = 'can_change_items'
@@ -782,7 +683,7 @@ class ItemVariations(ItemDetailMixin, EventPermissionRequiredMixin, TemplateView
grids.append({'row': val1, 'forms': formrow}) grids.append({'row': val1, 'forms': formrow})
forms.append({'row': ", ".join([value.value for value in gridrow]), 'forms': grids}) forms.append({'row': ", ".join([str(value.value) for value in gridrow]), 'forms': grids})
return forms, forms_flat return forms, forms_flat
@@ -10,29 +10,29 @@ import pretix.base.models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('pretixbase', '0001_initial'), ('pretixbase', '__first__'),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='TimeRestriction', name='TimeRestriction',
fields=[ fields=[
('id', models.CharField(primary_key=True, serialize=False, max_length=36)), ('id', models.CharField(serialize=False, max_length=36, primary_key=True)),
('identity', models.CharField(max_length=36)), ('identity', models.CharField(max_length=36)),
('version_start_date', models.DateTimeField()), ('version_start_date', models.DateTimeField()),
('version_end_date', models.DateTimeField(blank=True, default=None, null=True)), ('version_end_date', models.DateTimeField(null=True, blank=True, default=None)),
('version_birth_date', models.DateTimeField()), ('version_birth_date', models.DateTimeField()),
('timeframe_from', models.DateTimeField(verbose_name='Start of time frame')), ('timeframe_from', models.DateTimeField(verbose_name='Start of time frame')),
('timeframe_to', models.DateTimeField(verbose_name='End of time frame')), ('timeframe_to', models.DateTimeField(verbose_name='End of time frame')),
('price', models.DecimalField(verbose_name='Price in time frame', blank=True, max_digits=7, null=True, decimal_places=2)), ('price', models.DecimalField(decimal_places=2, max_digits=7, null=True, blank=True, verbose_name='Price in time frame')),
('event', versions.models.VersionedForeignKey(verbose_name='Event', related_name='restrictions_timerestriction_timerestriction', to='pretixbase.Event')), ('event', versions.models.VersionedForeignKey(verbose_name='Event', to='pretixbase.Event', related_name='restrictions_timerestriction_timerestriction')),
('item', versions.models.VersionedForeignKey(verbose_name='Item', related_name='restrictions_timerestriction_timerestriction', to='pretixbase.Item', null=True, blank=True)), ('item', versions.models.VersionedForeignKey(related_name='restrictions_timerestriction_timerestriction', null=True, verbose_name='Item', to='pretixbase.Item', blank=True)),
('variations', pretix.base.models.VariationsField(verbose_name='Variations', blank=True, related_name='restrictions_timerestriction_timerestriction', to='pretixbase.ItemVariation')), ('variations', pretix.base.models.VariationsField(related_name='restrictions_timerestriction_timerestriction', blank=True, to='pretixbase.ItemVariation', verbose_name='Variations')),
], ],
options={ options={
'verbose_name_plural': 'Restrictions', 'verbose_name_plural': 'Restrictions',
'verbose_name': 'Restriction',
'abstract': False, 'abstract': False,
'verbose_name': 'Restriction',
}, },
), ),
] ]
+19 -2
View File
@@ -1,9 +1,26 @@
"use strict"; "use strict";
$(function () { $(function () {
$("[data-formset]").formset({ var nested_formset_config = {
form: '[data-nested-formset-form]',
emptyForm: 'script[type=form-template][data-nested-formset-empty-form]',
body: '[data-nested-formset-body]',
add: '[data-nested-formset-add]',
deleteButton: '[data-nested-formset-delete-button]',
moveUpButton: '[data-nested-formset-move-up-button]',
moveDownButton: '[data-nested-formset-move-down-button]',
animateForms: true, animateForms: true,
reorderMode: 'animate' reorderMode: 'animate',
empty_prefix: '__inner_prefix__'
};
$("[data-formset]").formset(
{
animateForms: true,
reorderMode: 'animate'
}
).on("formAdded", "[data-formset-form]", function () {
$(this).find(".nested-formset").formset(nested_formset_config);
}); });
$(".nested-formset").formset(nested_formset_config);
$(document).on("click", ".variations .variations-select-all", function (e) { $(document).on("click", ".variations .variations-select-all", function (e) {
$(this).parent().parent().find("input[type=checkbox]").prop("checked", true).change(); $(this).parent().parent().find("input[type=checkbox]").prop("checked", true).change();
e.stopPropagation(); e.stopPropagation();
+4
View File
@@ -28,6 +28,10 @@ td > .form-group > .checkbox {
.box-shadow(none); .box-shadow(none);
} }
div[data-formset-body], div[data-formset-form], div[data-nested-formset-form], div[data-nested-formset-body] {
width: 100%;
}
.form-plugins .panel-title { .form-plugins .panel-title {
line-height: 34px; line-height: 34px;
} }
+15 -13
View File
@@ -36,7 +36,8 @@ class ItemVariationsTest(TestCase):
def test_variationdict(self): def test_variationdict(self):
i = Item.objects.create(event=self.event, name='Dummy', default_price=0) i = Item.objects.create(event=self.event, name='Dummy', default_price=0)
i.properties.add(self.p_size) self.p_size.item = i
self.p_size.save()
iv = ItemVariation.objects.create(item=i) iv = ItemVariation.objects.create(item=i)
iv.values.add(self.pv_size_s) iv.values.add(self.pv_size_s)
@@ -83,7 +84,8 @@ class ItemVariationsTest(TestCase):
self.assertEqual(v[0], {}) self.assertEqual(v[0], {})
# One property, no variations # One property, no variations
i.properties.add(self.p_size) self.p_size.item = i
self.p_size.save()
v = i.get_all_variations() v = i.get_all_variations()
self.assertIs(type(v), list) self.assertIs(type(v), list)
self.assertEqual(len(v), 3) self.assertEqual(len(v), 3)
@@ -116,7 +118,8 @@ class ItemVariationsTest(TestCase):
self.assertEqual(num_variations, 1) self.assertEqual(num_variations, 1)
# Two properties, one variation # Two properties, one variation
i.properties.add(self.p_color) self.p_color.item = i
self.p_color.save()
iv.values.add(self.pv_color_black) iv.values.add(self.pv_color_black)
v = i.get_all_variations() v = i.get_all_variations()
self.assertIs(type(v), list) self.assertIs(type(v), list)
@@ -150,14 +153,14 @@ class VersionableTestCase(TestCase):
organizer=o, name='Dummy', slug='dummy', organizer=o, name='Dummy', slug='dummy',
date_from=now(), date_from=now(),
) )
old = Item.objects.create(event=event, name='Dummy', default_price=14) item = Item.objects.create(event=event, name='Dummy', default_price=14)
prop = Property.objects.create(event=event, name='Size') quota_old = Quota.objects.create(event=event, name='All', size=5)
old.properties.add(prop) quota_old.items.add(item)
new = old.clone_shallow() quota_new = quota_old.clone_shallow()
self.assertIsNone(new.version_end_date) self.assertIsNone(quota_new.version_end_date)
self.assertIsNotNone(old.version_end_date) self.assertIsNotNone(quota_old.version_end_date)
self.assertEqual(new.properties.count(), 0) self.assertEqual(quota_new.items.count(), 0)
self.assertEqual(old.properties.count(), 1) self.assertEqual(quota_old.items.count(), 1)
class UserTestCase(TestCase): class UserTestCase(TestCase):
@@ -198,13 +201,12 @@ class BaseQuotaTestCase(TestCase):
self.item1 = Item.objects.create(event=self.event, name="Ticket", default_price=23, self.item1 = Item.objects.create(event=self.event, name="Ticket", default_price=23,
admission=True) admission=True)
self.item2 = Item.objects.create(event=self.event, name="T-Shirt", default_price=23) self.item2 = Item.objects.create(event=self.event, name="T-Shirt", default_price=23)
p = Property.objects.create(event=self.event, name='Size') p = Property.objects.create(event=self.event, name='Size', item=self.item2)
pv1 = PropertyValue.objects.create(prop=p, value='S') pv1 = PropertyValue.objects.create(prop=p, value='S')
PropertyValue.objects.create(prop=p, value='M') PropertyValue.objects.create(prop=p, value='M')
PropertyValue.objects.create(prop=p, value='L') PropertyValue.objects.create(prop=p, value='L')
self.var1 = ItemVariation.objects.create(item=self.item2) self.var1 = ItemVariation.objects.create(item=self.item2)
self.var1.values.add(pv1) self.var1.values.add(pv1)
self.item2.properties.add(p)
class QuotaTestCase(BaseQuotaTestCase): class QuotaTestCase(BaseQuotaTestCase):
+7 -3
View File
@@ -110,7 +110,11 @@ class CategoriesTest(ItemFormTest):
class PropertiesTest(ItemFormTest): class PropertiesTest(ItemFormTest):
"""
Properties have moved from their original place, skip this for now
"""
@unittest.skip
def test_create(self): def test_create(self):
self.driver.get('%s/control/event/%s/%s/properties/add' % ( self.driver.get('%s/control/event/%s/%s/properties/add' % (
self.live_server_url, self.orga1.slug, self.event1.slug self.live_server_url, self.orga1.slug, self.event1.slug
@@ -125,7 +129,7 @@ class PropertiesTest(ItemFormTest):
self.assertEqual("S", self.driver.find_element_by_name("values-0-value_0").get_attribute("value")) self.assertEqual("S", self.driver.find_element_by_name("values-0-value_0").get_attribute("value"))
self.assertEqual("M", self.driver.find_element_by_name("values-1-value_0").get_attribute("value")) self.assertEqual("M", self.driver.find_element_by_name("values-1-value_0").get_attribute("value"))
@unittest.skipIf('TRAVIS' in os.environ, 'See CategoriesTest.test_sort for details.') @unittest.skip
def test_update(self): def test_update(self):
c = Property.objects.create(event=self.event1, name="Size") c = Property.objects.create(event=self.event1, name="Size")
p1 = PropertyValue.objects.create(prop=c, position=0, value="S") p1 = PropertyValue.objects.create(prop=c, position=0, value="S")
@@ -153,6 +157,7 @@ class PropertiesTest(ItemFormTest):
assert not PropertyValue.objects.current.filter(identity=p1.identity).exists() assert not PropertyValue.objects.current.filter(identity=p1.identity).exists()
assert str(Property.objects.as_of(t1).get(identity=c.identity).name) == 'Size' assert str(Property.objects.as_of(t1).get(identity=c.identity).name) == 'Size'
@unittest.skip
def test_delete(self): def test_delete(self):
c = Property.objects.create(event=self.event1, name="Size") c = Property.objects.create(event=self.event1, name="Size")
t1 = now() t1 = now()
@@ -224,8 +229,7 @@ class QuotaTest(ItemFormTest):
c = Quota.objects.create(event=self.event1, name="Full house", size=500) c = Quota.objects.create(event=self.event1, name="Full house", size=500)
item1 = Item.objects.create(event=self.event1, name="Standard", default_price=0) item1 = Item.objects.create(event=self.event1, name="Standard", default_price=0)
item2 = Item.objects.create(event=self.event1, name="Business", default_price=0) item2 = Item.objects.create(event=self.event1, name="Business", default_price=0)
prop1 = Property.objects.create(event=self.event1, name="Level") prop1 = Property.objects.create(event=self.event1, name="Level", item=item2)
item2.properties.add(prop1)
PropertyValue.objects.create(prop=prop1, value="Silver") PropertyValue.objects.create(prop=prop1, value="Silver")
PropertyValue.objects.create(prop=prop1, value="Gold") PropertyValue.objects.create(prop=prop1, value="Gold")
t1 = now() t1 = now()
+1 -8
View File
@@ -35,6 +35,7 @@ event_urls = [
"items/add", "items/add",
"items/abc/", "items/abc/",
"items/abc/variations", "items/abc/variations",
"items/abc/properties",
"items/abc/restrictions", "items/abc/restrictions",
"categories/", "categories/",
"categories/add", "categories/add",
@@ -46,10 +47,6 @@ event_urls = [
"questions/abc/delete", "questions/abc/delete",
"questions/abc/", "questions/abc/",
"questions/add", "questions/add",
"properties/",
"properties/abc/delete",
"properties/abc/",
"properties/add",
"quotas/", "quotas/",
"quotas/abc/delete", "quotas/abc/delete",
"quotas/abc/", "quotas/abc/",
@@ -112,10 +109,6 @@ event_permission_urls = [
("can_change_items", "questions/abc/", 404), ("can_change_items", "questions/abc/", 404),
("can_change_items", "questions/abc/delete", 404), ("can_change_items", "questions/abc/delete", 404),
("can_change_items", "questions/add", 200), ("can_change_items", "questions/add", 200),
# ("can_change_items", "properties/", 200),
("can_change_items", "properties/abc/", 404),
("can_change_items", "properties/abc/delete", 404),
("can_change_items", "properties/add", 200),
# ("can_change_items", "quotas/", 200), # ("can_change_items", "quotas/", 200),
("can_change_items", "quotas/abc/", 404), ("can_change_items", "quotas/abc/", 404),
("can_change_items", "quotas/abc/delete", 404), ("can_change_items", "quotas/abc/delete", 404),
+1 -2
View File
@@ -20,8 +20,7 @@ def env():
user = User.objects.create_user('dummy@dummy.dummy', 'dummy') user = User.objects.create_user('dummy@dummy.dummy', 'dummy')
EventPermission.objects.create(user=user, event=event) EventPermission.objects.create(user=user, event=event)
shirt = Item.objects.create(event=event, name='T-Shirt', default_price=12) shirt = Item.objects.create(event=event, name='T-Shirt', default_price=12)
prop1 = Property.objects.create(event=event, name="Color") prop1 = Property.objects.create(event=event, name="Color", item=shirt)
shirt.properties.add(prop1)
val1 = PropertyValue.objects.create(prop=prop1, value="Red", position=0) val1 = PropertyValue.objects.create(prop=prop1, value="Red", position=0)
val2 = PropertyValue.objects.create(prop=prop1, value="Black", position=1) val2 = PropertyValue.objects.create(prop=prop1, value="Black", position=1)
shirt_red = ItemVariation.objects.create(item=shirt, default_price=14) shirt_red = ItemVariation.objects.create(item=shirt, default_price=14)
+4 -2
View File
@@ -217,7 +217,8 @@ class TimeRestrictionTest(TestCase):
self.assertFalse(result[0]['available']) self.assertFalse(result[0]['available'])
def test_variation_specific(self): def test_variation_specific(self):
self.item.properties.add(self.property) self.property.item = self.item
self.property.save()
r1 = TimeRestriction.objects.create( r1 = TimeRestriction.objects.create(
timeframe_from=now() - timedelta(days=5), timeframe_from=now() - timedelta(days=5),
@@ -242,7 +243,8 @@ class TimeRestrictionTest(TestCase):
self.assertTrue(v['available']) self.assertTrue(v['available'])
def test_variation_specifics(self): def test_variation_specifics(self):
self.item.properties.add(self.property) self.property.item = self.item
self.property.save()
r1 = TimeRestriction.objects.create( r1 = TimeRestriction.objects.create(
timeframe_from=now() - timedelta(days=5), timeframe_from=now() - timedelta(days=5),
+1 -2
View File
@@ -25,8 +25,7 @@ class CartTestMixin:
self.category = ItemCategory.objects.create(event=self.event, name="Everything", position=0) self.category = ItemCategory.objects.create(event=self.event, name="Everything", position=0)
self.quota_shirts = Quota.objects.create(event=self.event, name='Shirts', size=2) self.quota_shirts = Quota.objects.create(event=self.event, name='Shirts', size=2)
self.shirt = Item.objects.create(event=self.event, name='T-Shirt', category=self.category, default_price=12) self.shirt = Item.objects.create(event=self.event, name='T-Shirt', category=self.category, default_price=12)
prop1 = Property.objects.create(event=self.event, name="Color") prop1 = Property.objects.create(event=self.event, name="Color", item=self.shirt)
self.shirt.properties.add(prop1)
val1 = PropertyValue.objects.create(prop=prop1, value="Red", position=0) val1 = PropertyValue.objects.create(prop=prop1, value="Red", position=0)
val2 = PropertyValue.objects.create(prop=prop1, value="Black", position=1) val2 = PropertyValue.objects.create(prop=prop1, value="Black", position=1)
self.quota_shirts.items.add(self.shirt) self.quota_shirts.items.add(self.shirt)
+3 -6
View File
@@ -74,8 +74,7 @@ class ItemDisplayTest(EventTestMixin, BrowserTest):
c = ItemCategory.objects.create(event=self.event, name="Entry tickets", position=0) c = ItemCategory.objects.create(event=self.event, name="Entry tickets", position=0)
q = Quota.objects.create(event=self.event, name='Quota', size=2) q = Quota.objects.create(event=self.event, name='Quota', size=2)
item = Item.objects.create(event=self.event, name='Early-bird ticket', category=c, default_price=0) item = Item.objects.create(event=self.event, name='Early-bird ticket', category=c, default_price=0)
prop1 = Property.objects.create(event=self.event, name="Color") prop1 = Property.objects.create(event=self.event, name="Color", item=item)
item.properties.add(prop1)
PropertyValue.objects.create(prop=prop1, value="Red") PropertyValue.objects.create(prop=prop1, value="Red")
PropertyValue.objects.create(prop=prop1, value="Black") PropertyValue.objects.create(prop=prop1, value="Black")
q.items.add(item) q.items.add(item)
@@ -87,8 +86,7 @@ class ItemDisplayTest(EventTestMixin, BrowserTest):
c = ItemCategory.objects.create(event=self.event, name="Entry tickets", position=0) c = ItemCategory.objects.create(event=self.event, name="Entry tickets", position=0)
q = Quota.objects.create(event=self.event, name='Quota', size=2) q = Quota.objects.create(event=self.event, name='Quota', size=2)
item = Item.objects.create(event=self.event, name='Early-bird ticket', category=c, default_price=0) item = Item.objects.create(event=self.event, name='Early-bird ticket', category=c, default_price=0)
prop1 = Property.objects.create(event=self.event, name="Color") prop1 = Property.objects.create(event=self.event, name="Color", item=item)
item.properties.add(prop1)
val1 = PropertyValue.objects.create(prop=prop1, value="Red") val1 = PropertyValue.objects.create(prop=prop1, value="Red")
PropertyValue.objects.create(prop=prop1, value="Black") PropertyValue.objects.create(prop=prop1, value="Black")
q.items.add(item) q.items.add(item)
@@ -110,8 +108,7 @@ class ItemDisplayTest(EventTestMixin, BrowserTest):
c = ItemCategory.objects.create(event=self.event, name="Entry tickets", position=0) c = ItemCategory.objects.create(event=self.event, name="Entry tickets", position=0)
q = Quota.objects.create(event=self.event, name='Quota', size=2) q = Quota.objects.create(event=self.event, name='Quota', size=2)
item = Item.objects.create(event=self.event, name='Early-bird ticket', category=c, default_price=12) item = Item.objects.create(event=self.event, name='Early-bird ticket', category=c, default_price=12)
prop1 = Property.objects.create(event=self.event, name="Color") prop1 = Property.objects.create(event=self.event, name="Color", item=item)
item.properties.add(prop1)
val1 = PropertyValue.objects.create(prop=prop1, value="Red", position=0) val1 = PropertyValue.objects.create(prop=prop1, value="Red", position=0)
val2 = PropertyValue.objects.create(prop=prop1, value="Black", position=1) val2 = PropertyValue.objects.create(prop=prop1, value="Black", position=1)
q.items.add(item) q.items.add(item)
+1 -1
View File
@@ -26,7 +26,7 @@ class OrdersTest(TestCase):
self.category = ItemCategory.objects.create(event=self.event, name="Everything", position=0) self.category = ItemCategory.objects.create(event=self.event, name="Everything", position=0)
self.quota_shirts = Quota.objects.create(event=self.event, name='Shirts', size=2) self.quota_shirts = Quota.objects.create(event=self.event, name='Shirts', size=2)
self.shirt = Item.objects.create(event=self.event, name='T-Shirt', category=self.category, default_price=12) self.shirt = Item.objects.create(event=self.event, name='T-Shirt', category=self.category, default_price=12)
prop1 = Property.objects.create(event=self.event, name="Color") prop1 = Property.objects.create(event=self.event, name="Color", item=self.shirt)
self.shirt.properties.add(prop1) self.shirt.properties.add(prop1)
val1 = PropertyValue.objects.create(prop=prop1, value="Red", position=0) val1 = PropertyValue.objects.create(prop=prop1, value="Red", position=0)
val2 = PropertyValue.objects.create(prop=prop1, value="Black", position=1) val2 = PropertyValue.objects.create(prop=prop1, value="Black", position=1)