mirror of
https://github.com/pretix/pretix.git
synced 2026-03-15 14:32:27 +00:00
Compare commits
6 Commits
fix-progra
...
programtim
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc17725a86 | ||
|
|
815e31d9a0 | ||
|
|
ed618f2f32 | ||
|
|
a900e11ce0 | ||
|
|
112d5da792 | ||
|
|
ceb2e13d27 |
@@ -222,7 +222,7 @@ class ItemBundleSerializer(serializers.ModelSerializer):
|
|||||||
class ItemProgramTimeSerializer(serializers.ModelSerializer):
|
class ItemProgramTimeSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ItemProgramTime
|
model = ItemProgramTime
|
||||||
fields = ('id', 'start', 'end')
|
fields = ('id', 'start', 'end', 'location')
|
||||||
|
|
||||||
def validate(self, data):
|
def validate(self, data):
|
||||||
data = super().validate(data)
|
data = super().validate(data)
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ class ItemViewSet(ConditionalListView, viewsets.ModelViewSet):
|
|||||||
'variations', 'addons', 'bundles', 'meta_values', 'meta_values__property',
|
'variations', 'addons', 'bundles', 'meta_values', 'meta_values__property',
|
||||||
'variations__meta_values', 'variations__meta_values__property',
|
'variations__meta_values', 'variations__meta_values__property',
|
||||||
'require_membership_types', 'variations__require_membership_types',
|
'require_membership_types', 'variations__require_membership_types',
|
||||||
'limit_sales_channels', 'variations__limit_sales_channels', 'program_times'
|
'limit_sales_channels', 'variations__limit_sales_channels',
|
||||||
).all()
|
).all()
|
||||||
|
|
||||||
def perform_create(self, serializer):
|
def perform_create(self, serializer):
|
||||||
|
|||||||
@@ -364,7 +364,7 @@ class OrderListExporter(MultiSheetListExporter):
|
|||||||
order.invoice_address.city,
|
order.invoice_address.city,
|
||||||
order.invoice_address.country if order.invoice_address.country else
|
order.invoice_address.country if order.invoice_address.country else
|
||||||
order.invoice_address.country_old,
|
order.invoice_address.country_old,
|
||||||
order.invoice_address.state,
|
order.invoice_address.state_for_address,
|
||||||
order.invoice_address.custom_field,
|
order.invoice_address.custom_field,
|
||||||
order.invoice_address.vat_id,
|
order.invoice_address.vat_id,
|
||||||
]
|
]
|
||||||
@@ -515,7 +515,7 @@ class OrderListExporter(MultiSheetListExporter):
|
|||||||
order.invoice_address.city,
|
order.invoice_address.city,
|
||||||
order.invoice_address.country if order.invoice_address.country else
|
order.invoice_address.country if order.invoice_address.country else
|
||||||
order.invoice_address.country_old,
|
order.invoice_address.country_old,
|
||||||
order.invoice_address.state,
|
order.invoice_address.state_for_address,
|
||||||
order.invoice_address.vat_id,
|
order.invoice_address.vat_id,
|
||||||
]
|
]
|
||||||
except InvoiceAddress.DoesNotExist:
|
except InvoiceAddress.DoesNotExist:
|
||||||
@@ -732,7 +732,7 @@ class OrderListExporter(MultiSheetListExporter):
|
|||||||
op.zipcode or '',
|
op.zipcode or '',
|
||||||
op.city or '',
|
op.city or '',
|
||||||
op.country if op.country else '',
|
op.country if op.country else '',
|
||||||
op.state or '',
|
op.state_for_address or '',
|
||||||
op.voucher.code if op.voucher else '',
|
op.voucher.code if op.voucher else '',
|
||||||
op.pseudonymization_id,
|
op.pseudonymization_id,
|
||||||
op.secret,
|
op.secret,
|
||||||
@@ -797,7 +797,7 @@ class OrderListExporter(MultiSheetListExporter):
|
|||||||
order.invoice_address.city,
|
order.invoice_address.city,
|
||||||
order.invoice_address.country if order.invoice_address.country else
|
order.invoice_address.country if order.invoice_address.country else
|
||||||
order.invoice_address.country_old,
|
order.invoice_address.country_old,
|
||||||
order.invoice_address.state,
|
order.invoice_address.state_for_address,
|
||||||
order.invoice_address.vat_id,
|
order.invoice_address.vat_id,
|
||||||
]
|
]
|
||||||
except InvoiceAddress.DoesNotExist:
|
except InvoiceAddress.DoesNotExist:
|
||||||
|
|||||||
19
src/pretix/base/migrations/0297_itemprogramtime_location.py
Normal file
19
src/pretix/base/migrations/0297_itemprogramtime_location.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 4.2.27 on 2026-01-21 12:06
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import i18nfield.fields
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("pretixbase", "0296_invoice_invoice_from_state"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="itemprogramtime",
|
||||||
|
name="location",
|
||||||
|
field=i18nfield.fields.I18nTextField(max_length=200, null=True),
|
||||||
|
)
|
||||||
|
]
|
||||||
@@ -349,7 +349,7 @@ class AttendeeProfile(models.Model):
|
|||||||
def state_name(self):
|
def state_name(self):
|
||||||
sd = pycountry.subdivisions.get(code='{}-{}'.format(self.country, self.state))
|
sd = pycountry.subdivisions.get(code='{}-{}'.format(self.country, self.state))
|
||||||
if sd:
|
if sd:
|
||||||
return sd.name
|
return _(sd.name)
|
||||||
return self.state
|
return self.state
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|||||||
@@ -594,10 +594,11 @@ class Item(LoggedModel):
|
|||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
verbose_name=_("Only show after sellout of"),
|
verbose_name=_("Only show after sellout of"),
|
||||||
help_text=_("If you select a product here, this product will only be shown when that product is "
|
help_text=_("If you select a product here, this product will only be shown when that product is "
|
||||||
"sold out. If combined with the option to hide sold-out products, this allows you to "
|
"no longer available. This will happen either because the other product has sold out or because "
|
||||||
"swap out products for more expensive ones once the cheaper option is sold out. There might "
|
"the time is outside of the sales window for the other product. If combined with the option "
|
||||||
"be a short period in which both products are visible while all tickets of the referenced "
|
"to hide sold-out products, this allows you to swap out products for more expensive ones once "
|
||||||
"product are reserved, but not yet sold.")
|
"the cheaper option is sold out. There might be a short period in which both products are visible "
|
||||||
|
"while all tickets of the referenced product are reserved, but not yet sold.")
|
||||||
)
|
)
|
||||||
hidden_if_item_available_mode = models.CharField(
|
hidden_if_item_available_mode = models.CharField(
|
||||||
choices=UNAVAIL_MODES,
|
choices=UNAVAIL_MODES,
|
||||||
@@ -2305,10 +2306,17 @@ class ItemProgramTime(models.Model):
|
|||||||
:type start: datetime
|
:type start: datetime
|
||||||
:param end: The date and time this program time ends
|
:param end: The date and time this program time ends
|
||||||
:type end: datetime
|
:type end: datetime
|
||||||
|
:param location: venue
|
||||||
|
:type location: str
|
||||||
"""
|
"""
|
||||||
item = models.ForeignKey('Item', related_name='program_times', on_delete=models.CASCADE)
|
item = models.ForeignKey('Item', related_name='program_times', on_delete=models.CASCADE)
|
||||||
start = models.DateTimeField(verbose_name=_("Start"))
|
start = models.DateTimeField(verbose_name=_("Start"))
|
||||||
end = models.DateTimeField(verbose_name=_("End"))
|
end = models.DateTimeField(verbose_name=_("End"))
|
||||||
|
location = I18nTextField(
|
||||||
|
null=True, blank=True,
|
||||||
|
max_length=200,
|
||||||
|
verbose_name=_("Location"),
|
||||||
|
)
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
if hasattr(self, 'item') and self.item and self.item.event.has_subevents:
|
if hasattr(self, 'item') and self.item and self.item.event.has_subevents:
|
||||||
|
|||||||
@@ -1675,7 +1675,7 @@ class AbstractPosition(RoundingCorrectionMixin, models.Model):
|
|||||||
def state_name(self):
|
def state_name(self):
|
||||||
sd = pycountry.subdivisions.get(code='{}-{}'.format(self.country, self.state))
|
sd = pycountry.subdivisions.get(code='{}-{}'.format(self.country, self.state))
|
||||||
if sd:
|
if sd:
|
||||||
return sd.name
|
return _(sd.name)
|
||||||
return self.state
|
return self.state
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -3480,7 +3480,7 @@ class InvoiceAddress(models.Model):
|
|||||||
def state_name(self):
|
def state_name(self):
|
||||||
sd = pycountry.subdivisions.get(code='{}-{}'.format(self.country, self.state))
|
sd = pycountry.subdivisions.get(code='{}-{}'.format(self.country, self.state))
|
||||||
if sd:
|
if sd:
|
||||||
return sd.name
|
return _(sd.name)
|
||||||
return self.state
|
return self.state
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|||||||
@@ -159,6 +159,7 @@ class WaitingListEntry(LoggedModel):
|
|||||||
if availability[1] is None or availability[1] < 1:
|
if availability[1] is None or availability[1] < 1:
|
||||||
raise WaitingListException(_('This product is currently not available.'))
|
raise WaitingListException(_('This product is currently not available.'))
|
||||||
|
|
||||||
|
event = self.event
|
||||||
ev = self.subevent or self.event
|
ev = self.subevent or self.event
|
||||||
if ev.seat_category_mappings.filter(product=self.item).exists():
|
if ev.seat_category_mappings.filter(product=self.item).exists():
|
||||||
# Generally, we advertise the waiting list to be based on quotas only. This makes it dangerous
|
# Generally, we advertise the waiting list to be based on quotas only. This makes it dangerous
|
||||||
@@ -191,6 +192,7 @@ class WaitingListEntry(LoggedModel):
|
|||||||
|
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
locked_wle = WaitingListEntry.objects.select_for_update(of=OF_SELF).get(pk=self.pk)
|
locked_wle = WaitingListEntry.objects.select_for_update(of=OF_SELF).get(pk=self.pk)
|
||||||
|
locked_wle.event = event
|
||||||
if locked_wle.voucher:
|
if locked_wle.voucher:
|
||||||
raise WaitingListException(_('A voucher has already been sent to this person.'))
|
raise WaitingListException(_('A voucher has already been sent to this person.'))
|
||||||
e = locked_wle.email
|
e = locked_wle.email
|
||||||
@@ -227,6 +229,7 @@ class WaitingListEntry(LoggedModel):
|
|||||||
locked_wle.save()
|
locked_wle.save()
|
||||||
|
|
||||||
self.refresh_from_db()
|
self.refresh_from_db()
|
||||||
|
self.event = event
|
||||||
|
|
||||||
with language(self.locale, self.event.settings.region):
|
with language(self.locale, self.event.settings.region):
|
||||||
self.send_mail(
|
self.send_mail(
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ def _info(cc):
|
|||||||
statelist = [s for s in pycountry.subdivisions.get(country_code=cc) if s.type in types]
|
statelist = [s for s in pycountry.subdivisions.get(country_code=cc) if s.type in types]
|
||||||
return {
|
return {
|
||||||
'data': [
|
'data': [
|
||||||
{'name': s.name, 'code': s.code[3:]}
|
{'name': gettext(s.name), 'code': s.code[3:]}
|
||||||
for s in sorted(statelist, key=lambda s: s.name)
|
for s in sorted(statelist, key=lambda s: s.name)
|
||||||
],
|
],
|
||||||
**info,
|
**info,
|
||||||
|
|||||||
@@ -1354,6 +1354,10 @@ class ItemProgramTimeForm(I18nModelForm):
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.fields['end'].widget.attrs['data-date-after'] = '#id_{prefix}-start_0'.format(prefix=self.prefix)
|
self.fields['end'].widget.attrs['data-date-after'] = '#id_{prefix}-start_0'.format(prefix=self.prefix)
|
||||||
|
self.fields['location'].widget.attrs['rows'] = '1'
|
||||||
|
self.fields['location'].widget.attrs['placeholder'] = _(
|
||||||
|
'Sample Conference Center, Heidelberg, Germany'
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ItemProgramTime
|
model = ItemProgramTime
|
||||||
@@ -1361,6 +1365,7 @@ class ItemProgramTimeForm(I18nModelForm):
|
|||||||
fields = [
|
fields = [
|
||||||
'start',
|
'start',
|
||||||
'end',
|
'end',
|
||||||
|
'location'
|
||||||
]
|
]
|
||||||
field_classes = {
|
field_classes = {
|
||||||
'start': forms.SplitDateTimeField,
|
'start': forms.SplitDateTimeField,
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
{% bootstrap_form_errors form %}
|
{% bootstrap_form_errors form %}
|
||||||
{% bootstrap_field form.start layout="control" %}
|
{% bootstrap_field form.start layout="control" %}
|
||||||
{% bootstrap_field form.end layout="control" %}
|
{% bootstrap_field form.end layout="control" %}
|
||||||
|
{% bootstrap_field form.location layout="control" %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@@ -59,6 +60,7 @@
|
|||||||
<div class="panel-body form-horizontal">
|
<div class="panel-body form-horizontal">
|
||||||
{% bootstrap_field formset.empty_form.start layout="control" %}
|
{% bootstrap_field formset.empty_form.start layout="control" %}
|
||||||
{% bootstrap_field formset.empty_form.end layout="control" %}
|
{% bootstrap_field formset.empty_form.end layout="control" %}
|
||||||
|
{% bootstrap_field formset.empty_form.location layout="control" %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endescapescript %}
|
{% endescapescript %}
|
||||||
|
|||||||
@@ -363,7 +363,7 @@ class Forgot(TemplateView):
|
|||||||
else:
|
else:
|
||||||
messages.info(request, _('If the address is registered to valid account, then we have sent you an email containing further instructions.'))
|
messages.info(request, _('If the address is registered to valid account, then we have sent you an email containing further instructions.'))
|
||||||
|
|
||||||
return redirect('control:auth.forgot')
|
return redirect('control:auth.forgot')
|
||||||
else:
|
else:
|
||||||
return self.get(request, *args, **kwargs)
|
return self.get(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ def get_private_icals(event, positions):
|
|||||||
# Actual ical organizer field is not useful since it will cause "your invitation was accepted" emails to the organizer
|
# Actual ical organizer field is not useful since it will cause "your invitation was accepted" emails to the organizer
|
||||||
descr.append(_('Organizer: {organizer}').format(organizer=event.organizer.name))
|
descr.append(_('Organizer: {organizer}').format(organizer=event.organizer.name))
|
||||||
description = '\n'.join(descr)
|
description = '\n'.join(descr)
|
||||||
location = None
|
location = ", ".join(l.strip() for l in str(pt.location).splitlines() if l.strip())
|
||||||
dtstart = pt.start.astimezone(tz)
|
dtstart = pt.start.astimezone(tz)
|
||||||
dtend = pt.end.astimezone(tz)
|
dtend = pt.end.astimezone(tz)
|
||||||
uid = 'pretix-{}-{}-{}-{}@{}'.format(
|
uid = 'pretix-{}-{}-{}-{}@{}'.format(
|
||||||
|
|||||||
@@ -519,7 +519,7 @@
|
|||||||
<dialog role="alertdialog" id="cart-extend-confirmation-dialog" class="inline-dialog" aria-labelledby="cart-deadline">
|
<dialog role="alertdialog" id="cart-extend-confirmation-dialog" class="inline-dialog" aria-labelledby="cart-deadline">
|
||||||
<form method="dialog">
|
<form method="dialog">
|
||||||
<p>
|
<p>
|
||||||
<button class="btn btn-success" autofocus value="OK">
|
<button class="btn btn-success" value="OK">
|
||||||
<span role="img" aria-label="{% trans "OK" %}.">
|
<span role="img" aria-label="{% trans "OK" %}.">
|
||||||
{% icon "check" %}
|
{% icon "check" %}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -311,7 +311,8 @@ def get_grouped_items(event, *, channel: SalesChannel, subevent=None, voucher=No
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
q = item.hidden_if_item_available.check_quotas(subevent=subevent, _cache=quota_cache, include_bundled=True)
|
q = item.hidden_if_item_available.check_quotas(subevent=subevent, _cache=quota_cache, include_bundled=True)
|
||||||
item._dependency_available = q[0] == Quota.AVAILABILITY_OK
|
time_available = item.hidden_if_item_available.is_available()
|
||||||
|
item._dependency_available = (q[0] == Quota.AVAILABILITY_OK) and time_available
|
||||||
if item._dependency_available and item.hidden_if_item_available_mode == Item.UNAVAIL_MODE_HIDDEN:
|
if item._dependency_available and item.hidden_if_item_available_mode == Item.UNAVAIL_MODE_HIDDEN:
|
||||||
item._remove = True
|
item._remove = True
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -50,7 +50,6 @@ from pretix.base.models import (
|
|||||||
QuestionOption, Quota,
|
QuestionOption, Quota,
|
||||||
)
|
)
|
||||||
from pretix.base.models.orders import OrderFee
|
from pretix.base.models.orders import OrderFee
|
||||||
from pretix.testutils.queries import assert_num_queries
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@@ -423,13 +422,6 @@ def test_item_list(token_client, organizer, event, team, item):
|
|||||||
assert [] == resp.data['results']
|
assert [] == resp.data['results']
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
|
||||||
def test_item_list_queries(token_client, organizer, event, team, item, item3):
|
|
||||||
with assert_num_queries(18):
|
|
||||||
resp = token_client.get('/api/v1/organizers/{}/events/{}/items/'.format(organizer.slug, event.slug))
|
|
||||||
assert resp.status_code == 200
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_item_detail(token_client, organizer, event, team, item):
|
def test_item_detail(token_client, organizer, event, team, item):
|
||||||
res = dict(TEST_ITEM_RES)
|
res = dict(TEST_ITEM_RES)
|
||||||
@@ -1972,6 +1964,13 @@ def program_time2(item, category):
|
|||||||
end=datetime(2017, 12, 30, 0, 0, 0, tzinfo=timezone.utc))
|
end=datetime(2017, 12, 30, 0, 0, 0, tzinfo=timezone.utc))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def program_time3(item, category):
|
||||||
|
return item.program_times.create(start=datetime(2017, 12, 30, 0, 0, 0, tzinfo=timezone.utc),
|
||||||
|
end=datetime(2017, 12, 31, 0, 0, 0, tzinfo=timezone.utc),
|
||||||
|
location='Testlocation')
|
||||||
|
|
||||||
|
|
||||||
TEST_PROGRAM_TIMES_RES = {
|
TEST_PROGRAM_TIMES_RES = {
|
||||||
0: {
|
0: {
|
||||||
"start": "2017-12-27T00:00:00Z",
|
"start": "2017-12-27T00:00:00Z",
|
||||||
@@ -1980,24 +1979,37 @@ TEST_PROGRAM_TIMES_RES = {
|
|||||||
1: {
|
1: {
|
||||||
"start": "2017-12-29T00:00:00Z",
|
"start": "2017-12-29T00:00:00Z",
|
||||||
"end": "2017-12-30T00:00:00Z",
|
"end": "2017-12-30T00:00:00Z",
|
||||||
|
},
|
||||||
|
2: {
|
||||||
|
"start": "2017-12-30T00:00:00Z",
|
||||||
|
"end": "2017-12-31T00:00:00Z",
|
||||||
|
"location": "Testlocation",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_program_times_list(token_client, organizer, event, item, program_time, program_time2):
|
def test_program_times_list(token_client, organizer, event, item, program_time, program_time2, program_time3):
|
||||||
res = dict(TEST_PROGRAM_TIMES_RES)
|
res = dict(TEST_PROGRAM_TIMES_RES)
|
||||||
res[0]["id"] = program_time.pk
|
res[0]["id"] = program_time.pk
|
||||||
res[1]["id"] = program_time2.pk
|
res[1]["id"] = program_time2.pk
|
||||||
|
res[2]["id"] = program_time3.pk
|
||||||
resp = token_client.get('/api/v1/organizers/{}/events/{}/items/{}/program_times/'.format(organizer.slug, event.slug,
|
resp = token_client.get('/api/v1/organizers/{}/events/{}/items/{}/program_times/'.format(organizer.slug, event.slug,
|
||||||
item.pk))
|
item.pk))
|
||||||
assert resp.status_code == 200
|
assert resp.status_code == 200
|
||||||
assert res[0]['start'] == resp.data['results'][0]['start']
|
assert res[0]['start'] == resp.data['results'][0]['start']
|
||||||
assert res[0]['end'] == resp.data['results'][0]['end']
|
assert res[0]['end'] == resp.data['results'][0]['end']
|
||||||
assert res[0]['id'] == resp.data['results'][0]['id']
|
assert res[0]['id'] == resp.data['results'][0]['id']
|
||||||
|
# assert res[0] == resp.data['results'][0]
|
||||||
assert res[1]['start'] == resp.data['results'][1]['start']
|
assert res[1]['start'] == resp.data['results'][1]['start']
|
||||||
assert res[1]['end'] == resp.data['results'][1]['end']
|
assert res[1]['end'] == resp.data['results'][1]['end']
|
||||||
assert res[1]['id'] == resp.data['results'][1]['id']
|
assert res[1]['id'] == resp.data['results'][1]['id']
|
||||||
|
# assert res[1] == resp.data['results'][1]
|
||||||
|
assert res[2]['start'] == resp.data['results'][2]['start']
|
||||||
|
assert res[2]['end'] == resp.data['results'][2]['end']
|
||||||
|
assert res[2]['location'] == resp.data['results'][2]['location']
|
||||||
|
assert res[2]['id'] == resp.data['results'][2]['id']
|
||||||
|
assert res[2] == resp.data['results'][2]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ from django.core.exceptions import ValidationError
|
|||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from django_scopes import scopes_disabled
|
from django_scopes import scopes_disabled
|
||||||
|
from freezegun import freeze_time
|
||||||
from tests.base import SoupTest
|
from tests.base import SoupTest
|
||||||
from tests.testdummy.signals import FoobarSalesChannel
|
from tests.testdummy.signals import FoobarSalesChannel
|
||||||
|
|
||||||
@@ -272,6 +273,48 @@ class ItemDisplayTest(EventTestMixin, SoupTest):
|
|||||||
resp = self.client.get('/%s/%s/' % (self.orga.slug, self.event.slug))
|
resp = self.client.get('/%s/%s/' % (self.orga.slug, self.event.slug))
|
||||||
self.assertNotIn("Early-bird", resp.rendered_content)
|
self.assertNotIn("Early-bird", resp.rendered_content)
|
||||||
|
|
||||||
|
def tiered_availability_by_date_and_quota(self, q1_size, q2_size, time_offset, expected_phase):
|
||||||
|
current_time = now()
|
||||||
|
|
||||||
|
with scopes_disabled():
|
||||||
|
q1 = Quota.objects.create(event=self.event, name='Phase 1', size=q1_size)
|
||||||
|
item1 = Item.objects.create(
|
||||||
|
event=self.event,
|
||||||
|
name='Phase 1',
|
||||||
|
default_price=0,
|
||||||
|
available_from=current_time,
|
||||||
|
available_until=current_time + datetime.timedelta(days=1),
|
||||||
|
available_from_mode=Item.UNAVAIL_MODE_HIDDEN,
|
||||||
|
available_until_mode=Item.UNAVAIL_MODE_HIDDEN,
|
||||||
|
hidden_if_item_available_mode=Item.UNAVAIL_MODE_HIDDEN,
|
||||||
|
)
|
||||||
|
q1.items.add(item1)
|
||||||
|
q2 = Quota.objects.create(event=self.event, name='Phase 2', size=q2_size)
|
||||||
|
item2 = Item.objects.create(
|
||||||
|
event=self.event,
|
||||||
|
name='Phase 2',
|
||||||
|
default_price=0,
|
||||||
|
available_from=current_time + datetime.timedelta(days=0),
|
||||||
|
available_until=current_time + datetime.timedelta(days=2),
|
||||||
|
available_from_mode=Item.UNAVAIL_MODE_HIDDEN,
|
||||||
|
available_until_mode=Item.UNAVAIL_MODE_HIDDEN,
|
||||||
|
hidden_if_item_available_mode=Item.UNAVAIL_MODE_HIDDEN,
|
||||||
|
hidden_if_item_available=item1
|
||||||
|
)
|
||||||
|
q2.items.add(item2)
|
||||||
|
with freeze_time(current_time + time_offset):
|
||||||
|
resp = self.client.get('/%s/%s/' % (self.orga.slug, self.event.slug))
|
||||||
|
self.assertIn(expected_phase, resp.rendered_content)
|
||||||
|
|
||||||
|
def test_tiered_availability_by_date_and_quota_phase1_available(self):
|
||||||
|
self.tiered_availability_by_date_and_quota(1, 1, datetime.timedelta(seconds=1), "Phase 1")
|
||||||
|
|
||||||
|
def test_tiered_availability_by_date_and_quota_phase1_sold_out(self):
|
||||||
|
self.tiered_availability_by_date_and_quota(0, 1, datetime.timedelta(seconds=1), "Phase 2")
|
||||||
|
|
||||||
|
def test_tiered_availability_by_date_and_quota_phase1_timed_out(self):
|
||||||
|
self.tiered_availability_by_date_and_quota(1, 1, datetime.timedelta(days=1, hours=1), "Phase 2")
|
||||||
|
|
||||||
def test_subevents_inactive_unknown(self):
|
def test_subevents_inactive_unknown(self):
|
||||||
self.event.has_subevents = True
|
self.event.has_subevents = True
|
||||||
self.event.save()
|
self.event.save()
|
||||||
|
|||||||
Reference in New Issue
Block a user