mirror of
https://github.com/pretix/pretix.git
synced 2026-05-15 16:54:00 +00:00
Compare commits
8 Commits
api-paymen
...
fix-clone-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c808afc2e9 | ||
|
|
baf7e098b6 | ||
|
|
a78e2f3ceb | ||
|
|
2fdd9f991c | ||
|
|
111127c0fa | ||
|
|
f1a4b51604 | ||
|
|
c9d29bbca5 | ||
|
|
bfdf959fad |
@@ -117,7 +117,7 @@ dev = [
|
||||
"isort==8.0.*",
|
||||
"pep8-naming==0.15.*",
|
||||
"potypo",
|
||||
"pytest-asyncio>=1.3.0",
|
||||
"pytest-asyncio>=0.24",
|
||||
"pytest-cache",
|
||||
"pytest-cov",
|
||||
"pytest-django==4.*",
|
||||
|
||||
@@ -1658,7 +1658,6 @@ class PaymentViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
|
||||
count_waitinglist=False,
|
||||
force=request.data.get('force', False),
|
||||
send_mail=send_mail,
|
||||
ignore_date=True,
|
||||
)
|
||||
except Quota.QuotaExceededException:
|
||||
pass
|
||||
@@ -1694,8 +1693,7 @@ class PaymentViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
|
||||
auth=self.request.auth,
|
||||
count_waitinglist=False,
|
||||
send_mail=send_mail,
|
||||
force=force,
|
||||
ignore_date=True)
|
||||
force=force)
|
||||
except Quota.QuotaExceededException as e:
|
||||
return Response({'detail': str(e)}, status=status.HTTP_400_BAD_REQUEST)
|
||||
except PaymentException as e:
|
||||
|
||||
81
src/pretix/base/migrations/0299_fixup_eventmetaproperties.py
Normal file
81
src/pretix/base/migrations/0299_fixup_eventmetaproperties.py
Normal file
@@ -0,0 +1,81 @@
|
||||
# Generated by Django 5.2.12 on 2026-04-28 11:34
|
||||
import logging
|
||||
|
||||
from django.db import IntegrityError, migrations, transaction
|
||||
from django.db.models import Count, F
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def fix_cross_organizer_eventmetavalues(apps, schema_editor):
|
||||
EventMetaProperty = apps.get_model("pretixbase", "EventMetaProperty")
|
||||
EventMetaValue = apps.get_model("pretixbase", "EventMetaValue")
|
||||
|
||||
cross_org_values = EventMetaValue.objects.filter(event__organizer__pk__ne=F('property__organizer__pk'))
|
||||
for emv in cross_org_values:
|
||||
logger.info(f"Fixup cross-organizer value {emv.event.organizer.slug}/{emv.event.slug}\n before: {emv.property.name}({emv.property.id}@{emv.property.organizer.slug}) = {emv.value}")
|
||||
try:
|
||||
emv.property = emv.event.organizer.meta_properties.filter(name=emv.property.name).first()
|
||||
logger.info(f" found existing EventMetaProperty in {emv.event.organizer.slug}")
|
||||
if EventMetaValue.objects.filter(event=emv.event, property=emv.property).exists():
|
||||
logger.info(f" EventMetaValue with property in correct organizer already exists, deleting the cross-organizer one")
|
||||
emv.delete()
|
||||
continue
|
||||
except EventMetaProperty.DoesNotExist:
|
||||
meta_prop = emv.property
|
||||
meta_prop.pk = None
|
||||
meta_prop.organizer = emv.event.organizer
|
||||
meta_prop.save(force_insert=True)
|
||||
logger.info(f" created new EventMetaProperty")
|
||||
emv.property = meta_prop
|
||||
logger.info(f" after: {emv.property.name}({emv.property.id}@{emv.property.organizer.slug}) = {emv.value}")
|
||||
emv.save(update_fields=["property"])
|
||||
|
||||
|
||||
def make_eventmetaproperties_unique(apps, schema_editor):
|
||||
EventMetaProperty = apps.get_model("pretixbase", "EventMetaProperty")
|
||||
EventMetaValue = apps.get_model("pretixbase", "EventMetaValue")
|
||||
|
||||
duplicates = EventMetaProperty.objects.values('organizer', 'organizer__slug', 'name').annotate(count=Count('id')).filter(count__gt=1)
|
||||
for dup in duplicates:
|
||||
logger.info(f"Fixup duplicate property {dup['organizer__slug']} {dup['name']}")
|
||||
props = list(EventMetaProperty.objects.filter(organizer=dup['organizer'], name=dup['name']))
|
||||
|
||||
# TODO: any better idea than picking the property to keep more or less randomly (first in database order)?
|
||||
target = props[0]
|
||||
invalid = props[1:]
|
||||
|
||||
try:
|
||||
with transaction.atomic():
|
||||
affected = EventMetaValue.objects.filter(
|
||||
event__organizer=dup['organizer'], property__in=invalid
|
||||
).update(
|
||||
property=target
|
||||
)
|
||||
logger.info(f" Switching {affected} value(s) over to {target.name}({target.id}@{target.organizer.slug})")
|
||||
|
||||
except IntegrityError as e:
|
||||
logger.info(f" Failed to switch all value(s) over to {target.name}({target.id}@{target.organizer.slug})")
|
||||
logger.info(f" {e}")
|
||||
for prop in invalid:
|
||||
newname = f'{prop.name}_DUPLICATE_{prop.id}'
|
||||
logger.info(f" Renaming {prop.name}({prop.id}@{prop.organizer.slug}) to {newname}({prop.id}@{prop.organizer.slug})")
|
||||
prop.name = newname
|
||||
prop.save()
|
||||
|
||||
else:
|
||||
for prop in invalid:
|
||||
logger.info(f" Deleting {prop.name}({prop.id}@{prop.organizer.slug})")
|
||||
prop.delete()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("pretixbase", "0298_pluggable_permissions"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(fix_cross_organizer_eventmetavalues, migrations.RunPython.noop),
|
||||
migrations.RunPython(make_eventmetaproperties_unique, migrations.RunPython.noop),
|
||||
]
|
||||
@@ -0,0 +1,17 @@
|
||||
# Generated by Django 5.2.12 on 2026-04-28 11:34
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("pretixbase", "0299_fixup_eventmetaproperties"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterUniqueTogether(
|
||||
name="eventmetaproperty",
|
||||
unique_together={("organizer", "name")},
|
||||
),
|
||||
]
|
||||
@@ -715,12 +715,6 @@ class Event(EventMixin, LoggedModel):
|
||||
self.settings.name_scheme = 'given_family'
|
||||
self.settings.payment_banktransfer_invoice_immediately = True
|
||||
self.settings.low_availability_percentage = 10
|
||||
self.settings.mail_send_order_free_attendee = True
|
||||
self.settings.mail_send_order_placed_attendee = True
|
||||
self.settings.mail_send_order_paid_attendee = True
|
||||
self.settings.mail_send_order_approved_attendee = True
|
||||
self.settings.mail_send_order_approved_free_attendee = True
|
||||
self.settings.mail_text_download_reminder_attendee = True
|
||||
|
||||
@property
|
||||
def social_image(self):
|
||||
@@ -883,6 +877,8 @@ class Event(EventMixin, LoggedModel):
|
||||
ItemProgramTime, ItemVariationMetaValue, Question, Quota,
|
||||
)
|
||||
|
||||
is_cross_organizer = other.organizer_id != self.organizer_id
|
||||
|
||||
# Note: avoid self.set_active_plugins(), it causes trouble e.g. for the badges plugin.
|
||||
# Plugins can create data in installed() hook based on existing data of the event.
|
||||
# Calling set_active_plugins() results in defaults being created while actually data
|
||||
@@ -911,6 +907,15 @@ class Event(EventMixin, LoggedModel):
|
||||
for emv in EventMetaValue.objects.filter(event=other):
|
||||
emv.pk = None
|
||||
emv.event = self
|
||||
if is_cross_organizer:
|
||||
try:
|
||||
emv.property = self.organizer.meta_properties.get(name=emv.property.name)
|
||||
except EventMetaProperty.DoesNotExist:
|
||||
meta_prop = emv.property
|
||||
meta_prop.pk = None
|
||||
meta_prop.organizer = self.organizer
|
||||
meta_prop.save(force_insert=True)
|
||||
emv.property = meta_prop
|
||||
emv.save(force_insert=True)
|
||||
|
||||
for fl in EventFooterLink.objects.filter(event=other):
|
||||
@@ -964,13 +969,13 @@ class Event(EventMixin, LoggedModel):
|
||||
if i.tax_rule_id:
|
||||
i.tax_rule = tax_map[i.tax_rule_id]
|
||||
|
||||
if i.grant_membership_type and other.organizer_id != self.organizer_id:
|
||||
if i.grant_membership_type and is_cross_organizer:
|
||||
i.grant_membership_type = None
|
||||
|
||||
i.save() # no force_insert since i.picture.save could have already inserted
|
||||
i.log_action('pretix.object.cloned')
|
||||
|
||||
if require_membership_types and other.organizer_id == self.organizer_id:
|
||||
if require_membership_types and not is_cross_organizer:
|
||||
i.require_membership_types.set(require_membership_types)
|
||||
|
||||
if not i.all_sales_channels:
|
||||
@@ -985,7 +990,7 @@ class Event(EventMixin, LoggedModel):
|
||||
v._prefetched_objects_cache = {}
|
||||
v.save(force_insert=True)
|
||||
|
||||
if require_membership_types and other.organizer_id == self.organizer_id:
|
||||
if require_membership_types and not is_cross_organizer:
|
||||
v.require_membership_types.set(require_membership_types)
|
||||
if not v.all_sales_channels:
|
||||
v.limit_sales_channels.set(self.organizer.sales_channels.filter(identifier__in=[s.identifier for s in limit_sales_channels]))
|
||||
@@ -1836,6 +1841,7 @@ class EventMetaProperty(LoggedModel):
|
||||
|
||||
class Meta:
|
||||
ordering = ("position", "name",)
|
||||
unique_together = ('organizer', 'name')
|
||||
|
||||
@property
|
||||
def choice_keys(self):
|
||||
@@ -1869,6 +1875,8 @@ class EventMetaValue(LoggedModel):
|
||||
self.event.cache.clear()
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if self.event and self.event.organizer != self.property.organizer:
|
||||
raise ValidationError(_("Property and event must belong to the same organizer."))
|
||||
super().save(*args, **kwargs)
|
||||
if self.event:
|
||||
self.event.cache.clear()
|
||||
|
||||
@@ -38,7 +38,6 @@ SOURCE_NAMES = {
|
||||
None: _('European Central Bank'), # backwards-compatibility
|
||||
'eu:ecb:eurofxref-daily': _('European Central Bank'),
|
||||
'cz:cnb:rate-fixing-daily': _('Czech National Bank'),
|
||||
'pl:nbp:table-a': _('National Bank of Poland'),
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +49,6 @@ def fetch_rates(sender, **kwargs):
|
||||
source_tasks = {
|
||||
'eu:ecb:eurofxref-daily': fetch_ecb_rates,
|
||||
'cz:cnb:rate-fixing-daily': fetch_cnb_cz_rates,
|
||||
'pl:nbp:table-a': fetch_nbp_pl_rates,
|
||||
}
|
||||
|
||||
for source_name, task in source_tasks.items():
|
||||
@@ -146,29 +144,3 @@ def fetch_cnb_cz_rates():
|
||||
rate=rate,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@app.task()
|
||||
def fetch_nbp_pl_rates():
|
||||
"""
|
||||
Fetches currency rates from the Polish National Bank.
|
||||
"""
|
||||
r = requests.get("https://api.nbp.pl/api/exchangerates/tables/A/", headers={
|
||||
"Accept": "application/json",
|
||||
})
|
||||
r.raise_for_status()
|
||||
data = r.json()[0]
|
||||
|
||||
source_date = datetime.strptime(data["effectiveDate"], "%Y-%m-%d").date()
|
||||
|
||||
for r in data["rates"]:
|
||||
rate = Decimal(r["mid"]).quantize(Decimal('0.000001'))
|
||||
ExchangeRate.objects.update_or_create(
|
||||
source='pl:nbp:table-a',
|
||||
source_currency=r["code"],
|
||||
other_currency='PLN',
|
||||
defaults=dict(
|
||||
source_date=source_date,
|
||||
rate=rate,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -205,19 +205,6 @@ def build_invoice(invoice: Invoice) -> Invoice:
|
||||
invoice.foreign_currency_rate = rate.rate.quantize(Decimal('0.0001'), ROUND_HALF_UP)
|
||||
invoice.foreign_currency_rate_date = rate.source_date
|
||||
invoice.foreign_currency_source = 'cz:cnb:rate-fixing-daily'
|
||||
elif invoice.event.settings.invoice_eu_currencies == 'PLN' and invoice.event.currency != 'PLN':
|
||||
invoice.foreign_currency_display = 'PLN'
|
||||
if settings.FETCH_ECB_RATES:
|
||||
rate = ExchangeRate.objects.filter(
|
||||
source='pl:nbp:table-a',
|
||||
source_currency=invoice.event.currency,
|
||||
other_currency=invoice.foreign_currency_display,
|
||||
source_date__gt=now().date() - timedelta(days=7)
|
||||
).first()
|
||||
if rate:
|
||||
invoice.foreign_currency_rate = rate.rate.quantize(Decimal('0.0001'), ROUND_HALF_UP)
|
||||
invoice.foreign_currency_rate_date = rate.source_date
|
||||
invoice.foreign_currency_source = 'pl:nbp:table-a'
|
||||
|
||||
except InvoiceAddress.DoesNotExist:
|
||||
ia = None
|
||||
|
||||
@@ -574,7 +574,6 @@ DEFAULTS = {
|
||||
('True', _('Based on European Central Bank daily rates, whenever the invoice recipient is in an EU '
|
||||
'country that uses a different currency.')),
|
||||
('CZK', _('Based on Czech National Bank daily rates, whenever the invoice amount is not in CZK.')),
|
||||
('PLN', _('Based on National Bank of Poland daily rates, whenever the invoice amount is not in PLN.')),
|
||||
),
|
||||
),
|
||||
'serializer_kwargs': dict(
|
||||
@@ -583,7 +582,6 @@ DEFAULTS = {
|
||||
('True', _('Based on European Central Bank daily rates, whenever the invoice recipient is in an EU '
|
||||
'country that uses a different currency.')),
|
||||
('CZK', _('Based on Czech National Bank daily rates, whenever the invoice amount is not in CZK.')),
|
||||
('PLN', _('Based on National Bank of Poland daily rates, whenever the invoice amount is not in PLN.')),
|
||||
),
|
||||
),
|
||||
},
|
||||
|
||||
@@ -28,7 +28,7 @@ from django.forms import formset_factory
|
||||
from django.forms.utils import ErrorDict
|
||||
from django.urls import reverse
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.translation import gettext_lazy as _, pgettext_lazy
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from i18nfield.forms import I18nInlineFormSet
|
||||
|
||||
from pretix.base.forms import I18nModelForm
|
||||
@@ -102,16 +102,6 @@ class SubEventBulkForm(SubEventForm):
|
||||
required=False,
|
||||
limit_choices=('date_from', 'date_to'),
|
||||
)
|
||||
skip_if_overlap = forms.BooleanField(
|
||||
label=pgettext_lazy('subevent', 'Skip dates that overlap with any existing date'),
|
||||
help_text=pgettext_lazy(
|
||||
'subevent',
|
||||
'This can be useful if all your dates happen in the same location and no repeated dates should '
|
||||
'be created in conflict with existing special events. This respects even inactive dates and works best if '
|
||||
'all dates have both a start and end time.'
|
||||
),
|
||||
required=False,
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.event = kwargs['event']
|
||||
|
||||
@@ -379,8 +379,6 @@
|
||||
<i class="fa fa-calendar"></i> {% trans "Add many time slots" %}</button>
|
||||
</p>
|
||||
</div>
|
||||
<hr />
|
||||
{% bootstrap_field form.skip_if_overlap layout="control" horizontal_label_class='sr-only' horizontal_field_class='col-md-12' %}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "General information" %}</legend>
|
||||
|
||||
@@ -917,35 +917,6 @@ class SubEventBulkCreate(SubEventEditorMixin, EventPermissionRequiredMixin, Asyn
|
||||
if len(subevents) > 100_000:
|
||||
raise ValidationError(_('Please do not create more than 100.000 dates at once.'))
|
||||
|
||||
if form.cleaned_data.get("skip_if_overlap") and subevents:
|
||||
def overlaps(a_from, a_to, b_from, b_to):
|
||||
if a_from == b_from:
|
||||
return True
|
||||
if a_from > b_from:
|
||||
# a starts after b
|
||||
# check if it starts before b ends
|
||||
return b_to and a_from < b_to
|
||||
# a starts before b
|
||||
# check if it ends before b starts
|
||||
return a_to and a_to > b_from
|
||||
|
||||
date_min = min(se.date_from for se in subevents)
|
||||
date_max = max(se.date_to or se.date_from for se in subevents)
|
||||
dates_existing = list(self.request.event.subevents.annotate(
|
||||
date_fromto=Coalesce('date_to', 'date_from'),
|
||||
).filter(
|
||||
date_from__lte=date_max,
|
||||
date_fromto__gte=date_min,
|
||||
).values('date_from', 'date_to'))
|
||||
subevents = [
|
||||
se for se in subevents if not any(
|
||||
overlaps(se.date_from, se.date_to, other['date_from'], other['date_to'])
|
||||
for other in dates_existing
|
||||
)
|
||||
]
|
||||
if not subevents:
|
||||
raise ValidationError(_('All dates would be skipped because they conflict with existing dates.'))
|
||||
|
||||
for i, se in enumerate(subevents):
|
||||
se.save(clear_cache=False)
|
||||
if i % 100 == 0:
|
||||
|
||||
@@ -316,7 +316,7 @@ def nav_context_list(request):
|
||||
page = 1
|
||||
|
||||
qs_events = request.user.get_events_with_any_permission(request).filter(
|
||||
Q(name__icontains=i18ncomp(query)) | Q(slug__icontains=query) | Q(domain__domainname__iexact=query)
|
||||
Q(name__icontains=i18ncomp(query)) | Q(slug__icontains=query)
|
||||
).annotate(
|
||||
min_from=Min('subevents__date_from'),
|
||||
max_from=Max('subevents__date_from'),
|
||||
@@ -331,7 +331,7 @@ def nav_context_list(request):
|
||||
else:
|
||||
qs_orga = Organizer.objects.filter(pk__in=request.user.teams.values_list('organizer', flat=True))
|
||||
if query:
|
||||
qs_orga = qs_orga.filter(Q(name__icontains=query) | Q(slug__icontains=query) | Q(domains__domainname__iexact=query))
|
||||
qs_orga = qs_orga.filter(Q(name__icontains=query) | Q(slug__icontains=query))
|
||||
qs_orga = qs_orga.annotate(
|
||||
n_events=Count("events")
|
||||
).order_by("-n_events")
|
||||
@@ -619,7 +619,7 @@ def checkinlist_select2(request, **kwargs):
|
||||
|
||||
qs = request.event.checkin_lists.select_related('subevent').filter(
|
||||
qf
|
||||
).order_by('subevent__date_from', 'name', 'pk')
|
||||
).order_by('name')
|
||||
|
||||
total = qs.count()
|
||||
pagesize = 20
|
||||
|
||||
@@ -114,10 +114,8 @@ class CartMixin:
|
||||
return cached_invoice_address(self.request)
|
||||
|
||||
def get_cart(self, answers=False, queryset=None, order=None, downloads=False, payments=None):
|
||||
from pretix.presale.views.cart import get_or_create_cart_id
|
||||
|
||||
if not get_or_create_cart_id(self.request, create=False) and not order:
|
||||
# The user has no cart, so we can save a lot of work
|
||||
if not self.request.session.session_key and not order:
|
||||
# The user has not even a session ID yet, so they can't have a cart and we can save a lot of work
|
||||
return {
|
||||
'positions': [],
|
||||
# Other keys are not used on non-checkout pages
|
||||
@@ -379,13 +377,9 @@ def cart_exists(request):
|
||||
from pretix.presale.views.cart import get_or_create_cart_id
|
||||
|
||||
if not hasattr(request, '_cart_cache'):
|
||||
cid = get_or_create_cart_id(request, create=False)
|
||||
if cid:
|
||||
return CartPosition.objects.filter(
|
||||
cart_id=cid, event=request.event
|
||||
).exists()
|
||||
else:
|
||||
return False
|
||||
return CartPosition.objects.filter(
|
||||
cart_id=get_or_create_cart_id(request), event=request.event
|
||||
).exists()
|
||||
return bool(request._cart_cache)
|
||||
|
||||
|
||||
|
||||
@@ -71,7 +71,6 @@ $(document).ajaxError(function (event, jqXHR, settings, thrownError) {
|
||||
});
|
||||
|
||||
var form_handlers = function (el) {
|
||||
el.trigger("rescan.areYouSure");
|
||||
el.find("[data-formset]").formset(
|
||||
{
|
||||
animateForms: true,
|
||||
|
||||
@@ -966,7 +966,6 @@ $table-bg-accent: rgba(128, 128, 128, 0.05);
|
||||
width: 80vw;
|
||||
max-width: 1080px;
|
||||
height: 80vh;
|
||||
max-height: 100dvh;
|
||||
}
|
||||
.pretix-widget-frame-inner iframe {
|
||||
width: 100% !important;
|
||||
|
||||
@@ -730,34 +730,6 @@ def test_payment_create_confirmed(token_client, organizer, event, order):
|
||||
assert len(djmail.outbox) == 0
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_payment_create_confirmed_after_expiry(token_client, organizer, event, order):
|
||||
djmail.outbox = []
|
||||
order.expires = now() - datetime.timedelta(days=2)
|
||||
order.save()
|
||||
event.settings.payment_term_last = (now() - datetime.timedelta(days=2)).strftime('%Y-%m-%d')
|
||||
|
||||
resp = token_client.post('/api/v1/organizers/{}/events/{}/orders/{}/payments/'.format(
|
||||
organizer.slug, event.slug, order.code
|
||||
), format='json', data={
|
||||
'provider': 'banktransfer',
|
||||
'state': 'confirmed',
|
||||
'amount': order.total,
|
||||
'send_email': False,
|
||||
'info': {
|
||||
'foo': 'bar'
|
||||
}
|
||||
})
|
||||
with scopes_disabled():
|
||||
p = order.payments.last()
|
||||
assert resp.status_code == 201
|
||||
assert p.state == OrderPayment.PAYMENT_STATE_CONFIRMED
|
||||
assert p.info_data == {'foo': 'bar'}
|
||||
order.refresh_from_db()
|
||||
assert order.status == Order.STATUS_PAID
|
||||
assert len(djmail.outbox) == 0
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_payment_create_pending(token_client, organizer, event, order):
|
||||
resp = token_client.post('/api/v1/organizers/{}/events/{}/orders/{}/payments/'.format(
|
||||
|
||||
@@ -238,6 +238,9 @@ def test_full_clone_cross_organizer_differences():
|
||||
sc1_c = organizer.sales_channels.create(identifier="c")
|
||||
sc2_a = organizer2.sales_channels.get(identifier="web")
|
||||
sc2_c = organizer2.sales_channels.create(identifier="c")
|
||||
o1_meta_prop_a = organizer.meta_properties.create(name="Prop to copy")
|
||||
o1_meta_prop_b = organizer.meta_properties.create(name="Prop to find")
|
||||
o2_meta_prop_b = organizer2.meta_properties.create(name="Prop to find")
|
||||
|
||||
event = Event.objects.create(
|
||||
organizer=organizer, name='Dummy', slug='dummy',
|
||||
@@ -262,6 +265,9 @@ def test_full_clone_cross_organizer_differences():
|
||||
event.settings.payment_giftcard__enabled = True
|
||||
event.settings.payment_giftcard__restrict_to_sales_channels = ['web', 'b', 'c']
|
||||
|
||||
event.meta_values.create(property=o1_meta_prop_a, value='a')
|
||||
event.meta_values.create(property=o1_meta_prop_b, value='b')
|
||||
|
||||
copied_event = Event.objects.create(
|
||||
organizer=organizer2, name='Dummy2', slug='dummy2',
|
||||
date_from=datetime.datetime(2022, 4, 15, 9, 0, 0, tzinfo=datetime.timezone.utc),
|
||||
@@ -284,3 +290,9 @@ def test_full_clone_cross_organizer_differences():
|
||||
|
||||
assert event.settings.get('payment_giftcard__restrict_to_sales_channels', as_type=list) == ['web', 'b', 'c']
|
||||
assert copied_event.settings.get('payment_giftcard__restrict_to_sales_channels', as_type=list) == ['web', 'c']
|
||||
|
||||
assert event.meta_values.get(property__name=o1_meta_prop_a.name).property.organizer == organizer
|
||||
assert copied_event.meta_values.get(property__name=o1_meta_prop_a.name).value == 'a'
|
||||
assert copied_event.meta_values.get(property__name=o1_meta_prop_a.name).property.organizer == organizer2
|
||||
assert copied_event.meta_values.get(property=o2_meta_prop_b).value == 'b'
|
||||
assert copied_event.meta_values.get(property=o2_meta_prop_b).property.organizer == organizer2
|
||||
|
||||
@@ -123,8 +123,6 @@ def env():
|
||||
ExchangeRate.objects.create(source_date=date.today(), source='eu:ecb:eurofxref-daily', source_currency='EUR', other_currency=currency, rate=rate)
|
||||
ExchangeRate.objects.create(source_date=date.today(), source='cz:cnb:rate-fixing-daily', source_currency='EUR',
|
||||
other_currency='CZK', rate=Decimal('25.0000'))
|
||||
ExchangeRate.objects.create(source_date=date.today(), source='pl:nbp:table-a', source_currency='EUR',
|
||||
other_currency='PLN', rate=Decimal('4.2355'))
|
||||
yield event, o
|
||||
|
||||
|
||||
@@ -349,23 +347,6 @@ def test_invoice_indirect_currency_conversion(env):
|
||||
assert inv.foreign_currency_source == 'eu:ecb:eurofxref-daily'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_invoice_pln_currency_conversion(env):
|
||||
event, order = env
|
||||
event.settings.invoice_eu_currencies = 'PLN'
|
||||
|
||||
event.settings.set('invoice_language', 'en')
|
||||
InvoiceAddress.objects.create(company='Acme Company', street='221B Baker Street', zipcode='12345', city='Warsaw',
|
||||
country=Country('PL'), vat_id='PL123456780', vat_id_validated=True, order=order,
|
||||
is_business=True)
|
||||
|
||||
inv = generate_invoice(order)
|
||||
assert inv.foreign_currency_display == "PLN"
|
||||
assert inv.foreign_currency_rate == Decimal("4.2355")
|
||||
assert inv.foreign_currency_rate_date == date.today()
|
||||
assert inv.foreign_currency_source == 'pl:nbp:table-a'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_invoice_czk_currency_conversion(env):
|
||||
event, order = env
|
||||
|
||||
@@ -747,92 +747,6 @@ class SubEventsTest(SoupTest):
|
||||
assert ses[1].date_from.isoformat() == "2018-04-12T11:29:31+00:00"
|
||||
assert ses[-1].date_from.isoformat() == "2019-03-28T12:29:31+00:00"
|
||||
|
||||
def test_create_bulk_skip_existing(self):
|
||||
with scopes_disabled():
|
||||
self.event1.subevents.all().delete()
|
||||
# SubEvent ends at rrule start time
|
||||
self.event1.subevents.create(
|
||||
date_from=datetime.datetime(2018, 4, 4, 9, 0, tzinfo=datetime.timezone.utc),
|
||||
date_to=datetime.datetime(2018, 4, 4, 10, 0, tzinfo=datetime.timezone.utc),
|
||||
)
|
||||
# SubEvent overlaps rrule start
|
||||
self.event1.subevents.create(
|
||||
date_from=datetime.datetime(2018, 4, 5, 9, 30, tzinfo=datetime.timezone.utc),
|
||||
date_to=datetime.datetime(2018, 4, 5, 10, 30, tzinfo=datetime.timezone.utc),
|
||||
)
|
||||
# SubEvent times are same as rrule
|
||||
self.event1.subevents.create(
|
||||
date_from=datetime.datetime(2018, 4, 6, 10, 0, tzinfo=datetime.timezone.utc),
|
||||
date_to=datetime.datetime(2018, 4, 6, 11, 0, tzinfo=datetime.timezone.utc),
|
||||
)
|
||||
# SubEvent starts at rrule end time
|
||||
self.event1.subevents.create(
|
||||
date_from=datetime.datetime(2018, 4, 7, 11, 0, tzinfo=datetime.timezone.utc),
|
||||
date_to=datetime.datetime(2018, 4, 7, 12, 0, tzinfo=datetime.timezone.utc),
|
||||
)
|
||||
# SubEvent overlaps entire rrule time
|
||||
self.event1.subevents.create(
|
||||
date_from=datetime.datetime(2018, 4, 8, 9, 0, tzinfo=datetime.timezone.utc),
|
||||
date_to=datetime.datetime(2018, 4, 8, 12, 0, tzinfo=datetime.timezone.utc),
|
||||
)
|
||||
# SubEvent has before rrule time and no end
|
||||
self.event1.subevents.create(
|
||||
date_from=datetime.datetime(2018, 4, 9, 9, 0, tzinfo=datetime.timezone.utc),
|
||||
)
|
||||
existing_events = list(self.event1.subevents.values_list('pk', flat=True))
|
||||
|
||||
self.event1.settings.timezone = 'Europe/Berlin'
|
||||
doc = self.post_doc('/control/event/ccc/30c3/subevents/bulk_add', {
|
||||
'rruleformset-TOTAL_FORMS': '1',
|
||||
'rruleformset-INITIAL_FORMS': '0',
|
||||
'rruleformset-MIN_NUM_FORMS': '0',
|
||||
'rruleformset-MAX_NUM_FORMS': '1000',
|
||||
'rruleformset-0-end': 'count',
|
||||
'rruleformset-0-count': '10',
|
||||
'rruleformset-0-interval': '1',
|
||||
'rruleformset-0-freq': 'weekly',
|
||||
'rruleformset-0-dtstart': '2018-04-03',
|
||||
'rruleformset-0-weekly_byweekday': ['MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU'],
|
||||
'rruleformset-0-yearly_same': 'on',
|
||||
'rruleformset-0-monthly_same': 'on',
|
||||
'timeformset-TOTAL_FORMS': '1',
|
||||
'timeformset-INITIAL_FORMS': '0',
|
||||
'timeformset-MIN_NUM_FORMS': '1',
|
||||
'timeformset-MAX_NUM_FORMS': '1000',
|
||||
'timeformset-0-time_from': '12:00:00',
|
||||
'timeformset-0-time_to': '13:00:00',
|
||||
'rruleformset-0-until': '2019-04-03',
|
||||
'skip_if_overlap': 'on',
|
||||
'name_0': 'Foo',
|
||||
'active': 'on',
|
||||
'frontpage_text_0': '',
|
||||
'quotas-TOTAL_FORMS': '1',
|
||||
'quotas-INITIAL_FORMS': '0',
|
||||
'quotas-MIN_NUM_FORMS': '0',
|
||||
'quotas-MAX_NUM_FORMS': '1000',
|
||||
'quotas-0-name': 'Q1',
|
||||
'quotas-0-size': '50',
|
||||
'quotas-0-itemvars': str(self.ticket.pk),
|
||||
'checkinlist_set-TOTAL_FORMS': '0',
|
||||
'checkinlist_set-INITIAL_FORMS': '0',
|
||||
'checkinlist_set-MIN_NUM_FORMS': '0',
|
||||
'checkinlist_set-MAX_NUM_FORMS': '1000',
|
||||
})
|
||||
assert doc.select(".alert-success")
|
||||
with scopes_disabled():
|
||||
ses = list(self.event1.subevents.exclude(pk__in=existing_events).order_by('date_from'))
|
||||
|
||||
assert len(ses) == 7
|
||||
assert [s.date_from.date().isoformat() for s in ses] == [
|
||||
'2018-04-03',
|
||||
'2018-04-04',
|
||||
'2018-04-07',
|
||||
'2018-04-09',
|
||||
'2018-04-10',
|
||||
'2018-04-11',
|
||||
'2018-04-12'
|
||||
]
|
||||
|
||||
def test_delete_bulk(self):
|
||||
self.subevent2.active = True
|
||||
self.subevent2.save()
|
||||
|
||||
@@ -33,7 +33,7 @@ from django.conf import settings
|
||||
from django.core import mail as djmail
|
||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
from django.core.signing import dumps
|
||||
from django.test import Client, TestCase, TransactionTestCase
|
||||
from django.test import TestCase, TransactionTestCase
|
||||
from django.utils.crypto import get_random_string
|
||||
from django.utils.timezone import now
|
||||
from django_countries.fields import Country
|
||||
@@ -4413,18 +4413,6 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
|
||||
assert len(djmail.outbox) == 1
|
||||
assert any(["Invoice_" in a[0] for a in djmail.outbox[0].attachments])
|
||||
|
||||
def test_checkout_empty_session_valid_cart(self):
|
||||
client = Client()
|
||||
with scopes_disabled():
|
||||
api_cid = "{}@api".format(get_random_string(48))
|
||||
CartPosition.objects.create(
|
||||
event=self.event, cart_id=api_cid, item=self.ticket,
|
||||
price=23, expires=now() + timedelta(minutes=10)
|
||||
)
|
||||
|
||||
response = client.get('/%s/%s/w/1234567890abcdef/checkout/questions/' % (self.orga.slug, self.event.slug), query_params={"take_cart_id": api_cid})
|
||||
assert '€23.00' in response.content.decode()
|
||||
|
||||
|
||||
class CheckoutTransactionTestCase(BaseCheckoutTestCase, TransactionTestCase):
|
||||
def test_order_confirmation_mail_invoice_sent_somewhere_else(self):
|
||||
|
||||
Reference in New Issue
Block a user