Exports: Add predefined timeframes (#3027)

Co-authored-by: Richard Schreiber <schreiber@rami.io>
This commit is contained in:
Raphael Michel
2023-01-13 13:14:08 +01:00
committed by GitHub
parent 95979143d7
commit bf4569b080
13 changed files with 715 additions and 269 deletions

View File

@@ -33,7 +33,6 @@
# License for the specific language governing permissions and limitations under the License.
from collections import OrderedDict
from datetime import datetime, time, timedelta
import bleach
import dateutil.parser
@@ -44,7 +43,7 @@ from django.db.models import (
from django.db.models.functions import Coalesce, NullIf
from django.urls import reverse
from django.utils.formats import date_format
from django.utils.timezone import is_aware, make_aware
from django.utils.timezone import is_aware, make_aware, now
from django.utils.translation import (
gettext as _, gettext_lazy, pgettext, pgettext_lazy,
)
@@ -58,6 +57,10 @@ from pretix.base.models import (
)
from pretix.base.settings import PERSON_NAME_SCHEMES
from pretix.base.templatetags.money import money_filter
from pretix.base.timeframes import (
DateFrameField,
resolve_timeframe_to_datetime_start_inclusive_end_exclusive,
)
from pretix.control.forms.widgets import Select2
from pretix.helpers.templatetags.jsonfield import JSONExtract
from pretix.plugins.reports.exporters import ReportlabExportMixin
@@ -78,19 +81,12 @@ class CheckInListMixin(BaseExporter):
),
initial=self.event.checkin_lists.first()
)),
('date_from',
forms.DateField(
label=_('Start date'),
widget=forms.DateInput(attrs={'class': 'datepickerfield'}),
('date_range',
DateFrameField(
label=_('Date range'),
include_future_frames=True,
required=False,
help_text=_('Only include tickets for dates on or after this date.')
)),
('date_to',
forms.DateField(
label=_('End date'),
widget=forms.DateInput(attrs={'class': 'datepickerfield'}),
required=False,
help_text=_('Only include tickets for dates on or before this date.')
help_text=_('Only include tickets for dates within this range.')
)),
('secrets',
forms.BooleanField(
@@ -129,8 +125,7 @@ class CheckInListMixin(BaseExporter):
)
if not self.event.has_subevents:
del d['date_from']
del d['date_to']
del d['date_range']
d['list'].queryset = self.event.checkin_lists.all()
d['list'].widget = Select2(
@@ -181,19 +176,12 @@ class CheckInListMixin(BaseExporter):
if cl.subevent:
qs = qs.filter(subevent=cl.subevent)
if form_data.get('date_from'):
dt = make_aware(datetime.combine(
dateutil.parser.parse(form_data['date_from']).date(),
time(hour=0, minute=0, second=0)
), self.event.timezone)
qs = qs.filter(subevent__date_from__gte=dt)
if form_data.get('date_to'):
dt = make_aware(datetime.combine(
dateutil.parser.parse(form_data['date_to']).date() + timedelta(days=1),
time(hour=0, minute=0, second=0)
), self.event.timezone)
qs = qs.filter(subevent__date_from__lt=dt)
if form_data.get('date_range'):
dt_start, dt_end = resolve_timeframe_to_datetime_start_inclusive_end_exclusive(now(), form_data['date_range'], self.timezone)
if dt_start:
qs = qs.filter(subevent__date_from__gte=dt_start)
if dt_end:
qs = qs.filter(subevent__date_to__lt=dt_end)
o = ()
if self.event.has_subevents and not cl.subevent:

View File

@@ -35,7 +35,6 @@
import copy
import tempfile
from collections import OrderedDict, defaultdict
from datetime import date, datetime, time, timedelta
from decimal import Decimal
import pytz
@@ -47,7 +46,7 @@ from django.db import models
from django.db.models import DateTimeField, Max, OuterRef, Subquery, Sum
from django.template.defaultfilters import floatformat
from django.utils.formats import date_format, localize
from django.utils.timezone import get_current_timezone, make_aware, now
from django.utils.timezone import get_current_timezone, now
from django.utils.translation import (
gettext as _, gettext_lazy, pgettext, pgettext_lazy,
)
@@ -59,11 +58,14 @@ from reportlab.platypus import PageBreak, Paragraph, Spacer, Table, TableStyle
from pretix.base.decimal import round_decimal
from pretix.base.exporter import BaseExporter, MultiSheetListExporter
from pretix.base.forms.widgets import DatePickerWidget
from pretix.base.models import Order, OrderPosition
from pretix.base.models.event import SubEvent
from pretix.base.models.orders import OrderFee, OrderPayment
from pretix.base.services.stats import order_overview
from pretix.base.timeframes import (
DateFrameField, resolve_timeframe_to_dates_inclusive,
resolve_timeframe_to_datetime_start_inclusive_end_exclusive,
)
from pretix.control.forms.filter import OverviewFilterForm
@@ -236,12 +238,13 @@ class OverviewReport(Report):
def _filter_story(self, doc, form_data, net=False):
story = []
if form_data.get('date_axis') and (form_data.get('date_from') or form_data.get('date_until')):
if form_data.get('date_axis') and form_data.get('date_range'):
d_start, d_end = resolve_timeframe_to_dates_inclusive(now(), form_data['date_range'], self.timezone)
story += [
Paragraph(_('{axis} between {start} and {end}').format(
axis=dict(OverviewFilterForm(event=self.event).fields['date_axis'].choices)[form_data.get('date_axis')],
start=date_format(form_data.get('date_from'), 'SHORT_DATE_FORMAT') if form_data.get('date_from') else '',
end=date_format(form_data.get('date_until'), 'SHORT_DATE_FORMAT') if form_data.get('date_until') else '',
start=date_format(d_start, 'SHORT_DATE_FORMAT') if d_start else '',
end=date_format(d_end, 'SHORT_DATE_FORMAT') if d_end else '',
), self.get_style()),
Spacer(1, 5 * mm)
]
@@ -256,12 +259,16 @@ class OverviewReport(Report):
return story
def _get_data(self, form_data):
if form_data.get('date_range'):
d_start, d_end = resolve_timeframe_to_dates_inclusive(now(), form_data['date_range'], self.timezone)
else:
d_start, d_end = None, None
return order_overview(
self.event,
subevent=form_data.get('subevent'),
date_filter=form_data.get('date_axis'),
date_from=form_data.get('date_from'),
date_until=form_data.get('date_until'),
date_from=d_start,
date_until=d_end,
fees=True
)
@@ -381,6 +388,13 @@ class OverviewReport(Report):
def export_form_fields(self) -> dict:
f = OverviewFilterForm(event=self.event)
del f.fields['ordering']
del f.fields['date_from']
del f.fields['date_until']
f.fields['date_range'] = DateFrameField(
label=_('Date range'),
include_future_frames=False,
required=False,
)
return f.fields
@@ -606,48 +620,30 @@ class OrderTaxListReport(MultiSheetListExporter):
),
required=False,
)),
('date_from', forms.DateField(
label=_('Date from'),
required=False,
widget=DatePickerWidget,
)),
('date_until', forms.DateField(
label=_('Date until'),
required=False,
widget=DatePickerWidget,
))
('date_range',
DateFrameField(
label=_('Date range'),
include_future_frames=False,
required=False,
help_text=_('Only include orders created within this date range.')
)),
]
))
return f
def filter_qs(self, qs, form_data):
date_from = form_data.get('date_from')
date_until = form_data.get('date_until')
date_range = form_data.get('date_range')
date_filter = form_data.get('date_axis')
if date_from:
if isinstance(date_from, str):
date_from = parse(date_from).date()
if isinstance(date_from, date):
date_from = make_aware(datetime.combine(
date_from,
time(hour=0, minute=0, second=0, microsecond=0)
), self.event.timezone)
if date_until:
if isinstance(date_until, str):
date_until = parse(date_until).date()
if isinstance(date_until, date):
date_until = make_aware(datetime.combine(
date_until + timedelta(days=1),
time(hour=0, minute=0, second=0, microsecond=0)
), self.event.timezone)
if date_range:
dt_start, dt_end = resolve_timeframe_to_datetime_start_inclusive_end_exclusive(now(), date_range, self.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':
if date_filter == 'order_date' and date_range:
if dt_start:
qs = qs.filter(order__datetime__gte=dt_start)
if dt_end:
qs = qs.filter(order__datetime__lt=dt_end)
elif date_filter == 'last_payment_date' and date_range:
p_date = OrderPayment.objects.filter(
order=OuterRef('order'),
state__in=[OrderPayment.PAYMENT_STATE_CONFIRMED, OrderPayment.PAYMENT_STATE_REFUNDED],
@@ -656,10 +652,10 @@ class OrderTaxListReport(MultiSheetListExporter):
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)
if dt_start:
qs = qs.filter(payment_date__gte=dt_start)
if dt_end:
qs = qs.filter(payment_date__lt=dt_end)
return qs
def iterate_sheet(self, form_data, sheet):

View File

@@ -34,16 +34,14 @@
import logging
from collections import OrderedDict
from datetime import datetime, time, timedelta
from io import BytesIO
import dateutil.parser
from django import forms
from django.core.files.base import ContentFile
from django.db import DataError, models
from django.db.models import OuterRef, Q, Subquery
from django.db.models.functions import Cast, Coalesce
from django.utils.timezone import make_aware
from django.utils.timezone import now
from django.utils.translation import gettext as _, gettext_lazy, pgettext_lazy
from PyPDF2 import PdfMerger
@@ -55,6 +53,10 @@ from pretix.base.models import (
from pretix.base.settings import PERSON_NAME_SCHEMES
from ...base.services.export import ExportError
from ...base.timeframes import (
DateFrameField,
resolve_timeframe_to_datetime_start_inclusive_end_exclusive,
)
from ...helpers.templatetags.jsonfield import JSONExtract
from .ticketoutput import PdfTicketOutput
@@ -78,19 +80,12 @@ class AllTicketsPDF(BaseExporter):
label=_('Include pending orders'),
required=False
)),
('date_from',
forms.DateField(
label=_('Start date'),
widget=forms.DateInput(attrs={'class': 'datepickerfield'}),
('date_range',
DateFrameField(
label=_('Date range'),
include_future_frames=True,
required=False,
help_text=_('Only include tickets for dates on or after this date.')
)),
('date_to',
forms.DateField(
label=_('End date'),
widget=forms.DateInput(attrs={'class': 'datepickerfield'}),
required=False,
help_text=_('Only include tickets for dates on or before this date.')
help_text=_('Only include tickets for dates within this range.')
)),
('order_by',
forms.ChoiceField(
@@ -117,8 +112,7 @@ class AllTicketsPDF(BaseExporter):
)
if not self.is_multievent and not self.event.has_subevents:
del d['date_from']
del d['date_to']
del d['date_range']
return d
@@ -135,19 +129,12 @@ class AllTicketsPDF(BaseExporter):
else:
qs = qs.filter(order__status__in=[Order.STATUS_PAID])
if form_data.get('date_from'):
dt = make_aware(datetime.combine(
dateutil.parser.parse(form_data['date_from']).date(),
time(hour=0, minute=0, second=0)
), self.timezone)
qs = qs.filter(Q(subevent__date_from__gte=dt) | Q(subevent__isnull=True, order__event__date_from__gte=dt))
if form_data.get('date_to'):
dt = make_aware(datetime.combine(
dateutil.parser.parse(form_data['date_to']).date() + timedelta(days=1),
time(hour=0, minute=0, second=0)
), self.timezone)
qs = qs.filter(Q(subevent__date_from__lt=dt) | Q(subevent__isnull=True, order__event__date_from__lt=dt))
if form_data.get('date_range'):
dt_start, dt_end = resolve_timeframe_to_datetime_start_inclusive_end_exclusive(now(), form_data['date_range'], self.timezone)
if dt_start:
qs = qs.filter(Q(subevent__date_from__gte=dt_start) | Q(subevent__isnull=True, order__event__date_from__gte=dt_start))
if dt_end:
qs = qs.filter(Q(subevent__date_from__lt=dt_end) | Q(subevent__isnull=True, order__event__date_from__lt=dt_end))
if form_data.get('order_by') == 'name':
qs = qs.order_by('attendee_name_cached', 'order__code')