mirror of
https://github.com/pretix/pretix.git
synced 2026-05-07 15:34:02 +00:00
New implementation of sales channels (#4111)
Co-authored-by: Martin Gross <gross@rami.io>
This commit is contained in:
@@ -49,7 +49,6 @@ from django.views.generic import FormView, ListView, TemplateView
|
||||
from i18nfield.strings import LazyI18nString
|
||||
|
||||
from pretix.api.views.checkin import _redeem_process
|
||||
from pretix.base.channels import get_all_sales_channels
|
||||
from pretix.base.models import Checkin, Order, OrderPosition
|
||||
from pretix.base.models.checkin import CheckinList
|
||||
from pretix.base.services.checkin import (
|
||||
@@ -296,7 +295,9 @@ class CheckinListList(EventPermissionRequiredMixin, PaginationMixin, ListView):
|
||||
ordering = ('subevent__date_from', 'name', 'pk')
|
||||
|
||||
def get_queryset(self):
|
||||
qs = self.request.event.checkin_lists.select_related('subevent').prefetch_related("limit_products")
|
||||
qs = self.request.event.checkin_lists.select_related('subevent').prefetch_related(
|
||||
"limit_products", "auto_checkin_sales_channels"
|
||||
)
|
||||
|
||||
if self.filter_form.is_valid():
|
||||
qs = self.filter_form.filter_qs(qs)
|
||||
@@ -305,12 +306,10 @@ class CheckinListList(EventPermissionRequiredMixin, PaginationMixin, ListView):
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
clists = list(ctx['checkinlists'])
|
||||
sales_channels = get_all_sales_channels()
|
||||
|
||||
for cl in clists:
|
||||
if cl.subevent:
|
||||
cl.subevent.event = self.request.event # re-use same event object to make sure settings are cached
|
||||
cl.auto_checkin_sales_channels = [sales_channels[channel] for channel in cl.auto_checkin_sales_channels]
|
||||
ctx['checkinlists'] = clists
|
||||
|
||||
ctx['can_change_organizer_settings'] = self.request.user.has_organizer_permission(
|
||||
|
||||
@@ -43,7 +43,6 @@ from pretix.control.permissions import (
|
||||
)
|
||||
from pretix.helpers.models import modelcopy
|
||||
|
||||
from ...base.channels import get_all_sales_channels
|
||||
from ...helpers.compat import CompatDeleteView
|
||||
from . import CreateView, PaginationMixin, UpdateView
|
||||
|
||||
@@ -190,11 +189,14 @@ class DiscountList(PaginationMixin, ListView):
|
||||
template_name = 'pretixcontrol/items/discounts.html'
|
||||
|
||||
def get_queryset(self):
|
||||
return self.request.event.discounts.prefetch_related('condition_limit_products')
|
||||
return self.request.event.discounts.prefetch_related('condition_limit_products', 'limit_sales_channels')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
ctx['sales_channels'] = get_all_sales_channels()
|
||||
ctx['sales_channels'] = [
|
||||
c for c in self.request.organizer.sales_channels.all()
|
||||
if c.type_instance.discounts_supported
|
||||
]
|
||||
return ctx
|
||||
|
||||
|
||||
|
||||
@@ -71,7 +71,6 @@ from django.views.generic.detail import SingleObjectMixin
|
||||
from i18nfield.strings import LazyI18nString
|
||||
from i18nfield.utils import I18nJSONEncoder
|
||||
|
||||
from pretix.base.channels import get_all_sales_channels
|
||||
from pretix.base.email import get_available_placeholders
|
||||
from pretix.base.forms import PlaceholderValidator
|
||||
from pretix.base.models import Event, LogEntry, Order, TaxRule, Voucher
|
||||
@@ -559,7 +558,7 @@ class PaymentSettings(EventSettingsViewMixin, EventSettingsFormView):
|
||||
key=lambda s: s.verbose_name
|
||||
)
|
||||
|
||||
sales_channels = get_all_sales_channels()
|
||||
sales_channels = {s.identifier: s for s in self.request.organizer.sales_channels.all()}
|
||||
for p in context['providers']:
|
||||
p.show_enabled = p.is_enabled
|
||||
p.sales_channels = [sales_channels[channel] for channel in p.settings.get('_restrict_to_sales_channels', as_type=list, default=['web'])]
|
||||
@@ -1468,7 +1467,7 @@ class QuickSetupView(FormView):
|
||||
admission=True,
|
||||
personalized=True,
|
||||
position=i,
|
||||
sales_channels=list(get_all_sales_channels().keys())
|
||||
all_sales_channels=True,
|
||||
)
|
||||
item.log_action('pretix.event.item.added', user=self.request.user, data=dict(f.cleaned_data))
|
||||
if f.cleaned_data['quota'] or not form.cleaned_data['total_quota']:
|
||||
|
||||
@@ -84,7 +84,6 @@ from pretix.control.permissions import (
|
||||
from pretix.control.signals import item_forms, item_formsets
|
||||
from pretix.helpers.models import modelcopy
|
||||
|
||||
from ...base.channels import get_all_sales_channels
|
||||
from ...helpers.compat import CompatDeleteView
|
||||
from . import ChartContainingView, CreateView, PaginationMixin, UpdateView
|
||||
|
||||
@@ -106,14 +105,14 @@ class ItemList(ListView):
|
||||
event=self.request.event
|
||||
).annotate(
|
||||
var_count=Count('variations')
|
||||
).prefetch_related("category").order_by(
|
||||
).prefetch_related("category", "limit_sales_channels").order_by(
|
||||
F('category__position').asc(nulls_first=True),
|
||||
'category', 'position'
|
||||
)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
ctx['sales_channels'] = get_all_sales_channels()
|
||||
ctx['sales_channels'] = self.request.organizer.sales_channels.all()
|
||||
items_by_category = {cat: list(items) for cat, items in groupby(ctx['items'], lambda item: item.category)}
|
||||
ctx['cat_list'] = [(cat, items_by_category.get(cat, [])) for cat in [None, *self.request.event.categories.all()]]
|
||||
return ctx
|
||||
@@ -1503,7 +1502,7 @@ class ItemUpdateGeneral(ItemDetailMixin, EventPermissionRequiredMixin, MetaDataE
|
||||
"Your participants won't be able to buy the bundle unless you remove this "
|
||||
"item from it."))
|
||||
|
||||
ctx['sales_channels'] = get_all_sales_channels()
|
||||
ctx['sales_channels'] = self.request.organizer.sales_channels.all()
|
||||
return ctx
|
||||
|
||||
@cached_property
|
||||
@@ -1515,7 +1514,9 @@ class ItemUpdateGeneral(ItemDetailMixin, EventPermissionRequiredMixin, MetaDataE
|
||||
can_order=True, can_delete=True, extra=0
|
||||
)(
|
||||
self.request.POST if self.request.method == "POST" else None,
|
||||
queryset=ItemVariation.objects.filter(item=self.get_object()).prefetch_related('meta_values', 'require_membership_types'),
|
||||
queryset=ItemVariation.objects.filter(item=self.get_object()).prefetch_related(
|
||||
'meta_values', 'limit_sales_channels', 'require_membership_types'
|
||||
),
|
||||
event=self.request.event, prefix="variations"
|
||||
)),
|
||||
('addons', inlineformset_factory(
|
||||
|
||||
@@ -71,7 +71,6 @@ from django.views.generic import (
|
||||
)
|
||||
from i18nfield.strings import LazyI18nString
|
||||
|
||||
from pretix.base.channels import get_all_sales_channels
|
||||
from pretix.base.decimal import round_decimal
|
||||
from pretix.base.email import get_email_context
|
||||
from pretix.base.exporter import MultiSheetListExporter
|
||||
@@ -375,7 +374,7 @@ class OrderList(OrderSearchMixin, EventPermissionRequiredMixin, PaginationMixin,
|
||||
def get_queryset(self):
|
||||
qs = Order.objects.filter(
|
||||
event=self.request.event
|
||||
).select_related('invoice_address')
|
||||
).select_related('invoice_address').prefetch_related("sales_channel")
|
||||
|
||||
if self.filter_form.is_valid():
|
||||
qs = self.filter_form.filter_qs(qs)
|
||||
@@ -420,7 +419,6 @@ class OrderList(OrderSearchMixin, EventPermissionRequiredMixin, PaginationMixin,
|
||||
)
|
||||
}
|
||||
|
||||
scs = get_all_sales_channels()
|
||||
for o in ctx['orders']:
|
||||
if o.pk not in annotated:
|
||||
continue
|
||||
@@ -433,7 +431,6 @@ class OrderList(OrderSearchMixin, EventPermissionRequiredMixin, PaginationMixin,
|
||||
o.has_cancellation_request = annotated.get(o.pk)['has_cancellation_request']
|
||||
o.computed_payment_refund_sum = annotated.get(o.pk)['computed_payment_refund_sum']
|
||||
o.icnt = annotated.get(o.pk)['icnt']
|
||||
o.sales_channel_obj = scs[o.sales_channel]
|
||||
|
||||
if ctx['page_obj'].paginator.count < 1000:
|
||||
# Performance safeguard: Only count positions if the data set is small
|
||||
@@ -520,7 +517,6 @@ class OrderDetail(OrderView):
|
||||
ctx['display_locale'] = dict(settings.LANGUAGES)[self.object.locale or self.request.event.settings.locale]
|
||||
|
||||
ctx['overpaid'] = self.order.pending_sum * -1
|
||||
ctx['sales_channel'] = get_all_sales_channels().get(self.order.sales_channel)
|
||||
ctx['download_buttons'] = self.download_buttons
|
||||
ctx['payment_refund_sum'] = self.order.payment_refund_sum
|
||||
ctx['pending_sum'] = self.order.pending_sum
|
||||
|
||||
@@ -56,7 +56,7 @@ from django.forms import DecimalField
|
||||
from django.http import (
|
||||
Http404, HttpResponse, HttpResponseBadRequest, JsonResponse,
|
||||
)
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.urls import reverse
|
||||
from django.utils.formats import date_format
|
||||
from django.utils.functional import cached_property
|
||||
@@ -71,7 +71,7 @@ from django.views.generic import (
|
||||
from pretix.api.models import ApiCall, WebHook
|
||||
from pretix.api.webhooks import manually_retry_all_calls
|
||||
from pretix.base.auth import get_auth_backends
|
||||
from pretix.base.channels import get_all_sales_channels
|
||||
from pretix.base.channels import get_all_sales_channel_types
|
||||
from pretix.base.exporter import (
|
||||
MultiSheetListExporter, OrganizerLevelExportMixin,
|
||||
)
|
||||
@@ -87,7 +87,7 @@ from pretix.base.models.giftcards import (
|
||||
GiftCardAcceptance, GiftCardTransaction, gen_giftcard_secret,
|
||||
)
|
||||
from pretix.base.models.orders import CancellationRequest
|
||||
from pretix.base.models.organizer import TeamAPIToken
|
||||
from pretix.base.models.organizer import SalesChannel, TeamAPIToken
|
||||
from pretix.base.payment import PaymentException
|
||||
from pretix.base.services.export import multiexport, scheduled_organizer_export
|
||||
from pretix.base.services.mail import SendMailException, mail
|
||||
@@ -107,8 +107,8 @@ from pretix.control.forms.organizer import (
|
||||
MailSettingsForm, MembershipTypeForm, MembershipUpdateForm,
|
||||
OrganizerDeleteForm, OrganizerFooterLinkFormset, OrganizerForm,
|
||||
OrganizerSettingsForm, OrganizerUpdateForm, ReusableMediumCreateForm,
|
||||
ReusableMediumUpdateForm, SSOClientForm, SSOProviderForm, TeamForm,
|
||||
WebHookForm,
|
||||
ReusableMediumUpdateForm, SalesChannelForm, SSOClientForm, SSOProviderForm,
|
||||
TeamForm, WebHookForm,
|
||||
)
|
||||
from pretix.control.forms.rrule import RRuleForm
|
||||
from pretix.control.logdisplay import OVERVIEW_BANLIST
|
||||
@@ -2206,7 +2206,7 @@ def meta_property_move_down(request, organizer, property):
|
||||
|
||||
|
||||
@transaction.atomic
|
||||
@organizer_permission_required("can_change_items")
|
||||
@organizer_permission_required("can_change_organizer_settings")
|
||||
@require_http_methods(["POST"])
|
||||
def reorder_meta_properties(request, organizer):
|
||||
try:
|
||||
@@ -2643,7 +2643,7 @@ class CustomerDetailView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMi
|
||||
q |= Q(email__iexact=self.customer.email)
|
||||
qs = Order.objects.filter(
|
||||
q
|
||||
).select_related('event').order_by('-datetime', 'pk')
|
||||
).select_related('event').prefetch_related('sales_channel').order_by('-datetime', 'pk')
|
||||
return qs
|
||||
|
||||
@cached_property
|
||||
@@ -2720,7 +2720,6 @@ class CustomerDetailView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMi
|
||||
)
|
||||
}
|
||||
|
||||
scs = get_all_sales_channels()
|
||||
for o in ctx['orders']:
|
||||
if o.pk not in annotated:
|
||||
continue
|
||||
@@ -2733,7 +2732,6 @@ class CustomerDetailView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMi
|
||||
o.has_cancellation_request = annotated.get(o.pk)['has_cancellation_request']
|
||||
o.computed_payment_refund_sum = annotated.get(o.pk)['computed_payment_refund_sum']
|
||||
o.icnt = annotated.get(o.pk)['icnt']
|
||||
o.sales_channel_obj = scs[o.sales_channel]
|
||||
|
||||
ctx["lifetime_spending"] = (
|
||||
self.get_queryset()
|
||||
@@ -3040,3 +3038,242 @@ class ReusableMediumUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequ
|
||||
'organizer': self.request.organizer.slug,
|
||||
'pk': self.object.pk,
|
||||
})
|
||||
|
||||
|
||||
class ChannelListView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, ListView):
|
||||
model = SalesChannel
|
||||
template_name = 'pretixcontrol/organizers/channels.html'
|
||||
permission = 'can_change_organizer_settings'
|
||||
context_object_name = 'channels'
|
||||
|
||||
def get_queryset(self):
|
||||
return self.request.organizer.sales_channels.all()
|
||||
|
||||
|
||||
class ChannelEditorMixin:
|
||||
form_class = SalesChannelForm
|
||||
|
||||
def get_form_kwargs(self):
|
||||
return {
|
||||
**super().get_form_kwargs(),
|
||||
'event': self.request.organizer,
|
||||
}
|
||||
|
||||
|
||||
class ChannelCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, ChannelEditorMixin, CreateView):
|
||||
model = SalesChannel
|
||||
permission = 'can_change_organizer_settings'
|
||||
template_name = 'pretixcontrol/organizers/channel_add.html'
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
return SalesChannel()
|
||||
|
||||
@property
|
||||
def allowed_types(self):
|
||||
existing_types = set(self.request.organizer.sales_channels.values_list("type", flat=True))
|
||||
return {
|
||||
k: t for k, t in get_all_sales_channel_types().items()
|
||||
if t.multiple_allowed or t.identifier not in existing_types
|
||||
}
|
||||
|
||||
@cached_property
|
||||
def selected_type(self):
|
||||
try:
|
||||
return self.allowed_types[self.request.GET.get("type")]
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
if not self.selected_type:
|
||||
return render(request, "pretixcontrol/organizers/channel_add_choice.html", {
|
||||
"types": self.allowed_types.values()
|
||||
})
|
||||
return super().post(request, *args, **kwargs)
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
if not self.selected_type:
|
||||
return render(request, "pretixcontrol/organizers/channel_add_choice.html", {
|
||||
"types": self.allowed_types.values()
|
||||
})
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('control:organizer.channels', kwargs={
|
||||
'organizer': self.request.organizer.slug,
|
||||
})
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
ctx["type"] = self.selected_type
|
||||
if self.selected_type.multiple_allowed:
|
||||
ctx["identifier_prefix"] = self.selected_type.identifier + "."
|
||||
return ctx
|
||||
|
||||
def get_form_kwargs(self):
|
||||
return {
|
||||
**super().get_form_kwargs(),
|
||||
"type": self.selected_type,
|
||||
}
|
||||
|
||||
def form_valid(self, form):
|
||||
messages.success(self.request, _('The sales channel has been created.'))
|
||||
form.instance.organizer = self.request.organizer
|
||||
form.instance.type = self.selected_type.identifier
|
||||
form.instance.position = (self.request.organizer.sales_channels.aggregate(m=Max("position"))["m"] or 0) + 1
|
||||
ret = super().form_valid(form)
|
||||
form.instance.log_action('pretix.saleschannel.created', user=self.request.user, data={
|
||||
k: getattr(self.object, k) for k in form.changed_data
|
||||
})
|
||||
return ret
|
||||
|
||||
def form_invalid(self, form):
|
||||
messages.error(self.request, _('Your changes could not be saved.'))
|
||||
return super().form_invalid(form)
|
||||
|
||||
|
||||
class ChannelUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, ChannelEditorMixin, UpdateView):
|
||||
model = SalesChannel
|
||||
permission = 'can_change_organizer_settings'
|
||||
context_object_name = 'channel'
|
||||
template_name = 'pretixcontrol/organizers/channel_edit.html'
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
return get_object_or_404(SalesChannel, organizer=self.request.organizer, identifier=self.kwargs.get('channel'))
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('control:organizer.channels', kwargs={
|
||||
'organizer': self.request.organizer.slug,
|
||||
})
|
||||
|
||||
@cached_property
|
||||
def type(self):
|
||||
return get_all_sales_channel_types()[self.object.type]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
ctx["type"] = self.type
|
||||
return ctx
|
||||
|
||||
def get_form_kwargs(self):
|
||||
return {
|
||||
**super().get_form_kwargs(),
|
||||
"type": self.type,
|
||||
}
|
||||
|
||||
def form_valid(self, form):
|
||||
if form.has_changed() or self.formset.has_changed():
|
||||
self.object.log_action('pretix.saleschannel.changed', user=self.request.user, data={
|
||||
k: getattr(self.object, k)
|
||||
for k in form.changed_data
|
||||
})
|
||||
messages.success(self.request, _('Your changes have been saved.'))
|
||||
return super().form_valid(form)
|
||||
|
||||
def form_invalid(self, form):
|
||||
messages.error(self.request, _('Your changes could not be saved.'))
|
||||
return super().form_invalid(form)
|
||||
|
||||
|
||||
class ChannelDeleteView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, CompatDeleteView):
|
||||
model = SalesChannel
|
||||
template_name = 'pretixcontrol/organizers/channel_delete.html'
|
||||
permission = 'can_change_organizer_settings'
|
||||
context_object_name = 'channel'
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
return get_object_or_404(SalesChannel, organizer=self.request.organizer, identifier=self.kwargs.get('channel'))
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('control:organizer.channels', kwargs={
|
||||
'organizer': self.request.organizer.slug,
|
||||
})
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data()
|
||||
ctx["is_allowed"] = self.get_object().allow_delete
|
||||
return ctx
|
||||
|
||||
@transaction.atomic
|
||||
def delete(self, request, *args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
success_url = self.get_success_url()
|
||||
if not self.object.allow_delete():
|
||||
messages.error(self.request, _('This channel can not be deleted.'))
|
||||
return redirect(success_url)
|
||||
try:
|
||||
self.object.log_action('pretix.saleschannel.deleted', user=self.request.user)
|
||||
self.object.delete()
|
||||
messages.success(request, _('The selected sales channel has been deleted.'))
|
||||
except ProtectedError:
|
||||
messages.error(self.request, _('The channel could not be deleted as some constraints (e.g. data created by '
|
||||
'plug-ins) did not allow it.'))
|
||||
return redirect(success_url)
|
||||
|
||||
|
||||
def channel_move(request, channel, up=True):
|
||||
channel = get_object_or_404(request.organizer.sales_channels, identifier=channel)
|
||||
channels = list(request.organizer.sales_channels.order_by("position"))
|
||||
|
||||
index = channels.index(channel)
|
||||
if index != 0 and up:
|
||||
channels[index - 1], channels[index] = channels[index], channels[index - 1]
|
||||
elif index != len(channels) - 1 and not up:
|
||||
channels[index + 1], channels[index] = channels[index], channels[index + 1]
|
||||
|
||||
for i, prop in enumerate(channels):
|
||||
if prop.position != i:
|
||||
prop.position = i
|
||||
prop.save()
|
||||
prop.log_action(
|
||||
'pretix.saleschannel.reordered', user=request.user, data={
|
||||
'position': i,
|
||||
}
|
||||
)
|
||||
messages.success(request, _('The order of sales channels has been updated.'))
|
||||
|
||||
|
||||
@organizer_permission_required("can_change_organizer_settings")
|
||||
@require_http_methods(["POST"])
|
||||
def channel_move_up(request, organizer, channel):
|
||||
channel_move(request, channel, up=True)
|
||||
return redirect('control:organizer.channels',
|
||||
organizer=request.organizer.slug)
|
||||
|
||||
|
||||
@organizer_permission_required("can_change_organizer_settings")
|
||||
@require_http_methods(["POST"])
|
||||
def channel_move_down(request, organizer, channel):
|
||||
channel_move(request, channel, up=False)
|
||||
return redirect('control:organizer.channels',
|
||||
organizer=request.organizer.slug)
|
||||
|
||||
|
||||
@transaction.atomic
|
||||
@organizer_permission_required("can_change_organizer_settings")
|
||||
@require_http_methods(["POST"])
|
||||
def reorder_channels(request, organizer):
|
||||
try:
|
||||
ids = json.loads(request.body.decode('utf-8'))['ids']
|
||||
except (JSONDecodeError, KeyError, ValueError):
|
||||
return HttpResponseBadRequest("expected JSON: {ids:[]}")
|
||||
|
||||
input_channels = list(request.organizer.sales_channels.filter(id__in=[i for i in ids if i.isdigit()]))
|
||||
|
||||
if len(input_channels) != len(ids):
|
||||
raise Http404(_("Some of the provided object ids are invalid."))
|
||||
|
||||
if len(input_channels) != request.organizer.sales_channels.count():
|
||||
raise Http404(_("Not all objects have been selected."))
|
||||
|
||||
for c in input_channels:
|
||||
pos = ids.index(str(c.pk))
|
||||
if pos != c.position: # Save unneccessary UPDATE queries
|
||||
c.position = pos
|
||||
c.save(update_fields=['position'])
|
||||
c.log_action(
|
||||
'pretix.saleschannel.reordered', user=request.user, data={
|
||||
'position': pos,
|
||||
}
|
||||
)
|
||||
|
||||
return HttpResponse()
|
||||
|
||||
@@ -98,6 +98,7 @@ class BaseEditorView(EventPermissionRequiredMixin, TemplateView):
|
||||
from pretix.base.models import Order
|
||||
order = self.request.event.orders.create(status=Order.STATUS_PENDING, datetime=now(),
|
||||
email='sample@pretix.eu',
|
||||
sales_channel=self.request.event.organizer.sales_channels.get(identifier="web"),
|
||||
locale=self.request.event.settings.locale,
|
||||
expires=now(), code="PREVIEW1234", total=Decimal('119.00'))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user