class EventCreate(OrganizerPermissionRequiredMixin, CreateView): model = Event form_class = EventCreateForm template_name = 'pretixcontrol/events/create.html' context_object_name = 'event' permission = 'can_create_events' @cached_property def sform(self): return EventCreateSettingsForm( obj=Event(), prefix='settings', data=self.request.POST if self.request.method == 'POST' else None ) def post(self, request, *args, **kwargs): form = self.get_form() if form.is_valid() and self.sform.is_valid(): return self.form_valid(form) else: return self.form_invalid(form) def get_context_data(self, *args, **kwargs) -> dict: context = super().get_context_data(*args, **kwargs) context['sform'] = self.sform return context def dispatch(self, request, *args, **kwargs): self.object = Event() return super().dispatch(request, *args, **kwargs) def get_form_kwargs(self): kwargs = super().get_form_kwargs() kwargs['organizer'] = self.request.organizer return kwargs @transaction.atomic def form_valid(self, form): messages.success(self.request, _('The new event has been created.')) form.instance.organizer = self.request.organizer ret = super().form_valid(form) EventPermission.objects.create( event=form.instance, user=self.request.user, ) self.object = form.instance self.object.plugins = settings.PRETIX_PLUGINS_DEFAULT self.object.save() self.sform.obj = form.instance self.sform.save() form.instance.log_action('pretix.event.settings', user=self.request.user, data={ k: form.instance.settings.get(k) for k in self.sform.changed_data }) return ret def get_success_url(self) -> str: return reverse('control:event.settings', kwargs={ 'organizer': self.request.organizer.slug, 'event': self.object.slug, })
def test_presale_end_before_start(self): event = Event( organizer=self.organizer, name='Dummy', slug='dummy', presale_start=now(), presale_end=now() - timedelta(hours=1) ) with self.assertRaises(ValidationError) as context: event.clean() self.assertIn('presale_end', str(context.exception))
def test_slug_validation(self): event = Event( organizer=self.organizer, name='Download', slug='download', date_from=datetime.datetime(2013, 12, 26, tzinfo=datetime.timezone.utc) ) with self.assertRaises(ValidationError) as context: event.full_clean() self.assertIn('slug', str(context.exception))
def validate(self, data): data = super().validate(data) full_data = self.to_internal_value(self.to_representation(self.instance)) if self.instance else {} full_data.update(data) Event.clean_dates(data.get('date_from'), data.get('date_to')) Event.clean_presale(data.get('presale_start'), data.get('presale_end')) return data
def test_slug_validation(self): event = Event(organizer=self.orga, name='download', slug='download', date_from=datetime.datetime( 2013, 12, 26, tzinfo=datetime.timezone.utc), live=True) with self.assertRaises(ValidationError): if event.full_clean(): event.save() self.assertEqual(Event.objects.filter(name='download').count(), 0)
def validate(self, data): data = super().validate(data) event = self.context['request'].event full_data = self.to_internal_value(self.to_representation(self.instance)) if self.instance else {} full_data.update(data) Event.clean_dates(data.get('date_from'), data.get('date_to')) Event.clean_presale(data.get('presale_start'), data.get('presale_end')) SubEvent.clean_items(event, [item['item'] for item in full_data.get('subeventitem_set', [])]) SubEvent.clean_variations(event, [item['variation'] for item in full_data.get('subeventitemvariation_set', [])]) return data
def validate(self, data): data = super().validate(data) full_data = self.to_internal_value(self.to_representation(self.instance)) if self.instance else {} full_data.update(data) Event.clean_dates(data.get('date_from'), data.get('date_to')) Event.clean_presale(data.get('presale_start'), data.get('presale_end')) if full_data.get('has_subevents') and full_data.get('seating_plan'): raise ValidationError('Event series should not directly be assigned a seating plan.') return data
def test_slug_validation(self): event = Event( organizer=self.orga, name='download', slug='download', date_from=datetime.datetime(2013, 12, 26, tzinfo=datetime.timezone.utc), live=True ) with self.assertRaises(ValidationError): if event.full_clean(): event.save() self.assertEqual(Event.objects.filter(name='download').count(), 0)
def _get_event_queryset(self): query = Q(is_public=True) & Q(live=True) qs = self.request.organizer.events.using(settings.DATABASE_REPLICA).filter(query) qs = qs.annotate( min_from=Min('subevents__date_from'), min_to=Min('subevents__date_to'), max_from=Max('subevents__date_from'), max_to=Max('subevents__date_to'), max_fromto=Greatest(Max('subevents__date_to'), Max('subevents__date_from')), ) if "old" in self.request.GET: qs = qs.filter( Q(Q(has_subevents=False) & Q( Q(date_to__lt=now()) | Q(Q(date_to__isnull=True) & Q(date_from__lt=now())) )) | Q(Q(has_subevents=True) & Q( Q(min_to__lt=now()) | Q(min_from__lt=now())) ) ).annotate( order_to=Coalesce('max_fromto', 'max_to', 'max_from', 'date_to', 'date_from'), ).order_by('-order_to') else: qs = qs.filter( Q(Q(has_subevents=False) & Q( Q(date_to__gte=now()) | Q(Q(date_to__isnull=True) & Q(date_from__gte=now())) )) | Q(Q(has_subevents=True) & Q( Q(max_to__gte=now()) | Q(max_from__gte=now())) ) ).annotate( order_from=Coalesce('min_from', 'date_from'), ).order_by('order_from') qs = Event.annotated(filter_qs_by_attr(qs, self.request)) return qs
def assign_automatically(event: Event, user_id: int=None, subevent_id: int=None): if user_id: user = User.objects.get(id=user_id) else: user = None quota_cache = {} gone = set() qs = WaitingListEntry.objects.filter( event=event, voucher__isnull=True ).select_related('item', 'variation', 'subevent').prefetch_related( 'item__quotas', 'variation__quotas' ).order_by('-priority', 'created') if subevent_id and event.has_subevents: subevent = event.subevents.get(id=subevent_id) qs = qs.filter(subevent=subevent) sent = 0 with event.lock(): for wle in qs: if (wle.item, wle.variation, wle.subevent) in gone: continue ev = (wle.subevent or event) if not ev.presale_is_running or (wle.subevent and not wle.subevent.active): continue if wle.subevent and not wle.subevent.presale_is_running: continue if not wle.item.is_available(): gone.add((wle.item, wle.variation, wle.subevent)) continue quotas = (wle.variation.quotas.filter(subevent=wle.subevent) if wle.variation else wle.item.quotas.filter(subevent=wle.subevent)) availability = ( wle.variation.check_quotas(count_waitinglist=False, _cache=quota_cache, subevent=wle.subevent) if wle.variation else wle.item.check_quotas(count_waitinglist=False, _cache=quota_cache, subevent=wle.subevent) ) if availability[1] is None or availability[1] > 0: try: wle.send_voucher(quota_cache, user=user) sent += 1 except WaitingListException: # noqa continue # Reduce affected quotas in cache for q in quotas: quota_cache[q.pk] = ( quota_cache[q.pk][0] if quota_cache[q.pk][0] > 1 else 0, quota_cache[q.pk][1] - 1 if quota_cache[q.pk][1] is not None else sys.maxsize ) else: gone.add((wle.item, wle.variation, wle.subevent)) return sent
def _events_by_day(self, before, after): ebd = defaultdict(list) timezones = set() add_events_for_days( self.request, Event.annotated(self.request.organizer.events, 'web').using(settings.DATABASE_REPLICA).filter( sales_channels__contains=self.request. sales_channel.identifier), before, after, ebd, timezones) add_subevents_for_days( filter_qs_by_attr( SubEvent.annotated( SubEvent.objects.filter( event__organizer=self.request.organizer, event__is_public=True, event__live=True, event__sales_channels__contains=self.request. sales_channel.identifier).prefetch_related( 'event___settings_objects', 'event__organizer___settings_objects')), self.request).using(settings.DATABASE_REPLICA), before, after, ebd, timezones) self._multiple_timezones = len(timezones) > 1 return ebd
def _add_items_to_cart(event: Event, items: List[Tuple[int, Optional[int], int, Optional[str]]], cart_id: str = None, voucher: str = None) -> None: with event.lock(): _check_date(event) existing = CartPosition.objects.filter( Q(cart_id=cart_id) & Q(event=event)).count() if sum(i[2] for i in items) + existing > int( event.settings.max_items_per_order): # TODO: i18n plurals raise CartError(error_messages['max_items'], (event.settings.max_items_per_order, )) expiry = now() + timedelta( minutes=event.settings.get('reservation_time', as_type=int)) _extend_existing(event, cart_id, expiry) expired = _re_add_expired_positions(items, event, cart_id) if items: err = _add_new_items(event, items, cart_id, expiry) _delete_expired(expired) if err: raise CartError(err) elif not voucher: raise CartError(error_messages['empty']) if voucher: _add_voucher(event, voucher, expiry, cart_id)
def shred(event: Event, fileid: str, confirm_code: str) -> None: known_shredders = event.get_data_shredders() try: cf = CachedFile.objects.get(pk=fileid) except CachedFile.DoesNotExist: raise ShredError( _("The download file could no longer be found on the server, please try to start again." )) with ZipFile(cf.file.file, 'r') as zipfile: indexdata = json.loads(zipfile.read('index.json').decode()) if indexdata['organizer'] != event.organizer.slug or indexdata[ 'event'] != event.slug: raise ShredError(_("This file is from a different event.")) if indexdata['confirm_code'] != confirm_code: raise ShredError(_("The confirm code you entered was incorrect.")) if event.logentry_set.filter(datetime__gte=parse(indexdata['time'])): raise ShredError( _("Something happened in your event after the export, please try again." )) for s in indexdata['shredders']: shredder = known_shredders.get(s) if not shredder: continue shredder.shred_data() cf.file.delete(save=False) cf.delete()
def perform_order(event: Event, payment_provider: BasePaymentProvider, positions: list, user: User=None, email: str=None, locale: str=None): dt = now() try: with event.lock(): check_positions(event, dt, positions) order = place_order(event, user, email if user is None else None, positions, dt, payment_provider, locale=locale) mail( order.email, _('Your order: %(code)s') % {'code': order.code}, 'pretixpresale/email/order_placed.txt', { 'order': order, 'event': event, 'url': build_absolute_uri('presale:event.order', kwargs={ 'event': event.slug, 'organizer': event.organizer.slug, 'order': order.code, }) + '?order_secret=' + order.secret, 'payment': payment_provider.order_pending_mail_render(order) }, event, locale=order.locale ) return order except EventLock.LockTimeoutException: # Is raised when there are too many threads asking for event locks and we were # unable to get one raise OrderError(error_messages['busy'])
def set_initial_from_event(self, event: Event): banktransfer = event.get_payment_providers( cached=True)[BankTransfer.identifier] self.initial["account_holder"] = banktransfer.settings.get( "bank_details_sepa_name") self.initial["iban"] = banktransfer.settings.get( "bank_details_sepa_iban") self.initial["bic"] = banktransfer.settings.get( "bank_details_sepa_bic")
def _events_by_day(self, before, after): ebd = defaultdict(list) timezones = set() add_events_for_days(self.request, Event.annotated(self.request.organizer.events, 'web'), before, after, ebd, timezones) add_subevents_for_days(filter_qs_by_attr(SubEvent.annotated(SubEvent.objects.filter( event__organizer=self.request.organizer, event__is_public=True, event__live=True, ).prefetch_related( 'event___settings_objects', 'event__organizer___settings_objects' )), self.request), before, after, ebd, timezones) self._multiple_timezones = len(timezones) > 1 return ebd
def _events_by_day(self, before, after): ebd = defaultdict(list) timezones = set() add_events_for_days(self.request, Event.annotated(self.request.organizer.events, 'web').using(settings.DATABASE_REPLICA), before, after, ebd, timezones) add_subevents_for_days(filter_qs_by_attr(SubEvent.annotated(SubEvent.objects.filter( event__organizer=self.request.organizer, event__is_public=True, event__live=True, ).prefetch_related( 'event___settings_objects', 'event__organizer___settings_objects' )), self.request).using(settings.DATABASE_REPLICA), before, after, ebd, timezones) self._multiple_timezones = len(timezones) > 1 return ebd
def _add_items_to_cart(event: Event, items: List[dict], cart_id: str=None) -> None: with event.lock() as now_dt: _check_date(event, now_dt) existing = CartPosition.objects.filter(Q(cart_id=cart_id) & Q(event=event)).count() if sum(i['count'] for i in items) + existing > int(event.settings.max_items_per_order): # TODO: i18n plurals raise CartError(error_messages['max_items'], (event.settings.max_items_per_order,)) expiry = now_dt + timedelta(minutes=event.settings.get('reservation_time', as_type=int)) _extend_existing(event, cart_id, expiry, now_dt) expired = _re_add_expired_positions(items, event, cart_id, now_dt) if items: err = _add_new_items(event, items, cart_id, expiry, now_dt) _delete_expired(expired, now_dt) if err: raise CartError(err)
def export(event: Event, shredders: List[str], session_key=None) -> None: known_shredders = event.get_data_shredders() with NamedTemporaryFile() as rawfile: with ZipFile(rawfile, 'w') as zipfile: ccode = get_random_string(6) zipfile.writestr( 'CONFIRM_CODE.txt', ccode, ) zipfile.writestr( 'index.json', json.dumps( { 'instance': settings.SITE_URL, 'organizer': event.organizer.slug, 'event': event.slug, 'time': now().isoformat(), 'shredders': shredders, 'confirm_code': ccode }, indent=4)) for s in shredders: shredder = known_shredders.get(s) if not shredder: continue it = shredder.generate_files() if not it: continue for fname, ftype, content in it: zipfile.writestr(fname, content) rawfile.seek(0) cf = CachedFile() cf.date = now() cf.filename = event.slug + '.zip' cf.type = 'application/zip' cf.session_key = session_key cf.web_download = True cf.expires = now() + timedelta(hours=1) cf.save() cf.file.save(cachedfile_name(cf, cf.filename), rawfile) return cf.pk
def _remove_items_from_cart(event: Event, items: List[dict], cart_id: str) -> None: with event.lock(): for i in items: cw = Q(cart_id=cart_id) & Q(item_id=i['item']) & Q(event=event) if i['variation']: cw &= Q(variation_id=i['variation']) else: cw &= Q(variation__isnull=True) # Prefer to delete positions that have the same price as the one the user clicked on, after thet # prefer the most expensive ones. cnt = i['count'] if i['price']: correctprice = CartPosition.objects.filter(cw).filter(price=Decimal(i['price'].replace(",", ".")))[:cnt] for cp in correctprice: cp.delete() cnt -= len(correctprice) if cnt > 0: for cp in CartPosition.objects.filter(cw).order_by("-price")[:cnt]: cp.delete()
def _add_items_to_cart(event: Event, items: list, session: str=None): with event.lock(): _check_date(event) existing = CartPosition.objects.current.filter(Q(session=session) & Q(event=event)).count() if sum(i[2] for i in items) + existing > int(event.settings.max_items_per_order): # TODO: i18n plurals raise CartError(error_messages['max_items'] % event.settings.max_items_per_order) expiry = now() + timedelta(minutes=event.settings.get('reservation_time', as_type=int)) _extend_existing(event, session, expiry) expired = _re_add_expired_positions(items, event, session) if not items: raise CartError(error_messages['empty']) err = _add_items(event, items, session, expiry) _delete_expired(expired) if err: raise CartError(err)
def _perform_order(event: Event, payment_provider: BasePaymentProvider, position_ids: list, email: str, locale: str): event = Event.objects.current.get(identity=event) responses = register_payment_providers.send(event) pprov = None for receiver, response in responses: provider = response(event) if provider.identifier == payment_provider: pprov = provider if not pprov: raise OrderError(error_messages['internal']) dt = now() with event.lock(): positions = list(CartPosition.objects.current.filter( identity__in=position_ids).select_related('item', 'variation')) if len(position_ids) != len(positions): raise OrderError(error_messages['internal']) _check_positions(event, dt, positions) order = _create_order(event, email, positions, dt, pprov, locale=locale) mail( order.email, _('Your order: %(code)s') % {'code': order.code}, 'pretixpresale/email/order_placed.txt', { 'order': order, 'event': event, 'url': build_absolute_uri('presale:event.order', kwargs={ 'event': event.slug, 'organizer': event.organizer.slug, 'order': order.code, 'secret': order.secret }), 'payment': pprov.order_pending_mail_render(order) }, event, locale=order.locale ) return order.identity
def _add_items_to_cart(event: Event, items: list, session: str = None): with event.lock(): _check_date(event) existing = CartPosition.objects.current.filter( Q(session=session) & Q(event=event)).count() if sum(i[2] for i in items) + existing > int( event.settings.max_items_per_order): # TODO: i18n plurals raise CartError(error_messages['max_items'] % event.settings.max_items_per_order) expiry = now() + timedelta( minutes=event.settings.get('reservation_time', as_type=int)) _extend_existing(event, session, expiry) expired = _re_add_expired_positions(items, event, session) if not items: raise CartError(error_messages['empty']) err = _add_items(event, items, session, expiry) _delete_expired(expired) if err: raise CartError(err)
def _add_items_to_cart(event: Event, items: List[Tuple[int, Optional[int], int, Optional[str]]], cart_id: str=None, voucher: str=None) -> None: with event.lock(): _check_date(event) existing = CartPosition.objects.filter(Q(cart_id=cart_id) & Q(event=event)).count() if sum(i[2] for i in items) + existing > int(event.settings.max_items_per_order): # TODO: i18n plurals raise CartError(error_messages['max_items'], (event.settings.max_items_per_order,)) expiry = now() + timedelta(minutes=event.settings.get('reservation_time', as_type=int)) _extend_existing(event, cart_id, expiry) expired = _re_add_expired_positions(items, event, cart_id) if items: err = _add_new_items(event, items, cart_id, expiry) _delete_expired(expired) if err: raise CartError(err) elif not voucher: raise CartError(error_messages['empty']) if voucher: _add_voucher(event, voucher, expiry, cart_id)
def get_queryset(self): if isinstance(self.request.auth, (TeamAPIToken, Device)): qs = self.request.auth.get_events_with_any_permission() elif self.request.user.is_authenticated: qs = self.request.user.get_events_with_any_permission( self.request).filter(organizer=self.request.organizer) qs = filter_qs_by_attr(qs, self.request) if 'with_availability_for' in self.request.GET: qs = Event.annotated( qs, channel=self.request.GET.get('with_availability_for')) return qs.prefetch_related( 'organizer', 'meta_values', 'meta_values__property', 'item_meta_properties', Prefetch( 'seat_category_mappings', to_attr='_seat_category_mappings', queryset=SeatCategoryMapping.objects.filter(subevent=None)), )
def dispatch(self, request, *args, **kwargs): self.object = Event() return super().dispatch(request, *args, **kwargs)
def validate_slug(self, value): Event.clean_slug(self.context['request'].organizer, self.instance, value) return value
def validate_has_subevents(self, value): Event.clean_has_subevents(self.instance, value) return value
def _get_event_list(self, request, **kwargs): data = {} o = getattr(request, 'event', request.organizer) list_type = self.request.GET.get("style", o.settings.event_list_type) data['list_type'] = list_type cache_key = ':'.join([ 'widget.py', 'eventlist', request.organizer.slug, request.event.slug if hasattr(request, 'event') else '-', list_type, request.GET.get("year") or "-", request.GET.get("month") or "-", request.GET.get("old") or "-", get_language(), ]) cached_data = cache.get(cache_key) if cached_data: return self.response(cached_data) if list_type == "calendar": self._set_month_year() _, ndays = calendar.monthrange(self.year, self.month) data['date'] = date(self.year, self.month, 1) if hasattr(self.request, 'event'): tz = pytz.timezone(self.request.event.settings.timezone) else: tz = pytz.UTC before = datetime(self.year, self.month, 1, 0, 0, 0, tzinfo=tz) - timedelta(days=1) after = datetime(self.year, self.month, ndays, 0, 0, 0, tzinfo=tz) + timedelta(days=1) ebd = defaultdict(list) if hasattr(self.request, 'event'): add_subevents_for_days( self.request.event.subevents_annotated('web'), before, after, ebd, set(), self.request.event, kwargs.get('cart_namespace') ) else: timezones = set() add_events_for_days(self.request, Event.annotated(self.request.organizer.events, 'web'), before, after, ebd, timezones) add_subevents_for_days(filter_qs_by_attr(SubEvent.annotated(SubEvent.objects.filter( event__organizer=self.request.organizer, event__is_public=True, event__live=True, ).prefetch_related( 'event___settings_objects', 'event__organizer___settings_objects' )), self.request), before, after, ebd, timezones) data['weeks'] = weeks_for_template(ebd, self.year, self.month) for w in data['weeks']: for d in w: if not d: continue d['events'] = self._serialize_events(d['events'] or []) else: if hasattr(self.request, 'event'): evs = self.request.event.subevents_sorted( self.request.event.subevents_annotated(self.request.sales_channel) ) tz = pytz.timezone(request.event.settings.timezone) data['events'] = [ { 'name': str(ev.name), 'date_range': ev.get_date_range_display(tz) + ( (" " + ev.get_time_from_display(tz)) if ev.event.settings.show_times else "" ), 'availability': self._get_availability(ev, ev.event), 'event_url': build_absolute_uri(ev.event, 'presale:event.index'), 'subevent': ev.pk, } for ev in evs ] else: data['events'] = [] qs = self._get_event_queryset() for event in qs: tz = pytz.timezone(event.cache.get_or_set('timezone', lambda: event.settings.timezone)) if event.has_subevents: dr = daterange( event.min_from.astimezone(tz), (event.max_fromto or event.max_to or event.max_from).astimezone(tz) ) avail = {'color': 'none', 'text': ugettext('Event series')} else: dr = event.get_date_range_display(tz) + ( " " + event.get_time_from_display(tz) if event.settings.show_times else "" ) avail = self._get_availability(event, event) data['events'].append({ 'name': str(event.name), 'date_range': dr, 'availability': avail, 'event_url': build_absolute_uri(event, 'presale:event.index'), }) cache.set(cache_key, data, 30) # These pages are cached for a really short duration – this should make them pretty accurate, while still # providing some protection against burst traffic. return self.response(data)
def mail(email: str, subject: str, template: Union[str, LazyI18nString], context: Dict[str, Any]=None, event: Event=None, locale: str=None, order: Order=None, position: OrderPosition=None, headers: dict=None, sender: str=None, invoices: list=None, attach_tickets=False, auto_email=True, user=None, attach_ical=False): """ Sends out an email to a user. The mail will be sent synchronously or asynchronously depending on the installation. :param email: The email address of the recipient :param subject: The email subject. Should be localized to the recipients's locale or a lazy object that will be localized by being casted to a string. :param template: The filename of a template to be used. It will be rendered with the locale given in the locale argument and the context given in the next argument. Alternatively, you can pass a LazyI18nString and ``context`` will be used as the argument to a Python ``.format_map()`` call on the template. :param context: The context for rendering the template (see ``template`` parameter) :param event: The event this email is related to (optional). If set, this will be used to determine the sender, a possible prefix for the subject and the SMTP server that should be used to send this email. :param order: The order this email is related to (optional). If set, this will be used to include a link to the order below the email. :param order: The order position this email is related to (optional). If set, this will be used to include a link to the order position instead of the order below the email. :param headers: A dict of custom mail headers to add to the mail :param locale: The locale to be used while evaluating the subject and the template :param sender: Set the sender email address. If not set and ``event`` is set, the event's default will be used, otherwise the system default. :param invoices: A list of invoices to attach to this email. :param attach_tickets: Whether to attach tickets to this email, if they are available to download. :param attach_ical: Whether to attach relevant ``.ics`` files to this email :param auto_email: Whether this email is auto-generated :param user: The user this email is sent to :raises MailOrderException: on obvious, immediate failures. Not raising an exception does not necessarily mean that the email has been sent, just that it has been queued by the email backend. """ if email == INVALID_ADDRESS: return headers = headers or {} if auto_email: headers['X-Auto-Response-Suppress'] = 'OOF, NRN, AutoReply, RN' headers['Auto-Submitted'] = 'auto-generated' with language(locale): if isinstance(context, dict) and event: for k, v in event.meta_data.items(): context['meta_' + k] = v if isinstance(context, dict) and order: try: context.update({ 'invoice_name': order.invoice_address.name, 'invoice_company': order.invoice_address.company }) except InvoiceAddress.DoesNotExist: context.update({ 'invoice_name': '', 'invoice_company': '' }) renderer = ClassicMailRenderer(None) content_plain = body_plain = render_mail(template, context) subject = str(subject).format_map(TolerantDict(context)) sender = sender or (event.settings.get('mail_from') if event else settings.MAIL_FROM) if event: sender_name = event.settings.mail_from_name or str(event.name) sender = formataddr((sender_name, sender)) else: sender = formataddr((settings.PRETIX_INSTANCE_NAME, sender)) subject = str(subject) signature = "" bcc = [] if event: renderer = event.get_html_mail_renderer() if event.settings.mail_bcc: for bcc_mail in event.settings.mail_bcc.split(','): bcc.append(bcc_mail.strip()) if event.settings.mail_from == settings.DEFAULT_FROM_EMAIL and event.settings.contact_mail and not headers.get('Reply-To'): headers['Reply-To'] = event.settings.contact_mail prefix = event.settings.get('mail_prefix') if prefix and prefix.startswith('[') and prefix.endswith(']'): prefix = prefix[1:-1] if prefix: subject = "[%s] %s" % (prefix, subject) body_plain += "\r\n\r\n-- \r\n" signature = str(event.settings.get('mail_text_signature')) if signature: signature = signature.format(event=event.name) body_plain += signature body_plain += "\r\n\r\n-- \r\n" if order and order.testmode: subject = "[TESTMODE] " + subject if order and position: body_plain += _( "You are receiving this email because someone placed an order for {event} for you." ).format(event=event.name) body_plain += "\r\n" body_plain += _( "You can view your order details at the following URL:\n{orderurl}." ).replace("\n", "\r\n").format( event=event.name, orderurl=build_absolute_uri( order.event, 'presale:event.order.position', kwargs={ 'order': order.code, 'secret': position.web_secret, 'position': position.positionid, } ) ) elif order: body_plain += _( "You are receiving this email because you placed an order for {event}." ).format(event=event.name) body_plain += "\r\n" body_plain += _( "You can view your order details at the following URL:\n{orderurl}." ).replace("\n", "\r\n").format( event=event.name, orderurl=build_absolute_uri( order.event, 'presale:event.order.open', kwargs={ 'order': order.code, 'secret': order.secret, 'hash': order.email_confirm_hash() } ) ) body_plain += "\r\n" try: if 'position' in inspect.signature(renderer.render).parameters: body_html = renderer.render(content_plain, signature, str(subject), order, position) else: # Backwards compatibility warnings.warn('E-mail renderer called without position argument because position argument is not ' 'supported.', DeprecationWarning) body_html = renderer.render(content_plain, signature, str(subject), order) except: logger.exception('Could not render HTML body') body_html = None send_task = mail_send_task.si( to=[email], bcc=bcc, subject=subject, body=body_plain, html=body_html, sender=sender, event=event.id if event else None, headers=headers, invoices=[i.pk for i in invoices] if invoices and not position else [], order=order.pk if order else None, position=position.pk if position else None, attach_tickets=attach_tickets, attach_ical=attach_ical, user=user.pk if user else None ) if invoices: task_chain = [invoice_pdf_task.si(i.pk).on_error(send_task) for i in invoices if not i.file] else: task_chain = [] task_chain.append(send_task) chain(*task_chain).apply_async()
def order_overview(event: Event, subevent: SubEvent=None) -> Tuple[List[Tuple[ItemCategory, List[Item]]], Dict[str, Tuple[Decimal, Decimal]]]: items = event.items.all().select_related( 'category', # for re-grouping ).prefetch_related( 'variations' ).order_by('category__position', 'category_id', 'position', 'name') qs = OrderPosition.objects if subevent: qs = qs.filter(subevent=subevent) counters = qs.filter( order__event=event ).values( 'item', 'variation', 'order__status' ).annotate(cnt=Count('id'), price=Sum('price'), tax_value=Sum('tax_value')).order_by() states = { 'canceled': Order.STATUS_CANCELED, 'refunded': Order.STATUS_REFUNDED, 'paid': Order.STATUS_PAID, 'pending': Order.STATUS_PENDING, 'expired': Order.STATUS_EXPIRED, } num = {} for l, s in states.items(): num[l] = { (p['item'], p['variation']): (p['cnt'], p['price'], p['price'] - p['tax_value']) for p in counters if p['order__status'] == s } num['total'] = dictsum(num['pending'], num['paid']) for item in items: item.all_variations = list(item.variations.all()) item.has_variations = (len(item.all_variations) > 0) item.num = {} if item.has_variations: for var in item.all_variations: variid = var.id var.num = {} for l in states.keys(): var.num[l] = num[l].get((item.id, variid), (0, 0, 0)) var.num['total'] = num['total'].get((item.id, variid), (0, 0, 0)) for l in states.keys(): item.num[l] = tuplesum(var.num[l] for var in item.all_variations) item.num['total'] = tuplesum(var.num['total'] for var in item.all_variations) else: for l in states.keys(): item.num[l] = num[l].get((item.id, None), (0, 0, 0)) item.num['total'] = num['total'].get((item.id, None), (0, 0, 0)) nonecat = ItemCategory(name=_('Uncategorized')) # Regroup those by category items_by_category = sorted( [ # a group is a tuple of a category and a list of items (cat if cat is not None else nonecat, [i for i in items if i.category == cat]) for cat in set([i.category for i in items]) # insert categories into a set for uniqueness # a set is unsorted, so sort again by category ], key=lambda group: (group[0].position, group[0].id) if ( group[0] is not None and group[0].id is not None) else (0, 0) ) for c in items_by_category: c[0].num = {} for l in states.keys(): c[0].num[l] = tuplesum(item.num[l] for item in c[1]) c[0].num['total'] = tuplesum(item.num['total'] for item in c[1]) # Payment fees payment_cat_obj = DummyObject() payment_cat_obj.name = _('Fees') payment_items = [] if not subevent: counters = OrderFee.objects.filter( order__event=event ).values( 'fee_type', 'internal_type', 'order__status' ).annotate(cnt=Count('id'), value=Sum('value'), tax_value=Sum('tax_value')).order_by() for l, s in states.items(): num[l] = { (o['fee_type'], o['internal_type']): (o['cnt'], o['value'], o['value'] - o['tax_value']) for o in counters if o['order__status'] == s } num['total'] = dictsum(num['pending'], num['paid']) provider_names = { k: v.verbose_name for k, v in event.get_payment_providers().items() } names = dict(OrderFee.FEE_TYPES) for pprov, total in sorted(num['total'].items(), key=lambda i: i[0]): ppobj = DummyObject() if pprov[0] == OrderFee.FEE_TYPE_PAYMENT: ppobj.name = '{} - {}'.format(names[pprov[0]], provider_names.get(pprov[1], pprov[1])) else: name = pprov[1] for r, resp in order_fee_type_name.send(sender=event, fee_type=pprov[0], internal_type=pprov[1]): if resp: name = resp break ppobj.name = '{} - {}'.format(names[pprov[0]], name) ppobj.provider = pprov[1] ppobj.has_variations = False ppobj.num = {} for l in states.keys(): ppobj.num[l] = num[l].get(pprov, (0, 0, 0)) ppobj.num['total'] = total payment_items.append(ppobj) payment_cat_obj.num = {} for l in states.keys(): payment_cat_obj.num[l] = ( Dontsum(''), sum(i.num[l][1] for i in payment_items), sum(i.num[l][2] for i in payment_items) ) payment_cat_obj.num['total'] = ( Dontsum(''), sum(i.num['total'][1] for i in payment_items), sum(i.num['total'][2] for i in payment_items) ) payment_cat = (payment_cat_obj, payment_items) any_payment = any(payment_cat_obj.num[s][1] for s in states.keys()) if any_payment: items_by_category.append(payment_cat) total = { 'num': {'total': tuplesum(c.num['total'] for c, i in items_by_category)} } for l in states.keys(): total['num'][l] = tuplesum(c.num[l] for c, i in items_by_category) return items_by_category, total
def mail(email: str, subject: str, template: Union[str, LazyI18nString], context: Dict[str, Any]=None, event: Event=None, locale: str=None, order: Order=None, headers: dict=None, sender: str=None, invoices: list=None, attach_tickets=False): """ Sends out an email to a user. The mail will be sent synchronously or asynchronously depending on the installation. :param email: The email address of the recipient :param subject: The email subject. Should be localized to the recipients's locale or a lazy object that will be localized by being casted to a string. :param template: The filename of a template to be used. It will be rendered with the locale given in the locale argument and the context given in the next argument. Alternatively, you can pass a LazyI18nString and ``context`` will be used as the argument to a Python ``.format_map()`` call on the template. :param context: The context for rendering the template (see ``template`` parameter) :param event: The event this email is related to (optional). If set, this will be used to determine the sender, a possible prefix for the subject and the SMTP server that should be used to send this email. :param order: The order this email is related to (optional). If set, this will be used to include a link to the order below the email. :param headers: A dict of custom mail headers to add to the mail :param locale: The locale to be used while evaluating the subject and the template :param sender: Set the sender email address. If not set and ``event`` is set, the event's default will be used, otherwise the system default. :param invoices: A list of invoices to attach to this email. :param attach_tickets: Whether to attach tickets to this email, if they are available to download. :raises MailOrderException: on obvious, immediate failures. Not raising an exception does not necessarily mean that the email has been sent, just that it has been queued by the email backend. """ if email == INVALID_ADDRESS: return headers = headers or {} with language(locale): if isinstance(context, dict) and order: try: context.update({ 'invoice_name': order.invoice_address.name, 'invoice_company': order.invoice_address.company }) except InvoiceAddress.DoesNotExist: context.update({ 'invoice_name': '', 'invoice_company': '' }) renderer = ClassicMailRenderer(None) content_plain = body_plain = render_mail(template, context) subject = str(subject).format_map(context) sender = sender or (event.settings.get('mail_from') if event else settings.MAIL_FROM) if event: sender = formataddr((str(event.name), sender)) else: sender = formataddr((settings.PRETIX_INSTANCE_NAME, sender)) subject = str(subject) signature = "" bcc = [] if event: renderer = event.get_html_mail_renderer() if event.settings.mail_bcc: bcc.append(event.settings.mail_bcc) if event.settings.mail_from == settings.DEFAULT_FROM_EMAIL and event.settings.contact_mail and not headers.get('Reply-To'): headers['Reply-To'] = event.settings.contact_mail prefix = event.settings.get('mail_prefix') if prefix and prefix.startswith('[') and prefix.endswith(']'): prefix = prefix[1:-1] if prefix: subject = "[%s] %s" % (prefix, subject) body_plain += "\r\n\r\n-- \r\n" signature = str(event.settings.get('mail_text_signature')) if signature: signature = signature.format(event=event.name) body_plain += signature body_plain += "\r\n\r\n-- \r\n" if order: body_plain += _( "You are receiving this email because you placed an order for {event}." ).format(event=event.name) body_plain += "\r\n" body_plain += _( "You can view your order details at the following URL:\n{orderurl}." ).replace("\n", "\r\n").format( event=event.name, orderurl=build_absolute_uri( order.event, 'presale:event.order', kwargs={ 'order': order.code, 'secret': order.secret } ) ) body_plain += "\r\n" try: body_html = renderer.render(content_plain, signature, str(subject), order) except: logger.exception('Could not render HTML body') body_html = None send_task = mail_send_task.si( to=[email], bcc=bcc, subject=subject, body=body_plain, html=body_html, sender=sender, event=event.id if event else None, headers=headers, invoices=[i.pk for i in invoices] if invoices else [], order=order.pk if order else None, attach_tickets=attach_tickets ) if invoices: task_chain = [invoice_pdf_task.si(i.pk).on_error(send_task) for i in invoices if not i.file] else: task_chain = [] task_chain.append(send_task) chain(*task_chain).apply_async()
def import_orders(event: Event, fileid: str, settings: dict, locale: str, user) -> None: # TODO: quotacheck? cf = CachedFile.objects.get(id=fileid) user = User.objects.get(pk=user) with language(locale, event.settings.region): cols = get_all_columns(event) parsed = parse_csv(cf.file) orders = [] order = None data = [] # Run validation for i, record in enumerate(parsed): if not any(record.values()): continue values = {} for c in cols: val = c.resolve(settings, record) if isinstance(val, str): val = val.strip() try: values[c.identifier] = c.clean(val, values) except ValidationError as e: raise DataImportError( _('Error while importing value "{value}" for column "{column}" in line "{line}": {message}' ).format(value=val if val is not None else '', column=c.verbose_name, line=i + 1, message=e.message)) data.append(values) # Prepare model objects. Yes, this might consume lots of RAM, but allows us to make the actual SQL transaction # shorter. We'll see what works better in reality… for i, record in enumerate(data): try: if order is None or settings['orders'] == 'many': order = Order( event=event, testmode=settings['testmode'], ) order.meta_info = {} order._positions = [] order._address = InvoiceAddress() order._address.name_parts = { '_scheme': event.settings.name_scheme } orders.append(order) position = OrderPosition(positionid=len(order._positions) + 1) position.attendee_name_parts = { '_scheme': event.settings.name_scheme } position.meta_info = {} order._positions.append(position) position.assign_pseudonymization_id() for c in cols: c.assign(record.get(c.identifier), order, position, order._address) except ImportError as e: raise ImportError( _('Invalid data in row {row}: {message}').format( row=i, message=str(e))) # quota check? with event.lock(): with transaction.atomic(): save_transactions = [] for o in orders: o.total = sum([c.price for c in o._positions ]) # currently no support for fees if o.total == Decimal('0.00'): o.status = Order.STATUS_PAID o.save() OrderPayment.objects.create( local_id=1, order=o, amount=Decimal('0.00'), provider='free', info='{}', payment_date=now(), state=OrderPayment.PAYMENT_STATE_CONFIRMED) elif settings['status'] == 'paid': o.status = Order.STATUS_PAID o.save() OrderPayment.objects.create( local_id=1, order=o, amount=o.total, provider='manual', info='{}', payment_date=now(), state=OrderPayment.PAYMENT_STATE_CONFIRMED) else: o.status = Order.STATUS_PENDING o.save() for p in o._positions: p.order = o p.save() o._address.order = o o._address.save() for c in cols: c.save(o) o.log_action('pretix.event.order.placed', user=user, data={'source': 'import'}) save_transactions += o.create_transactions( is_new=True, fees=[], positions=o._positions, save=False) Transaction.objects.bulk_create(save_transactions) for o in orders: with language(o.locale, event.settings.region): order_placed.send(event, order=o) if o.status == Order.STATUS_PAID: order_paid.send(event, order=o) gen_invoice = invoice_qualified(o) and ( (event.settings.get('invoice_generate') == 'True') or (event.settings.get('invoice_generate') == 'paid' and o.status == Order.STATUS_PAID)) and not o.invoices.last() if gen_invoice: generate_invoice(o, trigger_pdf=True) cf.delete()
def cancel_event(self, event: Event, subevent: int, auto_refund: bool, keep_fee_fixed: str, keep_fee_per_ticket: str, keep_fee_percentage: str, keep_fees: list = None, manual_refund: bool = False, send: bool = False, send_subject: dict = None, send_message: dict = None, send_waitinglist: bool = False, send_waitinglist_subject: dict = {}, send_waitinglist_message: dict = {}, user: int = None, refund_as_giftcard: bool = False, giftcard_expires=None, giftcard_conditions=None, subevents_from: str = None, subevents_to: str = None): send_subject = LazyI18nString(send_subject) send_message = LazyI18nString(send_message) send_waitinglist_subject = LazyI18nString(send_waitinglist_subject) send_waitinglist_message = LazyI18nString(send_waitinglist_message) if user: user = User.objects.get(pk=user) s = OrderPosition.objects.filter( order=OuterRef('pk')).order_by().values('order').annotate( k=Count('id')).values('k') orders_to_cancel = event.orders.annotate( pcnt=Subquery(s, output_field=IntegerField())).filter( status__in=[ Order.STATUS_PAID, Order.STATUS_PENDING, Order.STATUS_EXPIRED ], pcnt__gt=0).all() if subevent or subevents_from: if subevent: subevents = event.subevents.filter(pk=subevent) subevent = subevents.first() subevent_ids = {subevent.pk} else: subevents = event.subevents.filter(date_from__gte=subevents_from, date_from__lt=subevents_to) subevent_ids = set(subevents.values_list('id', flat=True)) has_subevent = OrderPosition.objects.filter( order_id=OuterRef('pk')).filter(subevent__in=subevents) has_other_subevent = OrderPosition.objects.filter( order_id=OuterRef('pk')).exclude(subevent__in=subevents) orders_to_change = orders_to_cancel.annotate( has_subevent=Exists(has_subevent), has_other_subevent=Exists(has_other_subevent), ).filter(has_subevent=True, has_other_subevent=True) orders_to_cancel = orders_to_cancel.annotate( has_subevent=Exists(has_subevent), has_other_subevent=Exists(has_other_subevent), ).filter(has_subevent=True, has_other_subevent=False) for se in subevents: se.log_action( 'pretix.subevent.canceled', user=user, ) se.active = False se.save(update_fields=['active']) se.log_action('pretix.subevent.changed', user=user, data={ 'active': False, '_source': 'cancel_event' }) else: subevents = None subevent_ids = set() orders_to_change = event.orders.none() event.log_action( 'pretix.event.canceled', user=user, ) for i in event.items.filter(active=True): i.active = False i.save(update_fields=['active']) i.log_action('pretix.event.item.changed', user=user, data={ 'active': False, '_source': 'cancel_event' }) failed = 0 total = orders_to_cancel.count() + orders_to_change.count() qs_wl = event.waitinglistentries.filter( voucher__isnull=True).select_related('subevent') if subevents: qs_wl = qs_wl.filter(subevent__in=subevents) if send_waitinglist: total += qs_wl.count() counter = 0 self.update_state(state='PROGRESS', meta={'value': 0}) for o in orders_to_cancel.only('id', 'total').iterator(): try: fee = Decimal('0.00') fee_sum = Decimal('0.00') keep_fee_objects = [] if keep_fees: for f in o.fees.all(): if f.fee_type in keep_fees: fee += f.value keep_fee_objects.append(f) fee_sum += f.value if keep_fee_percentage: fee += Decimal(keep_fee_percentage) / Decimal('100.00') * ( o.total - fee_sum) if keep_fee_fixed: fee += Decimal(keep_fee_fixed) if keep_fee_per_ticket: for p in o.positions.all(): if p.addon_to_id is None: fee += min(p.price, Decimal(keep_fee_per_ticket)) fee = round_decimal(min(fee, o.payment_refund_sum), event.currency) _cancel_order(o.pk, user, send_mail=False, cancellation_fee=fee, keep_fees=keep_fee_objects) refund_amount = o.payment_refund_sum try: if auto_refund: _try_auto_refund(o.pk, manual_refund=manual_refund, allow_partial=True, source=OrderRefund.REFUND_SOURCE_ADMIN, refund_as_giftcard=refund_as_giftcard, giftcard_expires=giftcard_expires, giftcard_conditions=giftcard_conditions, comment=gettext('Event canceled')) finally: if send: _send_mail(o, send_subject, send_message, subevent, refund_amount, user, o.positions.all()) counter += 1 if not self.request.called_directly and counter % max( 10, total // 100) == 0: self.update_state( state='PROGRESS', meta={ 'value': round(counter / total * 100 if total else 0, 2) }) except LockTimeoutException: logger.exception("Could not cancel order") failed += 1 except OrderError: logger.exception("Could not cancel order") failed += 1 for o in orders_to_change.values_list('id', flat=True).iterator(): with transaction.atomic(): o = event.orders.select_for_update().get(pk=o) total = Decimal('0.00') fee = Decimal('0.00') positions = [] ocm = OrderChangeManager(o, user=user, notify=False) for p in o.positions.all(): if p.subevent_id in subevent_ids: total += p.price ocm.cancel(p) positions.append(p) if keep_fee_per_ticket: if p.addon_to_id is None: fee += min(p.price, Decimal(keep_fee_per_ticket)) if keep_fee_fixed: fee += Decimal(keep_fee_fixed) if keep_fee_percentage: fee += Decimal(keep_fee_percentage) / Decimal('100.00') * total fee = round_decimal(min(fee, o.payment_refund_sum), event.currency) if fee: f = OrderFee( fee_type=OrderFee.FEE_TYPE_CANCELLATION, value=fee, order=o, tax_rule=o.event.settings.tax_rate_default, ) f._calculate_tax() ocm.add_fee(f) ocm.commit() refund_amount = o.payment_refund_sum - o.total if auto_refund: _try_auto_refund(o.pk, manual_refund=manual_refund, allow_partial=True, source=OrderRefund.REFUND_SOURCE_ADMIN, refund_as_giftcard=refund_as_giftcard, giftcard_expires=giftcard_expires, giftcard_conditions=giftcard_conditions, comment=gettext('Event canceled')) if send: _send_mail(o, send_subject, send_message, subevent, refund_amount, user, positions) counter += 1 if not self.request.called_directly and counter % max( 10, total // 100) == 0: self.update_state( state='PROGRESS', meta={ 'value': round(counter / total * 100 if total else 0, 2) }) if send_waitinglist: for wle in qs_wl: _send_wle_mail(wle, send_waitinglist_subject, send_waitinglist_message, wle.subevent) counter += 1 if not self.request.called_directly and counter % max( 10, total // 100) == 0: self.update_state( state='PROGRESS', meta={ 'value': round(counter / total * 100 if total else 0, 2) }) return failed
def order_overview( event: Event, subevent: SubEvent = None ) -> Tuple[List[Tuple[ItemCategory, List[Item]]], Dict[str, Tuple[Decimal, Decimal]]]: items = event.items.all().select_related( 'category', # for re-grouping ).prefetch_related('variations').order_by('category__position', 'category_id', 'position', 'name') qs = OrderPosition.objects if subevent: qs = qs.filter(subevent=subevent) counters = qs.filter(order__event=event).values( 'item', 'variation', 'order__status').annotate(cnt=Count('id'), price=Sum('price'), tax_value=Sum('tax_value')).order_by() num_canceled = {(p['item'], p['variation']): (p['cnt'], p['price'], p['price'] - p['tax_value']) for p in counters if p['order__status'] == Order.STATUS_CANCELED} num_refunded = {(p['item'], p['variation']): (p['cnt'], p['price'], p['price'] - p['tax_value']) for p in counters if p['order__status'] == Order.STATUS_REFUNDED} num_paid = {(p['item'], p['variation']): (p['cnt'], p['price'], p['price'] - p['tax_value']) for p in counters if p['order__status'] == Order.STATUS_PAID} num_pending = {(p['item'], p['variation']): (p['cnt'], p['price'], p['price'] - p['tax_value']) for p in counters if p['order__status'] == Order.STATUS_PENDING} num_expired = {(p['item'], p['variation']): (p['cnt'], p['price'], p['price'] - p['tax_value']) for p in counters if p['order__status'] == Order.STATUS_EXPIRED} num_total = dictsum(num_pending, num_paid) for item in items: item.all_variations = list(item.variations.all()) item.has_variations = (len(item.all_variations) > 0) if item.has_variations: for var in item.all_variations: variid = var.id var.num_total = num_total.get((item.id, variid), (0, 0, 0)) var.num_pending = num_pending.get((item.id, variid), (0, 0, 0)) var.num_expired = num_expired.get((item.id, variid), (0, 0, 0)) var.num_canceled = num_canceled.get((item.id, variid), (0, 0, 0)) var.num_refunded = num_refunded.get((item.id, variid), (0, 0, 0)) var.num_paid = num_paid.get((item.id, variid), (0, 0, 0)) item.num_total = tuplesum(var.num_total for var in item.all_variations) item.num_pending = tuplesum(var.num_pending for var in item.all_variations) item.num_expired = tuplesum(var.num_expired for var in item.all_variations) item.num_canceled = tuplesum(var.num_canceled for var in item.all_variations) item.num_refunded = tuplesum(var.num_refunded for var in item.all_variations) item.num_paid = tuplesum(var.num_paid for var in item.all_variations) else: item.num_total = num_total.get((item.id, None), (0, 0, 0)) item.num_pending = num_pending.get((item.id, None), (0, 0, 0)) item.num_expired = num_expired.get((item.id, None), (0, 0, 0)) item.num_canceled = num_canceled.get((item.id, None), (0, 0, 0)) item.num_refunded = num_refunded.get((item.id, None), (0, 0, 0)) item.num_paid = num_paid.get((item.id, None), (0, 0, 0)) nonecat = ItemCategory(name=_('Uncategorized')) # Regroup those by category items_by_category = sorted( [ # a group is a tuple of a category and a list of items (cat if cat is not None else nonecat, [i for i in items if i.category == cat]) for cat in set([i.category for i in items]) # insert categories into a set for uniqueness # a set is unsorted, so sort again by category ], key=lambda group: (group[0].position, group[0].id) if (group[0] is not None and group[0].id is not None) else (0, 0)) for c in items_by_category: c[0].num_total = tuplesum(item.num_total for item in c[1]) c[0].num_pending = tuplesum(item.num_pending for item in c[1]) c[0].num_expired = tuplesum(item.num_expired for item in c[1]) c[0].num_canceled = tuplesum(item.num_canceled for item in c[1]) c[0].num_refunded = tuplesum(item.num_refunded for item in c[1]) c[0].num_paid = tuplesum(item.num_paid for item in c[1]) # Payment fees payment_cat_obj = DummyObject() payment_cat_obj.name = _('Fees') payment_items = [] if not subevent: counters = OrderFee.objects.filter(order__event=event).values( 'fee_type', 'internal_type', 'order__status').annotate(cnt=Count('id'), value=Sum('value'), tax_value=Sum('tax_value')).order_by() num_canceled = {(o['fee_type'], o['internal_type']): (o['cnt'], o['value'], o['value'] - o['tax_value']) for o in counters if o['order__status'] == Order.STATUS_CANCELED} num_refunded = {(o['fee_type'], o['internal_type']): (o['cnt'], o['value'], o['value'] - o['tax_value']) for o in counters if o['order__status'] == Order.STATUS_REFUNDED} num_pending = {(o['fee_type'], o['internal_type']): (o['cnt'], o['value'], o['value'] - o['tax_value']) for o in counters if o['order__status'] == Order.STATUS_PENDING} num_expired = {(o['fee_type'], o['internal_type']): (o['cnt'], o['value'], o['value'] - o['tax_value']) for o in counters if o['order__status'] == Order.STATUS_EXPIRED} num_paid = {(o['fee_type'], o['internal_type']): (o['cnt'], o['value'], o['value'] - o['tax_value']) for o in counters if o['order__status'] == Order.STATUS_PAID} num_total = dictsum(num_pending, num_paid) provider_names = { k: v.verbose_name for k, v in event.get_payment_providers().items() } names = dict(OrderFee.FEE_TYPES) for pprov, total in sorted(num_total.items(), key=lambda i: i[0]): ppobj = DummyObject() if pprov[0] == OrderFee.FEE_TYPE_PAYMENT: ppobj.name = '{} - {}'.format( names[OrderFee.FEE_TYPE_PAYMENT], provider_names.get(pprov[1], pprov[1])) else: ppobj.name = '{} - {}'.format(names[OrderFee.FEE_TYPE_PAYMENT], pprov[1]) ppobj.provider = pprov[1] ppobj.has_variations = False ppobj.num_total = total ppobj.num_canceled = num_canceled.get(pprov, (0, 0, 0)) ppobj.num_refunded = num_refunded.get(pprov, (0, 0, 0)) ppobj.num_expired = num_expired.get(pprov, (0, 0, 0)) ppobj.num_pending = num_pending.get(pprov, (0, 0, 0)) ppobj.num_paid = num_paid.get(pprov, (0, 0, 0)) payment_items.append(ppobj) payment_cat_obj.num_total = (Dontsum(''), sum(i.num_total[1] for i in payment_items), sum(i.num_total[2] for i in payment_items)) payment_cat_obj.num_canceled = (Dontsum(''), sum(i.num_canceled[1] for i in payment_items), sum(i.num_canceled[2] for i in payment_items)) payment_cat_obj.num_refunded = (Dontsum(''), sum(i.num_refunded[1] for i in payment_items), sum(i.num_refunded[2] for i in payment_items)) payment_cat_obj.num_expired = (Dontsum(''), sum(i.num_expired[1] for i in payment_items), sum(i.num_expired[2] for i in payment_items)) payment_cat_obj.num_pending = (Dontsum(''), sum(i.num_pending[1] for i in payment_items), sum(i.num_pending[2] for i in payment_items)) payment_cat_obj.num_paid = (Dontsum(''), sum(i.num_paid[1] for i in payment_items), sum(i.num_paid[2] for i in payment_items)) payment_cat = (payment_cat_obj, payment_items) items_by_category.append(payment_cat) total = { 'num_total': tuplesum(c.num_total for c, i in items_by_category), 'num_pending': tuplesum(c.num_pending for c, i in items_by_category), 'num_expired': tuplesum(c.num_expired for c, i in items_by_category), 'num_canceled': tuplesum(c.num_canceled for c, i in items_by_category), 'num_refunded': tuplesum(c.num_refunded for c, i in items_by_category), 'num_paid': tuplesum(c.num_paid for c, i in items_by_category) } return items_by_category, total
def _get_event_list(self, request, **kwargs): data = {} o = getattr(request, 'event', request.organizer) list_type = self.request.GET.get("style", o.settings.event_list_type) data['list_type'] = list_type if hasattr(self.request, 'event') and data['list_type'] not in ("calendar", "week"): if self.request.event.subevents.filter( date_from__gt=now()).count() > 50: if self.request.event.settings.event_list_type not in ( "calendar", "week"): self.request.event.settings.event_list_type = "calendar" data['list_type'] = list_type = 'calendar' if hasattr(self.request, 'event'): data['name'] = str(request.event.name) data['frontpage_text'] = str( rich_text(request.event.settings.frontpage_text, safelinks=False)) cache_key = ':'.join([ 'widget.py', 'eventlist', request.organizer.slug, request.event.slug if hasattr(request, 'event') else '-', list_type, request.GET.urlencode(), get_language(), ]) cached_data = cache.get(cache_key) if cached_data: return self.response(cached_data) if list_type == "calendar": self._set_month_year() _, ndays = calendar.monthrange(self.year, self.month) data['date'] = date(self.year, self.month, 1) if hasattr(self.request, 'event'): tz = pytz.timezone(self.request.event.settings.timezone) else: tz = pytz.UTC before = datetime(self.year, self.month, 1, 0, 0, 0, tzinfo=tz) - timedelta(days=1) after = datetime(self.year, self.month, ndays, 0, 0, 0, tzinfo=tz) + timedelta(days=1) ebd = defaultdict(list) if hasattr(self.request, 'event'): add_subevents_for_days( filter_qs_by_attr( self.request.event.subevents_annotated('web').filter( event__sales_channels__contains=self.request. sales_channel.identifier), self.request), before, after, ebd, set(), self.request.event, kwargs.get('cart_namespace')) else: timezones = set() add_events_for_days( self.request, filter_qs_by_attr( Event.annotated( self.request.organizer.events, 'web').filter(sales_channels__contains=self. request.sales_channel.identifier), self.request), before, after, ebd, timezones) add_subevents_for_days( filter_qs_by_attr( SubEvent.annotated( SubEvent.objects.filter( event__organizer=self.request.organizer, event__is_public=True, event__live=True, event__sales_channels__contains=self.request. sales_channel.identifier).prefetch_related( 'event___settings_objects', 'event__organizer___settings_objects')), self.request), before, after, ebd, timezones) data['weeks'] = weeks_for_template(ebd, self.year, self.month) for w in data['weeks']: for d in w: if not d: continue d['events'] = self._serialize_events(d['events'] or []) elif list_type == "week": self._set_week_year() if hasattr(self.request, 'event'): tz = pytz.timezone(self.request.event.settings.timezone) else: tz = pytz.UTC week = isoweek.Week(self.year, self.week) data['week'] = [self.year, self.week] before = datetime(week.monday().year, week.monday().month, week.monday().day, 0, 0, 0, tzinfo=tz) - timedelta(days=1) after = datetime(week.sunday().year, week.sunday().month, week.sunday().day, 0, 0, 0, tzinfo=tz) + timedelta(days=1) ebd = defaultdict(list) if hasattr(self.request, 'event'): add_subevents_for_days( filter_qs_by_attr( self.request.event.subevents_annotated('web'), self.request), before, after, ebd, set(), self.request.event, kwargs.get('cart_namespace')) else: timezones = set() add_events_for_days( self.request, filter_qs_by_attr( Event.annotated(self.request.organizer.events, 'web'), self.request), before, after, ebd, timezones) add_subevents_for_days( filter_qs_by_attr( SubEvent.annotated( SubEvent.objects.filter( event__organizer=self.request.organizer, event__is_public=True, event__live=True, ).prefetch_related( 'event___settings_objects', 'event__organizer___settings_objects')), self.request), before, after, ebd, timezones) data['days'] = days_for_template(ebd, week) for d in data['days']: d['events'] = self._serialize_events(d['events'] or []) else: if hasattr(self.request, 'event'): evs = self.request.event.subevents_sorted( filter_qs_by_attr( self.request.event.subevents_annotated( self.request.sales_channel.identifier), self.request)) tz = pytz.timezone(request.event.settings.timezone) data['events'] = [{ 'name': str(ev.name), 'location': str(ev.location), 'date_range': self._get_date_range(ev, ev.event, tz), 'availability': self._get_availability(ev, ev.event), 'event_url': build_absolute_uri(ev.event, 'presale:event.index'), 'subevent': ev.pk, } for ev in evs] else: data['events'] = [] qs = self._get_event_queryset() for event in qs: tz = pytz.timezone( event.cache.get_or_set( 'timezone', lambda: event.settings.timezone)) if event.has_subevents: dr = daterange(event.min_from.astimezone(tz), (event.max_fromto or event.max_to or event.max_from).astimezone(tz)) avail = { 'color': 'none', 'text': gettext('Event series') } else: dr = self._get_date_range(event, event, tz) avail = self._get_availability(event, event) data['events'].append({ 'name': str(event.name), 'location': str(event.location), 'date_range': dr, 'availability': avail, 'event_url': build_absolute_uri(event, 'presale:event.index'), }) cache.set(cache_key, data, 30) # These pages are cached for a really short duration – this should make them pretty accurate, while still # providing some protection against burst traffic. return self.response(data)
def order_overview( event: Event, subevent: SubEvent=None, date_filter='', date_from=None, date_until=None, fees=False, admission_only=False ) -> Tuple[List[Tuple[ItemCategory, List[Item]]], Dict[str, Tuple[Decimal, Decimal]]]: items = event.items.all().select_related( 'category', # for re-grouping ).prefetch_related( 'variations' ).order_by('category__position', 'category_id', 'position', 'name') qs = OrderPosition.all if subevent: qs = qs.filter(subevent=subevent) if admission_only: qs = qs.filter(item__admission=True) items = items.filter(admission=True) if date_from and isinstance(date_from, date): date_from = make_aware(datetime.combine( date_from, time(hour=0, minute=0, second=0, microsecond=0) ), event.timezone) if date_until and isinstance(date_until, date): date_until = make_aware(datetime.combine( date_until + timedelta(days=1), time(hour=0, minute=0, second=0, microsecond=0) ), event.timezone) if date_filter == 'order_date': if date_from: qs = qs.filter(order__datetime__gte=date_from) if date_until: qs = qs.filter(order__datetime__lt=date_until) elif date_filter == 'last_payment_date': p_date = OrderPayment.objects.filter( order=OuterRef('order'), state__in=[OrderPayment.PAYMENT_STATE_CONFIRMED, OrderPayment.PAYMENT_STATE_REFUNDED], payment_date__isnull=False ).values('order').annotate( m=Max('payment_date') ).values('m').order_by() qs = qs.annotate(payment_date=Subquery(p_date, output_field=DateTimeField())) if date_from: qs = qs.filter(payment_date__gte=date_from) if date_until: qs = qs.filter(payment_date__lt=date_until) counters = qs.filter( order__event=event ).annotate( status=Case( When(canceled=True, then=Value('c')), default=F('order__status') ) ).values( 'item', 'variation', 'status' ).annotate(cnt=Count('id'), price=Sum('price'), tax_value=Sum('tax_value')).order_by() states = { 'canceled': Order.STATUS_CANCELED, 'paid': Order.STATUS_PAID, 'pending': Order.STATUS_PENDING, 'expired': Order.STATUS_EXPIRED, } num = {} for l, s in states.items(): num[l] = { (p['item'], p['variation']): (p['cnt'], p['price'], p['price'] - p['tax_value']) for p in counters if p['status'] == s } num['total'] = dictsum(num['pending'], num['paid']) for item in items: item.all_variations = list(item.variations.all()) item.has_variations = (len(item.all_variations) > 0) item.num = {} if item.has_variations: for var in item.all_variations: variid = var.id var.num = {} for l in states.keys(): var.num[l] = num[l].get((item.id, variid), (0, 0, 0)) var.num['total'] = num['total'].get((item.id, variid), (0, 0, 0)) for l in states.keys(): item.num[l] = tuplesum(var.num[l] for var in item.all_variations) item.num['total'] = tuplesum(var.num['total'] for var in item.all_variations) else: for l in states.keys(): item.num[l] = num[l].get((item.id, None), (0, 0, 0)) item.num['total'] = num['total'].get((item.id, None), (0, 0, 0)) nonecat = ItemCategory(name=_('Uncategorized')) # Regroup those by category items_by_category = sorted( [ # a group is a tuple of a category and a list of items (cat if cat is not None else nonecat, [i for i in items if i.category == cat]) for cat in set([i.category for i in items]) # insert categories into a set for uniqueness # a set is unsorted, so sort again by category ], key=lambda group: (group[0].position, group[0].id) if ( group[0] is not None and group[0].id is not None) else (0, 0) ) for c in items_by_category: c[0].num = {} for l in states.keys(): c[0].num[l] = tuplesum(item.num[l] for item in c[1]) c[0].num['total'] = tuplesum(item.num['total'] for item in c[1]) # Payment fees payment_cat_obj = DummyObject() payment_cat_obj.name = _('Fees') payment_items = [] if not subevent and fees: qs = OrderFee.all.filter( order__event=event ).annotate( status=Case( When(canceled=True, then=Value('c')), default=F('order__status') ) ) if date_filter == 'order_date': if date_from: qs = qs.filter(order__datetime__gte=date_from) if date_until: qs = qs.filter(order__datetime__lt=date_until) elif date_filter == 'last_payment_date': qs = qs.annotate(payment_date=Subquery(p_date, output_field=DateTimeField())) if date_from: qs = qs.filter(payment_date__gte=date_from) if date_until: qs = qs.filter(payment_date__lt=date_until) counters = qs.values( 'fee_type', 'internal_type', 'status' ).annotate(cnt=Count('id'), value=Sum('value'), tax_value=Sum('tax_value')).order_by() for l, s in states.items(): num[l] = { (o['fee_type'], o['internal_type']): (o['cnt'], o['value'], o['value'] - o['tax_value']) for o in counters if o['status'] == s } num['total'] = dictsum(num['pending'], num['paid']) provider_names = { k: v.verbose_name for k, v in event.get_payment_providers().items() } names = dict(OrderFee.FEE_TYPES) for pprov, total in sorted(num['total'].items(), key=lambda i: i[0]): ppobj = DummyObject() if pprov[0] == OrderFee.FEE_TYPE_PAYMENT: ppobj.name = '{} - {}'.format(names[pprov[0]], provider_names.get(pprov[1], pprov[1])) else: name = pprov[1] for r, resp in order_fee_type_name.send(sender=event, fee_type=pprov[0], internal_type=pprov[1]): if resp: name = resp break ppobj.name = '{} - {}'.format(names[pprov[0]], name) ppobj.provider = pprov[1] ppobj.has_variations = False ppobj.num = {} for l in states.keys(): ppobj.num[l] = num[l].get(pprov, (0, 0, 0)) ppobj.num['total'] = total payment_items.append(ppobj) payment_cat_obj.num = {} for l in states.keys(): payment_cat_obj.num[l] = ( Dontsum(''), sum(i.num[l][1] for i in payment_items), sum(i.num[l][2] for i in payment_items) ) payment_cat_obj.num['total'] = ( Dontsum(''), sum(i.num['total'][1] for i in payment_items), sum(i.num['total'][2] for i in payment_items) ) payment_cat = (payment_cat_obj, payment_items) any_payment = any(payment_cat_obj.num[s][1] for s in states.keys()) if any_payment: items_by_category.append(payment_cat) total = { 'num': {'total': tuplesum(c.num['total'] for c, i in items_by_category)} } for l in states.keys(): total['num'][l] = tuplesum(c.num[l] for c, i in items_by_category) return items_by_category, total
def sform(self): return EventCreateSettingsForm( obj=Event(), prefix='settings', data=self.request.POST if self.request.method == 'POST' else None)