mirror of
https://github.com/pretix/pretix.git
synced 2025-12-11 01:22:28 +00:00
Compare commits
46 Commits
custom-man
...
v1.4.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3acc29f51a | ||
|
|
4bae0d1b81 | ||
|
|
c38a850294 | ||
|
|
a7ec475c40 | ||
|
|
ac1467bd4b | ||
|
|
3f0af67345 | ||
|
|
4d14b6c096 | ||
|
|
cb789bc06c | ||
|
|
17dad33f8b | ||
|
|
1d5c160e1d | ||
|
|
f04b7fa365 | ||
|
|
fa011fbdce | ||
|
|
759c5374d9 | ||
|
|
f631acdf18 | ||
|
|
b2dfd8ab11 | ||
|
|
43f4803da7 | ||
|
|
019d8220b8 | ||
|
|
b946010bdb | ||
|
|
c3097b12c3 | ||
|
|
12a53710c3 | ||
|
|
983326e610 | ||
|
|
b4eb707b38 | ||
|
|
e44f34f0a9 | ||
|
|
3c762adbf4 | ||
|
|
ebabd20d09 | ||
|
|
8694e1901a | ||
|
|
0f2875e89a | ||
|
|
74d9921be1 | ||
|
|
41e56adfdb | ||
|
|
4ff1d302d9 | ||
|
|
d6e213d51a | ||
|
|
2a4deeba55 | ||
|
|
24b1d2afcb | ||
|
|
5cea3d824a | ||
|
|
78a8a7d744 | ||
|
|
a3bf85754a | ||
|
|
006b6fd5e8 | ||
|
|
8ff2c42070 | ||
|
|
c5d18c6884 | ||
|
|
48b3621f1e | ||
|
|
fb716eb498 | ||
|
|
5d8e294350 | ||
|
|
8a96d8c24e | ||
|
|
6396d2f922 | ||
|
|
ed7e90451b | ||
|
|
f5990dd5c4 |
@@ -5,7 +5,6 @@ tests:
|
||||
- virtualenv env
|
||||
- source env/bin/activate
|
||||
- pip install -U pip wheel setuptools
|
||||
- XDG_CACHE_HOME=/cache bash .travis.sh style
|
||||
- XDG_CACHE_HOME=/cache bash .travis.sh tests
|
||||
tags:
|
||||
- python3
|
||||
|
||||
@@ -51,7 +51,7 @@ If there is a problem, a status code in the ``5xx`` range will be returned.
|
||||
Performance monitoring
|
||||
----------------------
|
||||
|
||||
If you to generate detailled performance statistics of your pretix installation, there is an
|
||||
If you want to generate detailed performance statistics of your pretix installation, there is an
|
||||
endpoint at ``https://pretix.mydomain.com/metrics`` (no slash at the end) which returns a
|
||||
number of values in the text format understood by monitoring tools like Prometheus_. This data
|
||||
is only collected and exposed if you enable it in the :ref:`metrics-settings` section of your
|
||||
|
||||
@@ -25,7 +25,7 @@ Frontend
|
||||
--------
|
||||
|
||||
.. automodule:: pretix.presale.signals
|
||||
:members: html_head, html_footer, footer_links, front_page_top, front_page_bottom, checkout_confirm_messages
|
||||
:members: html_head, html_footer, footer_links, front_page_top, front_page_bottom, contact_form_fields, checkout_confirm_messages
|
||||
|
||||
|
||||
.. automodule:: pretix.presale.signals
|
||||
|
||||
@@ -40,7 +40,7 @@ automatically). If you are working on Ubuntu or Debian, we strongly recommend up
|
||||
your pip and setuptools installation inside the virtual environment, otherwise some of
|
||||
the dependencies might fail::
|
||||
|
||||
pip3 install -U pip setuptools==28.6.1
|
||||
pip3 install -U pip setuptools
|
||||
|
||||
Working with the code
|
||||
---------------------
|
||||
@@ -81,7 +81,7 @@ and head to http://localhost:8000/
|
||||
|
||||
As we did not implement an overall front page yet, you need to go directly to
|
||||
http://localhost:8000/control/ for the admin view or, if you imported the test
|
||||
data as suggested above, to the event page at http://localhost:8000/bigevents/2017/
|
||||
data as suggested above, to the event page at http://localhost:8000/bigevents/2018/
|
||||
|
||||
.. note:: If you want the development server to listen on a different interface or
|
||||
port (for example because you develop on `pretixdroid`_), you can check
|
||||
|
||||
@@ -15,7 +15,8 @@ ways that pretix itself is:
|
||||
* PDF ticket output
|
||||
|
||||
The following plugins are not shipped with pretix but are maintained by the
|
||||
same team:
|
||||
same team. We update them regularly to make them compatible with the latest
|
||||
pretix releases:
|
||||
|
||||
* `SEPA direct debit`_
|
||||
* `Pages`_
|
||||
@@ -23,8 +24,17 @@ same team:
|
||||
* `Cartshare`_
|
||||
* `Fontpack Free fonts`_
|
||||
|
||||
The following closed-source plugins are available to customers of the hosted pretix.eu platform.
|
||||
Please get in touch with the pretix team if you want to have them for your self-hosted
|
||||
pretix installation:
|
||||
|
||||
* Campaign tracking
|
||||
* Integration with Google Analytics and Facebook Pixel
|
||||
* Integration with Slack
|
||||
* Integration with MailChimp
|
||||
|
||||
The following plugins are from independent third-party authors, so we can make
|
||||
no statements about their stability:
|
||||
no statements about their stability or compatibility:
|
||||
|
||||
* `esPass ticket output`_
|
||||
* `IcePay integration`_
|
||||
|
||||
@@ -36,11 +36,11 @@ t = Team.objects.get_or_create(
|
||||
can_change_organizer_settings=True, can_change_event_settings=True, can_change_items=True,
|
||||
can_view_orders=True, can_change_orders=True, can_view_vouchers=True, can_change_vouchers=True
|
||||
)
|
||||
t.members.add(user)
|
||||
cat_tickets = ItemCategory.all.create(
|
||||
t[0].members.add(user)
|
||||
cat_tickets = ItemCategory.objects.create(
|
||||
event=event, name='Tickets'
|
||||
)
|
||||
cat_merch = ItemCategory.all.create(
|
||||
cat_merch = ItemCategory.objects.create(
|
||||
event=event, name='Merchandise'
|
||||
)
|
||||
question = Question.objects.create(
|
||||
|
||||
@@ -1 +1 @@
|
||||
__version__ = "1.4.0-dev0"
|
||||
__version__ = "1.4.1"
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import csv
|
||||
import io
|
||||
from collections import OrderedDict
|
||||
from decimal import Decimal
|
||||
|
||||
import pytz
|
||||
from defusedcsv import csv
|
||||
from django import forms
|
||||
from django.db.models import Sum
|
||||
from django.dispatch import receiver
|
||||
|
||||
27
src/pretix/base/migrations/0061_auto_20170521_0942.py
Normal file
27
src/pretix/base/migrations/0061_auto_20170521_0942.py
Normal file
@@ -0,0 +1,27 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.7 on 2017-05-21 09:42
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.core.cache import cache
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def rename_placeholder(app, schema_editor):
|
||||
EventSettingsStore = app.get_model('pretixbase', 'Event_SettingsStore')
|
||||
|
||||
for setting in EventSettingsStore.objects.all():
|
||||
if setting.key == 'mail_text_order_placed':
|
||||
new_value = setting.value.replace('{paymentinfo}', '{payment_info}')
|
||||
setting.value = new_value
|
||||
cache.delete('hierarkey_{}_{}'.format('event', setting.object_id))
|
||||
setting.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('pretixbase', '0060_auto_20170510_1027'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(rename_placeholder, migrations.RunPython.noop)
|
||||
]
|
||||
@@ -2,7 +2,6 @@ import json
|
||||
import uuid
|
||||
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core import checks
|
||||
from django.db import models
|
||||
from django.db.models.signals import post_delete
|
||||
from django.dispatch import receiver
|
||||
@@ -35,6 +34,7 @@ def cached_file_delete(sender, instance, **kwargs):
|
||||
|
||||
|
||||
class LoggingMixin:
|
||||
|
||||
def log_action(self, action, data=None, user=None):
|
||||
"""
|
||||
Create a LogEntry object that is related to this object.
|
||||
@@ -59,6 +59,7 @@ class LoggingMixin:
|
||||
|
||||
|
||||
class LoggedModel(models.Model, LoggingMixin):
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
@@ -73,38 +74,3 @@ class LoggedModel(models.Model, LoggingMixin):
|
||||
return LogEntry.objects.filter(
|
||||
content_type=ContentType.objects.get_for_model(type(self)), object_id=self.pk
|
||||
).select_related('user', 'event')
|
||||
|
||||
|
||||
class EventBoundModelMixin:
|
||||
event_lookup = 'event'
|
||||
|
||||
@classmethod
|
||||
def for_event(cls, event):
|
||||
return cls.all.filter(**{
|
||||
cls.event_lookup: event
|
||||
})
|
||||
|
||||
@classmethod
|
||||
def check(cls, **kwargs):
|
||||
try:
|
||||
errors = super(cls).check(**kwargs)
|
||||
except AttributeError:
|
||||
errors = []
|
||||
if hasattr(cls, 'objects'):
|
||||
errors.append(
|
||||
checks.Error(
|
||||
'Default model manager "objects" defined.',
|
||||
hint='Replace the objects manager by a manager called "all".',
|
||||
obj=cls,
|
||||
id='pretixbase.E001',
|
||||
)
|
||||
)
|
||||
if not hasattr(cls, 'all'):
|
||||
errors.append(
|
||||
checks.Error(
|
||||
'Model manager "all" not defined.',
|
||||
obj=cls,
|
||||
id='pretixbase.E002',
|
||||
)
|
||||
)
|
||||
return errors
|
||||
|
||||
@@ -234,14 +234,14 @@ class Event(LoggedModel):
|
||||
), tz)
|
||||
|
||||
def copy_data_from(self, other):
|
||||
from . import ItemAddOn, Item, Question, Quota
|
||||
from . import ItemAddOn, ItemCategory, Item, Question, Quota
|
||||
from ..signals import event_copy_data
|
||||
|
||||
self.plugins = other.plugins
|
||||
self.save()
|
||||
|
||||
category_map = {}
|
||||
for c in other.categories.all():
|
||||
for c in ItemCategory.objects.filter(event=other):
|
||||
category_map[c.pk] = c
|
||||
c.pk = None
|
||||
c.event = self
|
||||
|
||||
@@ -14,12 +14,12 @@ from django.utils.translation import ugettext_lazy as _
|
||||
from i18nfield.fields import I18nCharField, I18nTextField
|
||||
|
||||
from pretix.base.decimal import round_decimal
|
||||
from pretix.base.models.base import EventBoundModelMixin, LoggedModel
|
||||
from pretix.base.models.base import LoggedModel
|
||||
|
||||
from .event import Event
|
||||
|
||||
|
||||
class ItemCategory(EventBoundModelMixin, LoggedModel):
|
||||
class ItemCategory(LoggedModel):
|
||||
"""
|
||||
Items can be sorted into these categories.
|
||||
|
||||
@@ -53,8 +53,6 @@ class ItemCategory(EventBoundModelMixin, LoggedModel):
|
||||
'source for add-ons.')
|
||||
)
|
||||
|
||||
all = models.Manager()
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Product category")
|
||||
verbose_name_plural = _("Product categories")
|
||||
|
||||
@@ -5,6 +5,7 @@ from django.contrib.contenttypes.models import ContentType
|
||||
from django.db import models
|
||||
from django.urls import reverse
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.html import escape
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
@@ -66,7 +67,7 @@ class LogEntry(models.Model):
|
||||
'organizer': self.event.organizer.slug,
|
||||
'code': co.code
|
||||
}),
|
||||
'val': co.code,
|
||||
'val': escape(co.code),
|
||||
}
|
||||
elif isinstance(co, Voucher):
|
||||
a_text = _('Voucher {val}…')
|
||||
@@ -76,7 +77,7 @@ class LogEntry(models.Model):
|
||||
'organizer': self.event.organizer.slug,
|
||||
'voucher': co.id
|
||||
}),
|
||||
'val': co.code[:6],
|
||||
'val': escape(co.code[:6]),
|
||||
}
|
||||
elif isinstance(co, Item):
|
||||
a_text = _('Product {val}')
|
||||
@@ -86,7 +87,7 @@ class LogEntry(models.Model):
|
||||
'organizer': self.event.organizer.slug,
|
||||
'item': co.id
|
||||
}),
|
||||
'val': co.name,
|
||||
'val': escape(co.name),
|
||||
}
|
||||
elif isinstance(co, Quota):
|
||||
a_text = _('Quota {val}')
|
||||
@@ -96,7 +97,7 @@ class LogEntry(models.Model):
|
||||
'organizer': self.event.organizer.slug,
|
||||
'quota': co.id
|
||||
}),
|
||||
'val': co.name,
|
||||
'val': escape(co.name),
|
||||
}
|
||||
elif isinstance(co, ItemCategory):
|
||||
a_text = _('Category {val}')
|
||||
@@ -106,7 +107,7 @@ class LogEntry(models.Model):
|
||||
'organizer': self.event.organizer.slug,
|
||||
'category': co.id
|
||||
}),
|
||||
'val': co.name,
|
||||
'val': escape(co.name),
|
||||
}
|
||||
elif isinstance(co, Question):
|
||||
a_text = _('Question {val}')
|
||||
@@ -116,7 +117,7 @@ class LogEntry(models.Model):
|
||||
'organizer': self.event.organizer.slug,
|
||||
'question': co.id
|
||||
}),
|
||||
'val': co.question,
|
||||
'val': escape(co.question),
|
||||
}
|
||||
|
||||
if a_text and a_map:
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import copy
|
||||
import json
|
||||
import os
|
||||
import string
|
||||
from datetime import datetime
|
||||
@@ -183,6 +184,10 @@ class Order(LoggedModel):
|
||||
def __str__(self):
|
||||
return self.full_code
|
||||
|
||||
@cached_property
|
||||
def meta_info_data(self):
|
||||
return json.loads(self.meta_info)
|
||||
|
||||
@property
|
||||
def full_code(self):
|
||||
"""
|
||||
|
||||
@@ -414,7 +414,7 @@ def _perform_order(event: str, payment_provider: str, position_ids: List[str],
|
||||
'order': order.code,
|
||||
'secret': order.secret
|
||||
}),
|
||||
'paymentinfo': str(pprov.order_pending_mail_render(order)),
|
||||
'payment_info': str(pprov.order_pending_mail_render(order)),
|
||||
'invoice_name': invoice_name,
|
||||
'invoice_company': invoice_company,
|
||||
},
|
||||
@@ -512,7 +512,7 @@ class OrderChangeManager:
|
||||
if (not variation and item.has_variations) or (variation and variation.item_id != item.pk):
|
||||
raise OrderError(self.error_messages['product_without_variation'])
|
||||
price = item.default_price if variation is None else variation.price
|
||||
if not price:
|
||||
if price is None:
|
||||
raise OrderError(self.error_messages['product_invalid'])
|
||||
self._totaldiff = price - position.price
|
||||
self._quotadiff.update(variation.quotas.all() if variation else item.quotas.all())
|
||||
|
||||
@@ -240,7 +240,7 @@ Your {event} team"""))
|
||||
we successfully received your order for {event} with a total value
|
||||
of {total} {currency}. Please complete your payment before {date}.
|
||||
|
||||
{paymentinfo}
|
||||
{payment_info}
|
||||
|
||||
You can change your order details and view the status of your order at
|
||||
{url}
|
||||
|
||||
13
src/pretix/base/templatetags/escapejson.py
Normal file
13
src/pretix/base/templatetags/escapejson.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from django import template
|
||||
from django.template.defaultfilters import stringfilter
|
||||
|
||||
from pretix.helpers.escapejson import escapejson
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.filter("escapejson")
|
||||
@stringfilter
|
||||
def escapejs_filter(value):
|
||||
"""Hex encodes characters for use in a application/json type script."""
|
||||
return escapejson(value)
|
||||
@@ -1,6 +1,12 @@
|
||||
import urllib.parse
|
||||
|
||||
import bleach
|
||||
import markdown
|
||||
from bleach import DEFAULT_CALLBACKS
|
||||
from django import template
|
||||
from django.core import signing
|
||||
from django.urls import reverse
|
||||
from django.utils.http import is_safe_url
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
register = template.Library()
|
||||
@@ -42,6 +48,15 @@ ALLOWED_ATTRIBUTES = {
|
||||
}
|
||||
|
||||
|
||||
def safelink_callback(attrs, new=False):
|
||||
url = attrs.get((None, 'href'), '/')
|
||||
if not is_safe_url(url):
|
||||
signer = signing.Signer(salt='safe-redirect')
|
||||
attrs[None, 'href'] = reverse('redirect') + '?url=' + urllib.parse.quote(signer.sign(url))
|
||||
attrs[None, 'target'] = '_blank'
|
||||
return attrs
|
||||
|
||||
|
||||
@register.filter
|
||||
def rich_text(text: str, **kwargs):
|
||||
"""
|
||||
@@ -52,5 +67,5 @@ def rich_text(text: str, **kwargs):
|
||||
markdown.markdown(text),
|
||||
tags=ALLOWED_TAGS,
|
||||
attributes=ALLOWED_ATTRIBUTES,
|
||||
))
|
||||
), callbacks=DEFAULT_CALLBACKS + [safelink_callback])
|
||||
return mark_safe(body_md)
|
||||
|
||||
@@ -33,7 +33,7 @@ def contextprocessor(request):
|
||||
|
||||
_js_payment_weekdays_disabled = '[]'
|
||||
_nav_event = []
|
||||
if hasattr(request, 'event'):
|
||||
if getattr(request, 'event', None) and hasattr(request, 'organizer'):
|
||||
for receiver, response in nav_event.send(request.event, request=request):
|
||||
_nav_event += response
|
||||
if request.event.settings.get('payment_term_weekdays'):
|
||||
|
||||
@@ -259,10 +259,12 @@ class EventSettingsForm(SettingsForm):
|
||||
)
|
||||
attendee_emails_asked = forms.BooleanField(
|
||||
label=_("Ask for email addresses per ticket"),
|
||||
help_text=_("Normally, pretix asks for one email address per order and the order confirmation will be send "
|
||||
"to that email address. If you enable this option, the system will additionally ask for "
|
||||
help_text=_("Normally, pretix asks for one email address per order and the order confirmation will be sent "
|
||||
"only to that email address. If you enable this option, the system will additionally ask for "
|
||||
"individual email addresses for every admission ticket. This might be useful if you want to "
|
||||
"obtain individual addresses for every attendee even in case of group orders."),
|
||||
"obtain individual addresses for every attendee even in case of group orders. However, "
|
||||
"pretix will send the order confirmation only to the one primary email address, not to the "
|
||||
"per-attendee addresses."),
|
||||
required=False
|
||||
)
|
||||
attendee_emails_required = forms.BooleanField(
|
||||
@@ -490,9 +492,9 @@ class MailSettingsForm(SettingsForm):
|
||||
label=_("Text"),
|
||||
required=False,
|
||||
widget=I18nTextarea,
|
||||
help_text=_("Available placeholders: {event}, {total}, {currency}, {date}, {paymentinfo}, {url}, "
|
||||
help_text=_("Available placeholders: {event}, {total}, {currency}, {date}, {payment_info}, {url}, "
|
||||
"{invoice_name}, {invoice_company}"),
|
||||
validators=[PlaceholderValidator(['{event}', '{total}', '{currency}', '{date}', '{paymentinfo}',
|
||||
validators=[PlaceholderValidator(['{event}', '{total}', '{currency}', '{date}', '{payment_info}',
|
||||
'{url}', '{invoice_name}', '{invoice_company}'])]
|
||||
)
|
||||
mail_text_order_paid = I18nFormField(
|
||||
@@ -610,7 +612,7 @@ class DisplaySettingsForm(SettingsForm):
|
||||
)
|
||||
logo_image = ExtFileField(
|
||||
label=_('Logo image'),
|
||||
ext_whitelist=(".png", ".jpg", ".svg", ".gif", ".jpeg"),
|
||||
ext_whitelist=(".png", ".jpg", ".gif", ".jpeg"),
|
||||
required=False,
|
||||
help_text=_('If you provide a logo image, we will by default not show your events name and date '
|
||||
'in the page header. We will show your logo with a maximal height of 120 pixels.')
|
||||
|
||||
@@ -121,7 +121,7 @@ class OrganizerSettingsForm(SettingsForm):
|
||||
|
||||
organizer_logo_image = ExtFileField(
|
||||
label=_('Logo image'),
|
||||
ext_whitelist=(".png", ".jpg", ".svg", ".gif", ".jpeg"),
|
||||
ext_whitelist=(".png", ".jpg", ".gif", ".jpeg"),
|
||||
required=False,
|
||||
help_text=_('If you provide a logo image, we will by default not show your organization name '
|
||||
'in the page header. We will show your logo with a maximal height of 120 pixels.')
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{% extends "pretixcontrol/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load staticfiles %}
|
||||
{% block title %}{{ request.event.name }}{% endblock %}
|
||||
|
||||
{% block nav %}
|
||||
@@ -112,7 +113,9 @@
|
||||
<li>
|
||||
<a href="{{ nav.url }}" {% if nav.active %}class="active"{% endif %}
|
||||
{% if nav.children %}class="has-children"{% endif %}>
|
||||
{% if nav.icon %}
|
||||
{% if nav.icon and "." in nav.icon %}
|
||||
<img src="{% static nav.icon %}" class="fa-img">
|
||||
{% elif nav.icon %}
|
||||
<i class="fa fa-{{ nav.icon }} fa-fw"></i>
|
||||
{% endif %}
|
||||
{{ nav.label }}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{% extends "pretixcontrol/items/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load bootstrap3 %}
|
||||
{% load escapejson %}
|
||||
{% load formset_tags %}
|
||||
{% block title %}{% blocktrans with name=question.question %}Question: {{ name }}{% endblocktrans %}{% endblock %}
|
||||
{% block inside %}
|
||||
@@ -58,7 +59,7 @@
|
||||
<div class="chart" id="question_chart" data-type="{{ question.type }}">
|
||||
|
||||
</div>
|
||||
<script type="application/json" id="question-chart-data">{{ stats_json|safe }}</script>
|
||||
<script type="application/json" id="question-chart-data">{{ stats_json|escapejson }}</script>
|
||||
</div>
|
||||
<div class="col-md-5 col-xs-12">
|
||||
<table class="table table-bordered table-hover">
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{% extends "pretixcontrol/items/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load bootstrap3 %}
|
||||
{% load escapejson %}
|
||||
{% load eventsignal %}
|
||||
{% block title %}{% blocktrans with name=quota.name %}Quota: {{ name }}{% endblocktrans %}{% endblock %}
|
||||
{% block inside %}
|
||||
@@ -20,7 +21,7 @@
|
||||
<div class="chart" id="quota_chart">
|
||||
|
||||
</div>
|
||||
<script type="application/json" id="quota-chart-data">{{ quota_chart_data|safe }}</script>
|
||||
<script type="application/json" id="quota-chart-data">{{ quota_chart_data|escapejson }}</script>
|
||||
</div>
|
||||
<div class="col-md-5 col-xs-12">
|
||||
<legend>{% trans "Availability calculation" %}</legend>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from django.db.models import Prefetch, Q
|
||||
from django.db.models import F, Prefetch, Q
|
||||
from django.db.models.functions import Coalesce
|
||||
from django.views.generic import ListView
|
||||
|
||||
@@ -68,12 +68,15 @@ class CheckInView(EventPermissionRequiredMixin, ListView):
|
||||
'-code': '-order__code',
|
||||
'email': 'order__email',
|
||||
'-email': '-order__email',
|
||||
'status': 'checkins__id',
|
||||
'-status': '-checkins__id',
|
||||
'timestamp': 'checkins__datetime',
|
||||
'-timestamp': '-checkins__datetime',
|
||||
# Set nulls_first to be consistent over databases
|
||||
'status': F('checkins__id').asc(nulls_first=True),
|
||||
'-status': F('checkins__id').desc(nulls_last=True),
|
||||
'timestamp': F('checkins__datetime').asc(nulls_first=True),
|
||||
'-timestamp': F('checkins__datetime').desc(nulls_last=True),
|
||||
'item': 'item__name',
|
||||
'-item': '-item__name',
|
||||
'name': ('display_name', {'display_name': Coalesce('attendee_name', 'addon_to__attendee_name')}),
|
||||
'-name': ('-display_name', {'display_name': Coalesce('attendee_name', 'addon_to__attendee_name')}),
|
||||
'name': (F('display_name').asc(nulls_first=True),
|
||||
{'display_name': Coalesce('attendee_name', 'addon_to__attendee_name')}),
|
||||
'-name': (F('display_name').desc(nulls_last=True),
|
||||
{'display_name': Coalesce('attendee_name', 'addon_to__attendee_name')}),
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ from django.shortcuts import render
|
||||
from django.template.loader import get_template
|
||||
from django.utils import formats
|
||||
from django.utils.formats import date_format
|
||||
from django.utils.html import escape
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from pretix.base.models import (
|
||||
@@ -138,7 +139,7 @@ def quota_widgets(sender, **kwargs):
|
||||
status, left = q.availability()
|
||||
widgets.append({
|
||||
'content': NUM_WIDGET.format(num='{}/{}'.format(left, q.size) if q.size is not None else '\u221e',
|
||||
text=_('{quota} left').format(quota=q.name)),
|
||||
text=_('{quota} left').format(quota=escape(q.name))),
|
||||
'display_size': 'small',
|
||||
'priority': 50,
|
||||
'url': reverse('control:event.items.quotas.show', kwargs={
|
||||
@@ -269,7 +270,8 @@ def user_event_widgets(**kwargs):
|
||||
for event in events:
|
||||
widgets.append({
|
||||
'content': '<div class="event">{event}<span class="from">{df}</span><span class="to">{dt}</span></div>'.format(
|
||||
event=event.name, df=date_format(event.date_from, 'SHORT_DATE_FORMAT') if event.date_from else '',
|
||||
event=escape(event.name),
|
||||
df=date_format(event.date_from, 'SHORT_DATE_FORMAT') if event.date_from else '',
|
||||
dt=date_format(event.date_to, 'SHORT_DATE_FORMAT') if event.date_to else ''
|
||||
),
|
||||
'display_size': 'small',
|
||||
|
||||
@@ -316,8 +316,7 @@ class InvoicePreview(EventPermissionRequiredMixin, View):
|
||||
def get(self, request, *args, **kwargs):
|
||||
pdf = build_preview_invoice_pdf(request.event)
|
||||
resp = HttpResponse(pdf, content_type='application/pdf')
|
||||
resp['Content-Security-Policy'] = "style-src 'unsafe-inline'; script-src 'unsafe-inline'; object-src 'self'"
|
||||
resp['Content-Disposition'] = 'inline; filename="invoice-preview.pdf"'
|
||||
resp['Content-Disposition'] = 'attachment; filename="invoice-preview.pdf"'
|
||||
return resp
|
||||
|
||||
|
||||
@@ -437,7 +436,7 @@ class MailSettingsPreview(EventPermissionRequiredMixin, View):
|
||||
def items(self):
|
||||
return {
|
||||
'mail_text_order_placed': ['total', 'currency', 'date', 'invoice_company',
|
||||
'event', 'paymentinfo', 'url', 'invoice_name'],
|
||||
'event', 'payment_info', 'url', 'invoice_name'],
|
||||
'mail_text_order_paid': ['event', 'url', 'invoice_name', 'invoice_company', 'payment_info'],
|
||||
'mail_text_order_free': ['event', 'url', 'invoice_name', 'invoice_company'],
|
||||
'mail_text_resend_link': ['event', 'url', 'invoice_name', 'invoice_company'],
|
||||
@@ -468,7 +467,7 @@ class MailSettingsPreview(EventPermissionRequiredMixin, View):
|
||||
'code': '68CYU2H6ZTP3WLK5',
|
||||
'invoice_name': _('John Doe'),
|
||||
'invoice_company': _('Sample Corporation'),
|
||||
'paymentinfo': _('Please transfer money to this bank account: 9999-9999-9999-9999')
|
||||
'payment_info': _('Please transfer money to this bank account: 9999-9999-9999-9999')
|
||||
}
|
||||
|
||||
def generate_order_url(self, code, secret):
|
||||
@@ -528,11 +527,7 @@ class TicketSettingsPreview(EventPermissionRequiredMixin, View):
|
||||
fname, mimet, data = tickets.preview(self.request.event.pk, self.output.identifier)
|
||||
resp = HttpResponse(data, content_type=mimet)
|
||||
ftype = fname.split(".")[-1]
|
||||
if mimet == "application/pdf":
|
||||
resp['Content-Security-Policy'] = "style-src 'unsafe-inline'; script-src 'unsafe-inline'; object-src 'self'"
|
||||
resp['Content-Disposition'] = 'inline; filename="ticket-preview.{}"'.format(ftype)
|
||||
else:
|
||||
resp['Content-Disposition'] = 'attachment; filename="ticket-preview.{}"'.format(ftype)
|
||||
resp['Content-Disposition'] = 'attachment; filename="ticket-preview.{}"'.format(ftype)
|
||||
return resp
|
||||
|
||||
def get_error_url(self) -> str:
|
||||
|
||||
@@ -674,7 +674,7 @@ class ExportDoView(EventPermissionRequiredMixin, ExportMixin, AsyncAction, View)
|
||||
|
||||
if not self.exporter.form.is_valid():
|
||||
messages.error(self.request, _('There was a problem processing your input. See below for error details.'))
|
||||
return self.get(*args, **kwargs)
|
||||
return self.get(request, *args, **kwargs)
|
||||
|
||||
cf = CachedFile()
|
||||
cf.date = now()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import csv
|
||||
import io
|
||||
|
||||
from defusedcsv import csv
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.core.urlresolvers import resolve, reverse
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
from debug_toolbar.middleware import DebugToolbarMiddleware
|
||||
from django.utils.deprecation import MiddlewareMixin
|
||||
|
||||
|
||||
class DebugMiddlewareCompatibilityShim(MiddlewareMixin, DebugToolbarMiddleware):
|
||||
pass
|
||||
16
src/pretix/helpers/escapejson.py
Normal file
16
src/pretix/helpers/escapejson.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from django.utils import six
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.functional import keep_lazy
|
||||
from django.utils.safestring import SafeText, mark_safe
|
||||
|
||||
_json_escapes = {
|
||||
ord('>'): '\\u003E',
|
||||
ord('<'): '\\u003C',
|
||||
ord('&'): '\\u0026',
|
||||
}
|
||||
|
||||
|
||||
@keep_lazy(six.text_type, SafeText)
|
||||
def escapejson(value):
|
||||
"""Hex encodes characters for use in a application/json type script."""
|
||||
return mark_safe(force_text(value).translate(_json_escapes))
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,8 +7,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2017-05-18 09:17+0000\n"
|
||||
"PO-Revision-Date: 2017-05-11 12:13+0200\n"
|
||||
"POT-Creation-Date: 2017-06-01 10:54+0000\n"
|
||||
"PO-Revision-Date: 2017-06-01 12:56+0200\n"
|
||||
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
||||
"Language-Team: \n"
|
||||
"Language: de\n"
|
||||
@@ -79,59 +79,71 @@ msgid "May 31st, 2017"
|
||||
msgstr "31. Mai 2017"
|
||||
|
||||
#: pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/editor.js:164
|
||||
msgid "May 31st – June 4th, 2017"
|
||||
msgstr "31. Mai – 4. Juni 2017"
|
||||
|
||||
#: pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/editor.js:165
|
||||
msgid "20:00"
|
||||
msgstr "20:00"
|
||||
|
||||
#: pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/editor.js:165
|
||||
#: pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/editor.js:166
|
||||
msgid "19:00"
|
||||
msgstr "19:00"
|
||||
|
||||
#: pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/editor.js:166
|
||||
#: pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/editor.js:167
|
||||
msgid "2017-05-31 20:00"
|
||||
msgstr "31.05.2016 20:00"
|
||||
|
||||
#: pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/editor.js:167
|
||||
#: pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/editor.js:168
|
||||
msgid "2017-05-31 19:00"
|
||||
msgstr "31.05.2016 19:00"
|
||||
|
||||
#: pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/editor.js:168
|
||||
#: pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/editor.js:169
|
||||
msgid "Random City"
|
||||
msgstr "Musterstadt"
|
||||
|
||||
#: pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/editor.js:213
|
||||
#: pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/editor.js:170
|
||||
msgid ""
|
||||
"Addon 1\n"
|
||||
"Addon 2"
|
||||
msgstr ""
|
||||
"Workshop 1\n"
|
||||
"Workshop 2"
|
||||
|
||||
#: pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/editor.js:215
|
||||
msgid "The PDF background file could not be loaded for the following reason:"
|
||||
msgstr "Die Hintergrund-PDF-Datei konnte nicht geladen werden:"
|
||||
|
||||
#: pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/editor.js:362
|
||||
#: pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/editor.js:364
|
||||
msgid "Group of objects"
|
||||
msgstr "Gruppe von Objekten"
|
||||
|
||||
#: pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/editor.js:368
|
||||
#: pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/editor.js:370
|
||||
msgid "Text object"
|
||||
msgstr "Text-Objekt"
|
||||
|
||||
#: pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/editor.js:370
|
||||
#: pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/editor.js:372
|
||||
msgid "Barcode area"
|
||||
msgstr "QR-Code-Bereich"
|
||||
|
||||
#: pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/editor.js:372
|
||||
#: pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/editor.js:374
|
||||
msgid "Object"
|
||||
msgstr "Objekt"
|
||||
|
||||
#: pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/editor.js:376
|
||||
#: pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/editor.js:378
|
||||
msgid "Ticket design"
|
||||
msgstr "Ticket-Design"
|
||||
|
||||
#: pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/editor.js:612
|
||||
#: pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/editor.js:614
|
||||
msgid "Saving failed."
|
||||
msgstr "Speichern fehlgeschlagen."
|
||||
|
||||
#: pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/editor.js:644
|
||||
#: pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/editor.js:646
|
||||
msgid "Do you really want to leave the editor without saving your changes?"
|
||||
msgstr ""
|
||||
"Möchten Sie den Editor wirklich schließen ohne Ihre Änderungen zu speichern?"
|
||||
|
||||
#: pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/editor.js:657
|
||||
#: pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/editor.js:659
|
||||
msgid "Error while uploading your PDF file, please try again."
|
||||
msgstr ""
|
||||
"Es gab ein Problem beim Hochladen der PDF-Datei, bitte erneut versuchen."
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,8 +7,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2017-05-18 09:17+0000\n"
|
||||
"PO-Revision-Date: 2017-05-11 12:13+0200\n"
|
||||
"POT-Creation-Date: 2017-06-01 10:54+0000\n"
|
||||
"PO-Revision-Date: 2017-06-01 12:56+0200\n"
|
||||
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
||||
"Language-Team: \n"
|
||||
"Language: de\n"
|
||||
@@ -79,59 +79,71 @@ msgid "May 31st, 2017"
|
||||
msgstr "31. Mai 2017"
|
||||
|
||||
#: pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/editor.js:164
|
||||
msgid "May 31st – June 4th, 2017"
|
||||
msgstr "31. Mai – 4. Juni 2017"
|
||||
|
||||
#: pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/editor.js:165
|
||||
msgid "20:00"
|
||||
msgstr "20:00"
|
||||
|
||||
#: pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/editor.js:165
|
||||
#: pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/editor.js:166
|
||||
msgid "19:00"
|
||||
msgstr "19:00"
|
||||
|
||||
#: pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/editor.js:166
|
||||
#: pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/editor.js:167
|
||||
msgid "2017-05-31 20:00"
|
||||
msgstr "31.05.2016 20:00"
|
||||
|
||||
#: pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/editor.js:167
|
||||
#: pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/editor.js:168
|
||||
msgid "2017-05-31 19:00"
|
||||
msgstr "31.05.2016 19:00"
|
||||
|
||||
#: pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/editor.js:168
|
||||
#: pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/editor.js:169
|
||||
msgid "Random City"
|
||||
msgstr "Musterstadt"
|
||||
|
||||
#: pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/editor.js:213
|
||||
#: pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/editor.js:170
|
||||
msgid ""
|
||||
"Addon 1\n"
|
||||
"Addon 2"
|
||||
msgstr ""
|
||||
"Workshop 1\n"
|
||||
"Workshop 2"
|
||||
|
||||
#: pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/editor.js:215
|
||||
msgid "The PDF background file could not be loaded for the following reason:"
|
||||
msgstr "Die Hintergrund-PDF-Datei konnte nicht geladen werden:"
|
||||
|
||||
#: pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/editor.js:362
|
||||
#: pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/editor.js:364
|
||||
msgid "Group of objects"
|
||||
msgstr "Gruppe von Objekten"
|
||||
|
||||
#: pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/editor.js:368
|
||||
#: pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/editor.js:370
|
||||
msgid "Text object"
|
||||
msgstr "Text-Objekt"
|
||||
|
||||
#: pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/editor.js:370
|
||||
#: pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/editor.js:372
|
||||
msgid "Barcode area"
|
||||
msgstr "QR-Code-Bereich"
|
||||
|
||||
#: pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/editor.js:372
|
||||
#: pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/editor.js:374
|
||||
msgid "Object"
|
||||
msgstr "Objekt"
|
||||
|
||||
#: pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/editor.js:376
|
||||
#: pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/editor.js:378
|
||||
msgid "Ticket design"
|
||||
msgstr "Ticket-Design"
|
||||
|
||||
#: pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/editor.js:612
|
||||
#: pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/editor.js:614
|
||||
msgid "Saving failed."
|
||||
msgstr "Speichern fehlgeschlagen."
|
||||
|
||||
#: pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/editor.js:644
|
||||
#: pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/editor.js:646
|
||||
msgid "Do you really want to leave the editor without saving your changes?"
|
||||
msgstr ""
|
||||
"Möchtest du den Editor wirklich schließen ohne Ihre Änderungen zu speichern?"
|
||||
|
||||
#: pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/editor.js:657
|
||||
#: pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/editor.js:659
|
||||
msgid "Error while uploading your PDF file, please try again."
|
||||
msgstr ""
|
||||
"Es gab ein Problem beim Hochladen der PDF-Datei, bitte erneut versuchen."
|
||||
|
||||
@@ -2,8 +2,9 @@ import time
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.sessions.middleware import \
|
||||
SessionMiddleware as BaseSessionMiddleware
|
||||
from django.contrib.sessions.middleware import (
|
||||
SessionMiddleware as BaseSessionMiddleware,
|
||||
)
|
||||
from django.core.cache import cache
|
||||
from django.core.exceptions import DisallowedHost
|
||||
from django.core.urlresolvers import set_urlconf
|
||||
|
||||
@@ -13,6 +13,7 @@ def parse(data, hint):
|
||||
raise HintMismatchError('Invalid hint')
|
||||
if len(data[0]) != hint['cols']:
|
||||
raise HintMismatchError('Wrong column count')
|
||||
good_hint = False
|
||||
for row in data:
|
||||
resrow = {}
|
||||
if None in row or len(row) != hint['cols']:
|
||||
@@ -31,8 +32,10 @@ def parse(data, hint):
|
||||
or len(resrow['reference']) == 0 or resrow['date'] == '':
|
||||
# This is probably a headline or something other special.
|
||||
continue
|
||||
if resrow['reference'] or resrow['payer']:
|
||||
good_hint = True
|
||||
result.append(resrow)
|
||||
return result
|
||||
return result, good_hint
|
||||
|
||||
|
||||
def get_rows_from_file(file):
|
||||
|
||||
@@ -20,7 +20,7 @@ def parse(file):
|
||||
result.append({
|
||||
'reference': "\n".join([
|
||||
t.data.get(f) for f in ('transaction_details', 'customer_reference', 'bank_reference',
|
||||
'extra_details') if t.data.get(f, '')]),
|
||||
'extra_details', 'non_swift_text') if t.data.get(f, '')]),
|
||||
'amount': str(round_decimal(t.data['amount'].amount)),
|
||||
'date': t.data['date'].isoformat()
|
||||
})
|
||||
|
||||
@@ -84,7 +84,7 @@ def _get_unknown_transactions(event: Event, job: BankImportJob, data: list):
|
||||
amount = Decimal("0.00")
|
||||
|
||||
trans = BankTransaction(event=event, import_job=job,
|
||||
payer=row['payer'],
|
||||
payer=row.get('payer', ''),
|
||||
reference=row['reference'],
|
||||
amount=amount,
|
||||
date=row['date'])
|
||||
|
||||
@@ -14,9 +14,15 @@
|
||||
<dd>{{ payment_info.reference }}</dd>
|
||||
</dl>
|
||||
{% else %}
|
||||
<p>{% blocktrans trimmed %}
|
||||
This order has been planned to be paid via bank transfer, but no payment has been received yet.
|
||||
{% endblocktrans %}</p>
|
||||
{% if order.status == "p" %}
|
||||
<p>{% blocktrans trimmed %}
|
||||
This order has been marked as paid via bank transfer manually.
|
||||
{% endblocktrans %}</p>
|
||||
{% else %}
|
||||
<p>{% blocktrans trimmed %}
|
||||
This order has been planned to be paid via bank transfer, but no payment has been received yet.
|
||||
{% endblocktrans %}</p>
|
||||
{% endif %}
|
||||
<dl class="dl-horizontal">
|
||||
<dt>{% trans "Reference code" %}</dt>
|
||||
<dd>{{ order.full_code }}</dd>
|
||||
|
||||
@@ -275,12 +275,14 @@ class ImportView(EventPermissionRequiredMixin, ListView):
|
||||
|
||||
if self.request.event.settings.get('banktransfer_csvhint') is not None:
|
||||
hint = self.request.event.settings.get('banktransfer_csvhint', as_type=dict)
|
||||
|
||||
try:
|
||||
parsed = csvimport.parse(data, hint)
|
||||
parsed, good = csvimport.parse(data, hint)
|
||||
except csvimport.HintMismatchError: # TODO: narrow down
|
||||
logger.exception('Import using stored hint failed')
|
||||
else:
|
||||
return self.start_processing(parsed)
|
||||
if good:
|
||||
return self.start_processing(parsed)
|
||||
|
||||
return self.assign_view(data)
|
||||
|
||||
@@ -308,7 +310,7 @@ class ImportView(EventPermissionRequiredMixin, ListView):
|
||||
logger.error('Import using stored hint failed: ' + str(e))
|
||||
pass
|
||||
else:
|
||||
parsed = csvimport.parse(data, hint)
|
||||
parsed, __ = csvimport.parse(data, hint)
|
||||
return self.start_processing(parsed)
|
||||
|
||||
def process_csv(self):
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import csv
|
||||
import io
|
||||
from collections import OrderedDict
|
||||
|
||||
from defusedcsv import csv
|
||||
from django import forms
|
||||
from django.db.models.functions import Coalesce
|
||||
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||
@@ -110,7 +110,7 @@ class CSVCheckinList(BaseCheckinList):
|
||||
if form_data['secrets']:
|
||||
row.append(op.secret)
|
||||
if self.event.settings.attendee_emails_asked:
|
||||
row.append(op.attendee_email or (op.addon_to.attendee_name if op.addon_to else ''))
|
||||
row.append(op.attendee_email or (op.addon_to.attendee_email if op.addon_to else ''))
|
||||
acache = {}
|
||||
for a in op.answers.all():
|
||||
acache[a.question_id] = str(a)
|
||||
|
||||
@@ -17,8 +17,9 @@ from django.views.generic import TemplateView, View
|
||||
from pretix.base.models import Checkin, Event, Order, OrderPosition
|
||||
from pretix.control.permissions import EventPermissionRequiredMixin
|
||||
from pretix.helpers.urls import build_absolute_uri
|
||||
from pretix.multidomain.urlreverse import \
|
||||
build_absolute_uri as event_absolute_uri
|
||||
from pretix.multidomain.urlreverse import (
|
||||
build_absolute_uri as event_absolute_uri,
|
||||
)
|
||||
|
||||
logger = logging.getLogger('pretix.plugins.pretixdroid')
|
||||
API_VERSION = 3
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
{% load i18n %}
|
||||
{% load compress %}
|
||||
{% load staticfiles %}
|
||||
{% load escapejson %}
|
||||
{% block title %}{% trans "Statistics" %}{% endblock %}
|
||||
{% block content %}
|
||||
<h1>{% trans "Statistics" %}</h1>
|
||||
@@ -30,9 +31,9 @@
|
||||
<div id="obp_chart" class="chart"></div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="application/json" id="obd-data">{{ obd_data|safe }}</script>
|
||||
<script type="application/json" id="rev-data">{{ rev_data|safe }}</script>
|
||||
<script type="application/json" id="obp-data">{{ obp_data|safe }}</script>
|
||||
<script type="application/json" id="obd-data">{{ obd_data|escapejson }}</script>
|
||||
<script type="application/json" id="rev-data">{{ rev_data|escapejson }}</script>
|
||||
<script type="application/json" id="obp-data">{{ obp_data|escapejson }}</script>
|
||||
<script type="application/text" id="currency">{{ request.event.currency }}</script>
|
||||
<script type="application/javascript" src="{% static "pretixplugins/statistics/statistics.js" %}"></script>
|
||||
{% else %}
|
||||
|
||||
@@ -161,11 +161,13 @@ var editor = {
|
||||
"attendee_name": gettext("John Doe"),
|
||||
"event_name": gettext("Sample event name"),
|
||||
"event_date": gettext("May 31st, 2017"),
|
||||
"event_date_range": gettext("May 31st – June 4th, 2017"),
|
||||
"event_begin_time": gettext("20:00"),
|
||||
"event_admission_time": gettext("19:00"),
|
||||
"event_begin": gettext("2017-05-31 20:00"),
|
||||
"event_admission": gettext("2017-05-31 19:00"),
|
||||
"event_location": gettext("Random City")
|
||||
"event_location": gettext("Random City"),
|
||||
"addons": gettext("Addon 1\nAddon 2"),
|
||||
},
|
||||
|
||||
_load_pdf: function (dump) {
|
||||
|
||||
@@ -267,11 +267,13 @@
|
||||
<option value="attendee_name">{% trans "Attendee name" %}</option>
|
||||
<option value="event_name">{% trans "Event name" %}</option>
|
||||
<option value="event_date">{% trans "Event date" %}</option>
|
||||
<option value="event_date_range">{% trans "Event date range" %}</option>
|
||||
<option value="event_begin">{% trans "Event begin date and time" %}</option>
|
||||
<option value="event_begin_time">{% trans "Event begin time" %}</option>
|
||||
<option value="event_admission">{% trans "Event admission date and time" %}</option>
|
||||
<option value="event_admission_time">{% trans "Event admission time" %}</option>
|
||||
<option value="event_location">{% trans "Event location" %}</option>
|
||||
<option value="addons">{% trans "List of Add-Ons" %}</option>
|
||||
<option value="other">{% trans "Other…" %}</option>
|
||||
</select>
|
||||
<textarea type="text" value="" class="input-block-level form-control"
|
||||
|
||||
@@ -85,6 +85,8 @@ class PdfTicketOutput(BaseTicketOutput):
|
||||
return str(order.event.location).replace("\n", "<br/>\n")
|
||||
elif o['content'] == 'event_date':
|
||||
return order.event.get_date_from_display(show_times=False)
|
||||
elif o['content'] == 'event_date_range':
|
||||
return order.event.get_date_range_display()
|
||||
elif o['content'] == 'event_begin':
|
||||
return order.event.get_date_from_display(show_times=True)
|
||||
elif o['content'] == 'event_begin_time':
|
||||
@@ -97,6 +99,11 @@ class PdfTicketOutput(BaseTicketOutput):
|
||||
if order.event.date_admission:
|
||||
tz = timezone(order.event.settings.timezone)
|
||||
return date_format(order.event.date_admission.astimezone(tz), "TIME_FORMAT")
|
||||
elif o['content'] == 'addons':
|
||||
return "<br/>".join([
|
||||
'{} - {}'.format(p.item, p.variation) if p.variation else str(p.item)
|
||||
for p in op.addons.select_related('item', 'variation')
|
||||
])
|
||||
return ''
|
||||
|
||||
def _draw_textarea(self, canvas: Canvas, op: OrderPosition, order: Order, o: dict):
|
||||
@@ -226,9 +233,9 @@ class PdfTicketOutput(BaseTicketOutput):
|
||||
"fontfamily": "Open Sans", "bold": False, "italic": False, "width": "110.00", "content": "attendee_name",
|
||||
"text": "John Doe", "align": "left"},
|
||||
{"type": "textarea", "left": "17.50", "bottom": "242.10", "fontsize": "13.0", "color": [0, 0, 0, 1],
|
||||
"fontfamily": "Open Sans", "bold": False, "italic": False, "width": "110.00", "content": "event_date",
|
||||
"text": "May 31st, 2017", "align": "left"},
|
||||
{"type": "textarea", "left": "17.50", "bottom": "234.30", "fontsize": "13.0", "color": [0, 0, 0, 1],
|
||||
"fontfamily": "Open Sans", "bold": False, "italic": False, "width": "110.00",
|
||||
"content": "event_date_range", "text": "May 31st, 2017", "align": "left"},
|
||||
{"type": "textarea", "left": "17.50", "bottom": "204.80", "fontsize": "13.0", "color": [0, 0, 0, 1],
|
||||
"fontfamily": "Open Sans", "bold": False, "italic": False, "width": "110.00", "content": "event_location",
|
||||
"text": "Random City", "align": "left"},
|
||||
{"type": "textarea", "left": "17.50", "bottom": "194.50", "fontsize": "13.0", "color": [0, 0, 0, 1],
|
||||
|
||||
@@ -86,6 +86,7 @@ class EditorView(EventPermissionRequiredMixin, ChartContainingView, TemplateView
|
||||
if "preview" in request.POST:
|
||||
with rolledback_transaction(), language(request.event.settings.locale):
|
||||
item = request.event.items.create(name=_("Sample product"), default_price=42.23)
|
||||
item2 = request.event.items.create(name=_("Sample workshop"), default_price=23.40)
|
||||
|
||||
from pretix.base.models import Order
|
||||
order = request.event.orders.create(status=Order.STATUS_PENDING, datetime=now(),
|
||||
@@ -93,6 +94,8 @@ class EditorView(EventPermissionRequiredMixin, ChartContainingView, TemplateView
|
||||
expires=now(), code="PREVIEW1234", total=119)
|
||||
|
||||
p = order.positions.create(item=item, attendee_name=_("John Doe"), price=item.default_price)
|
||||
order.positions.create(item=item2, attendee_name=_("John Doe"), price=item.default_price, addon_to=p)
|
||||
order.positions.create(item=item2, attendee_name=_("John Doe"), price=item.default_price, addon_to=p)
|
||||
|
||||
prov = PdfTicketOutput(request.event,
|
||||
override_layout=(json.loads(request.POST.get("data"))
|
||||
@@ -102,8 +105,7 @@ class EditorView(EventPermissionRequiredMixin, ChartContainingView, TemplateView
|
||||
|
||||
resp = HttpResponse(data, content_type=mimet)
|
||||
ftype = fname.split(".")[-1]
|
||||
resp['Content-Security-Policy'] = "style-src 'unsafe-inline'; script-src 'unsafe-inline'; object-src 'self'"
|
||||
resp['Content-Disposition'] = 'inline; filename="ticket-preview.{}"'.format(ftype)
|
||||
resp['Content-Disposition'] = 'attachment; filename="ticket-preview.{}"'.format(ftype)
|
||||
return resp
|
||||
elif "data" in request.POST:
|
||||
if cf:
|
||||
|
||||
@@ -19,7 +19,8 @@ from pretix.presale.forms.checkout import (
|
||||
AddOnsForm, ContactForm, InvoiceAddressForm,
|
||||
)
|
||||
from pretix.presale.signals import (
|
||||
checkout_confirm_messages, checkout_flow_steps, order_meta_from_request,
|
||||
checkout_confirm_messages, checkout_flow_steps, contact_form_fields,
|
||||
order_meta_from_request,
|
||||
)
|
||||
from pretix.presale.views import CartMixin, get_cart, get_cart_total
|
||||
from pretix.presale.views.async import AsyncAction
|
||||
@@ -255,10 +256,13 @@ class QuestionsStep(QuestionsViewMixin, CartMixin, TemplateFlowStep):
|
||||
|
||||
@cached_property
|
||||
def contact_form(self):
|
||||
initial = {
|
||||
'email': self.request.session.get('email', '')
|
||||
}
|
||||
initial.update(self.request.session.get('contact_form_data', {}))
|
||||
return ContactForm(data=self.request.POST if self.request.method == "POST" else None,
|
||||
initial={
|
||||
'email': self.request.session.get('email', '')
|
||||
})
|
||||
event=self.request.event,
|
||||
initial=initial)
|
||||
|
||||
@cached_property
|
||||
def invoice_address(self):
|
||||
@@ -290,6 +294,7 @@ class QuestionsStep(QuestionsViewMixin, CartMixin, TemplateFlowStep):
|
||||
if request.event.settings.invoice_address_asked:
|
||||
addr = self.invoice_form.save()
|
||||
request.session['invoice_address'] = addr.pk
|
||||
request.session['contact_form_data'] = self.contact_form.cleaned_data
|
||||
|
||||
return redirect(self.get_next_url(request))
|
||||
|
||||
@@ -435,6 +440,18 @@ class ConfirmStep(CartMixin, AsyncAction, TemplateFlowStep):
|
||||
ctx['payment_provider'] = self.payment_provider
|
||||
ctx['addr'] = self.invoice_address
|
||||
ctx['confirm_messages'] = self.confirm_messages
|
||||
|
||||
ctx['contact_info'] = []
|
||||
responses = contact_form_fields.send(self.event)
|
||||
for r, response in sorted(responses, key=lambda r: str(r[0])):
|
||||
for key, value in response.items():
|
||||
v = self.request.session.get('contact_form_data', {}).get(key)
|
||||
if v is True:
|
||||
v = _('Yes')
|
||||
elif v is False:
|
||||
v = _('No')
|
||||
ctx['contact_info'].append((value.label, v))
|
||||
|
||||
return ctx
|
||||
|
||||
@cached_property
|
||||
@@ -485,9 +502,12 @@ class ConfirmStep(CartMixin, AsyncAction, TemplateFlowStep):
|
||||
})
|
||||
return redirect(self.get_error_url())
|
||||
|
||||
meta_info = {}
|
||||
meta_info = {
|
||||
'contact_form_data': self.request.session.get('contact_form_data', {})
|
||||
}
|
||||
for receiver, response in order_meta_from_request.send(sender=request.event, request=request):
|
||||
meta_info.update(response)
|
||||
|
||||
return self.do(self.request.event.id, self.payment_provider.identifier,
|
||||
[p.id for p in self.positions], request.session.get('email'),
|
||||
translation.get_language(), self.invoice_address.pk, meta_info)
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
from decimal import Decimal
|
||||
from itertools import chain
|
||||
|
||||
from django import forms
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db.models import Count, Prefetch, Q
|
||||
from django.forms.widgets import RadioChoiceInput, RadioFieldRenderer
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.formats import number_format
|
||||
from django.utils.html import format_html
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from pretix.base.models import ItemVariation, Question
|
||||
from pretix.base.models.orders import InvoiceAddress
|
||||
from pretix.base.templatetags.rich_text import rich_text
|
||||
from pretix.presale.signals import contact_form_fields
|
||||
|
||||
|
||||
class ContactForm(forms.Form):
|
||||
@@ -23,6 +22,16 @@ class ContactForm(forms.Form):
|
||||
'modifications to your order or download your ticket later.'),
|
||||
widget=forms.EmailInput(attrs={'data-typocheck-target': '1'}))
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.event = kwargs.pop('event')
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
responses = contact_form_fields.send(self.event)
|
||||
for r, response in sorted(responses, key=lambda r: str(r[0])):
|
||||
for key, value in response.items():
|
||||
# We need to be this explicit, since OrderedDict.update does not retain ordering
|
||||
self.fields[key] = value
|
||||
|
||||
|
||||
class InvoiceAddressForm(forms.ModelForm):
|
||||
|
||||
@@ -155,51 +164,35 @@ class QuestionsForm(forms.Form):
|
||||
self.fields['question_%s' % q.id] = field
|
||||
|
||||
|
||||
# The following will get totally different once Django 1.11 is integrated
|
||||
class AddOnVariationSelectInput(RadioChoiceInput):
|
||||
class AddOnRadioSelect(forms.RadioSelect):
|
||||
option_template_name = 'pretixpresale/forms/addon_choice_option.html'
|
||||
|
||||
def __init__(self, name, value, attrs, choice, index):
|
||||
super().__init__(name, value, attrs, choice, index)
|
||||
self.description = force_text(choice[2])
|
||||
def optgroups(self, name, value, attrs=None):
|
||||
attrs = attrs or {}
|
||||
groups = []
|
||||
has_selected = False
|
||||
for index, (option_value, option_label, option_desc) in enumerate(chain(self.choices)):
|
||||
if option_value is None:
|
||||
option_value = ''
|
||||
if isinstance(option_label, (list, tuple)):
|
||||
raise TypeError('Choice groups are not supported here')
|
||||
group_name = None
|
||||
subgroup = []
|
||||
groups.append((group_name, subgroup, index))
|
||||
|
||||
def render(self, name=None, value=None, attrs=None):
|
||||
if self.id_for_label:
|
||||
label_for = format_html(' for="{}"', self.id_for_label)
|
||||
else:
|
||||
label_for = ''
|
||||
attrs = dict(self.attrs, **attrs) if attrs else self.attrs
|
||||
if self.description:
|
||||
return format_html(
|
||||
'<label{}>{} {}</label> <span class="fa fa-info-circle toggle-variation-description"></span>'
|
||||
'<div class="variation-description addon-variation-description">{}</div>',
|
||||
label_for, self.tag(attrs), self.choice_label,
|
||||
rich_text(str(self.description))
|
||||
)
|
||||
else:
|
||||
return format_html(
|
||||
'<label{}>{} {}</label>',
|
||||
label_for, self.tag(attrs), self.choice_label,
|
||||
selected = (
|
||||
force_text(option_value) in value and
|
||||
(has_selected is False or self.allow_multiple_selected)
|
||||
)
|
||||
if selected is True and has_selected is False:
|
||||
has_selected = True
|
||||
attrs['description'] = option_desc
|
||||
subgroup.append(self.create_option(
|
||||
name, option_value, option_label, selected, index,
|
||||
subindex=None, attrs=attrs,
|
||||
))
|
||||
|
||||
|
||||
class AddOnVariationSelectRenderer(RadioFieldRenderer):
|
||||
choice_input_class = AddOnVariationSelectInput
|
||||
|
||||
def render(self):
|
||||
id_ = self.attrs.get('id')
|
||||
output = []
|
||||
for i, choice in enumerate(self.choices):
|
||||
w = self.choice_input_class(self.name, self.value, self.attrs.copy(), choice, i)
|
||||
output.append(format_html(self.inner_html, choice_value=force_text(w), sub_widgets=''))
|
||||
return format_html(
|
||||
self.outer_html,
|
||||
id_attr=format_html(' id="{}"', id_) if id_ else '',
|
||||
content=mark_safe('\n'.join(output)),
|
||||
)
|
||||
|
||||
|
||||
class AddOnVariationSelect(forms.RadioSelect):
|
||||
renderer = AddOnVariationSelectRenderer
|
||||
return groups
|
||||
|
||||
|
||||
class AddOnVariationField(forms.ChoiceField):
|
||||
@@ -307,7 +300,7 @@ class AddOnsForm(forms.Form):
|
||||
choices=choices,
|
||||
label=i.name,
|
||||
required=False,
|
||||
widget=AddOnVariationSelect,
|
||||
widget=AddOnRadioSelect,
|
||||
help_text=rich_text(str(i.description)),
|
||||
initial=current_addons.get(i.pk),
|
||||
)
|
||||
|
||||
@@ -69,6 +69,18 @@ You will recieve the request triggering the order creation as the ``request`` ke
|
||||
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
|
||||
"""
|
||||
|
||||
contact_form_fields = EventPluginSignal(
|
||||
providing_args=[]
|
||||
)
|
||||
"""
|
||||
This signals allows you to add form fields to the contact form that is presented during checkout
|
||||
and by default only asks for the email address. You are supposed to return a dictionary of
|
||||
form fields with globally unique keys. The validated form results will be saved into the
|
||||
``contact_form_data`` entry of the order metadata dictionary.
|
||||
|
||||
As with all plugin signals, the ``sender`` keyword argument will contain the event.
|
||||
"""
|
||||
|
||||
order_info = EventPluginSignal(
|
||||
providing_args=["order"]
|
||||
)
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<head>
|
||||
<title>{% block thetitle %}{% endblock %}</title>
|
||||
{% compress css %}
|
||||
<link rel="stylesheet" type="text/css" href="{% static "lightbox/css/lightbox.css" %}" />
|
||||
<link rel="stylesheet" type="text/x-scss" href="{% static "lightbox/css/lightbox.scss" %}" />
|
||||
{% endcompress %}
|
||||
{% if css_file %}
|
||||
<link rel="stylesheet" type="text/css" href="{{ css_file }}"/>
|
||||
|
||||
@@ -111,6 +111,12 @@
|
||||
<dt>{% trans "E-mail address" %}</dt>
|
||||
<dd>{{ request.session.email }}</dd>
|
||||
</dl>
|
||||
{% for l, v in contact_info %}
|
||||
<dl class="dl-horizontal">
|
||||
<dt>{{ l }}</dt>
|
||||
<dd>{{ v }}</dd>
|
||||
</dl>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -132,7 +132,8 @@
|
||||
<div class="col-md-8 col-xs-12">
|
||||
{% if item.picture %}
|
||||
<a href="{{ item.picture.url }}" class="productpicture"
|
||||
data-title="{{ item.name }}"
|
||||
data-title="{{ item.name|force_escape|force_escape }}"
|
||||
{# Yes, double-escape to prevent XSS in lightbox #}
|
||||
data-lightbox="{{ item.id }}">
|
||||
<img src="{{ item.picture|thumbnail_url:'productlist' }}"
|
||||
alt="{{ item.name }}"/>
|
||||
@@ -243,7 +244,7 @@
|
||||
<div class="col-md-8 col-xs-12">
|
||||
{% if item.picture %}
|
||||
<a href="{{ item.picture.url }}" class="productpicture"
|
||||
data-title="{{ item.name }}"
|
||||
data-title="{{ item.name|force_escape|force_escape }}"
|
||||
data-lightbox="{{ item.id }}">
|
||||
<img src="{{ item.picture|thumbnail_url:'productlist' }}"
|
||||
alt="{{ item.name }}"/>
|
||||
|
||||
@@ -29,7 +29,8 @@
|
||||
<div class="col-md-8 col-xs-12">
|
||||
{% if item.picture %}
|
||||
<a href="{{ item.picture.url }}" class="productpicture"
|
||||
data-title="{{ item.name }}"
|
||||
data-title="{{ item.name|force_escape|force_escape }}"
|
||||
{# Yes, double-escape to prevent XSS in lightbox #}
|
||||
data-lightbox="{{ item.id }}">
|
||||
<img src="{{ item.picture|thumbnail_url:'productlist' }}"
|
||||
alt="{{ item.name }}"/>
|
||||
@@ -116,7 +117,8 @@
|
||||
<div class="col-md-8 col-xs-12">
|
||||
{% if item.picture %}
|
||||
<a href="{{ item.picture.url }}" class="productpicture"
|
||||
data-title="{{ item.name }}"
|
||||
data-title="{{ item.name|force_escape|force_escape }}"
|
||||
{# Yes, double-escape to prevent XSS in lightbox #}
|
||||
data-lightbox="{{ item.id }}">
|
||||
<img src="{{ item.picture|thumbnail_url:'productlist' }}"
|
||||
alt="{{ item.name }}"/>
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{% load rich_text %}
|
||||
<label{% if widget.attrs.id %} for="{{ widget.attrs.id }}"{% endif %}>{% include "django/forms/widgets/input.html" %} {{ widget.label }}</label> {% if widget.attrs.description %}<span class="fa fa-info-circle toggle-variation-description"></span>
|
||||
<div class="variation-description addon-variation-description">{{ widget.attrs.description|rich_text }}</div>{% endif %}
|
||||
@@ -581,15 +581,9 @@ class OrderDownload(EventViewMixin, OrderDetailMixin, View):
|
||||
return render(self.request, "pretixbase/cachedfiles/pending.html", {})
|
||||
else:
|
||||
resp = FileResponse(ct.file.file, content_type=ct.type)
|
||||
if ct.type == "application/pdf":
|
||||
resp['Content-Security-Policy'] = "style-src 'unsafe-inline'; script-src 'unsafe-inline'; object-src 'self'"
|
||||
resp['Content-Disposition'] = 'inline; filename="{}-{}-{}{}"'.format(
|
||||
self.request.event.slug.upper(), self.order.code, self.output.identifier, ct.extension
|
||||
)
|
||||
else:
|
||||
resp['Content-Disposition'] = 'attachment; filename="{}-{}-{}{}"'.format(
|
||||
self.request.event.slug.upper(), self.order.code, self.output.identifier, ct.extension
|
||||
)
|
||||
resp['Content-Disposition'] = 'attachment; filename="{}-{}-{}{}"'.format(
|
||||
self.request.event.slug.upper(), self.order.code, self.output.identifier, ct.extension
|
||||
)
|
||||
return resp
|
||||
|
||||
def _download_position(self):
|
||||
@@ -620,17 +614,10 @@ class OrderDownload(EventViewMixin, OrderDetailMixin, View):
|
||||
return render(self.request, "pretixbase/cachedfiles/pending.html", {})
|
||||
else:
|
||||
resp = FileResponse(ct.file.file, content_type=ct.type)
|
||||
if ct.type == "application/pdf":
|
||||
resp['Content-Security-Policy'] = "style-src 'unsafe-inline'; script-src 'unsafe-inline'; object-src 'self'"
|
||||
resp['Content-Disposition'] = 'inline; filename="{}-{}-{}-{}{}"'.format(
|
||||
self.request.event.slug.upper(), self.order.code, self.order_position.positionid,
|
||||
self.output.identifier, ct.extension
|
||||
)
|
||||
else:
|
||||
resp['Content-Disposition'] = 'attachment; filename="{}-{}-{}-{}{}"'.format(
|
||||
self.request.event.slug.upper(), self.order.code, self.order_position.positionid,
|
||||
self.output.identifier, ct.extension
|
||||
)
|
||||
resp['Content-Disposition'] = 'attachment; filename="{}-{}-{}-{}{}"'.format(
|
||||
self.request.event.slug.upper(), self.order.code, self.order_position.positionid,
|
||||
self.output.identifier, ct.extension
|
||||
)
|
||||
return resp
|
||||
|
||||
|
||||
@@ -660,6 +647,5 @@ class InvoiceDownload(EventViewMixin, OrderDetailMixin, View):
|
||||
return redirect(self.get_order_url())
|
||||
|
||||
resp = FileResponse(invoice.file.file, content_type='application/pdf')
|
||||
resp['Content-Security-Policy'] = "style-src 'unsafe-inline'; script-src 'unsafe-inline'; object-src 'self'"
|
||||
resp['Content-Disposition'] = 'inline; filename="{}.pdf"'.format(invoice.number)
|
||||
resp['Content-Disposition'] = 'attachment; filename="{}.pdf"'.format(invoice.number)
|
||||
return resp
|
||||
|
||||
@@ -83,7 +83,7 @@ PRETIX_PASSWORD_RESET = config.getboolean('pretix', 'password_reset', fallback=T
|
||||
SITE_URL = config.get('pretix', 'url', fallback='http://localhost')
|
||||
|
||||
PRETIX_PLUGINS_DEFAULT = config.get('pretix', 'plugins_default',
|
||||
fallback='pretix.plugins.sendmail,pretix.plugins.statistics')
|
||||
fallback='pretix.plugins.sendmail,pretix.plugins.statistics,pretix.plugins.checkinlists')
|
||||
|
||||
DEFAULT_CURRENCY = config.get('pretix', 'currency', fallback='EUR')
|
||||
CURRENCIES = list(currencies)
|
||||
@@ -258,7 +258,7 @@ try:
|
||||
import debug_toolbar # noqa
|
||||
if DEBUG:
|
||||
INSTALLED_APPS.append('debug_toolbar.apps.DebugToolbarConfig')
|
||||
MIDDLEWARE.insert(0, 'pretix.helpers.debug.DebugMiddlewareCompatibilityShim')
|
||||
MIDDLEWARE.insert(0, 'debug_toolbar.middleware.DebugToolbarMiddleware')
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
@@ -376,7 +376,9 @@ COMPRESS_PRECOMPILERS = (
|
||||
COMPRESS_ENABLED = COMPRESS_OFFLINE = not debug_fallback
|
||||
|
||||
COMPRESS_CSS_FILTERS = (
|
||||
'compressor.filters.css_default.CssAbsoluteFilter',
|
||||
# CssAbsoluteFilter is incredibly slow, especially when dealing with our _flags.scss
|
||||
# However, we don't need it if we consequently use the static() function in Sass
|
||||
# 'compressor.filters.css_default.CssAbsoluteFilter',
|
||||
'compressor.filters.cssmin.CSSCompressorFilter',
|
||||
)
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* @license
|
||||
morris.js v0.5.0
|
||||
morris.js v0.5.1
|
||||
Copyright 2014 Olly Smith All rights reserved.
|
||||
Licensed under the BSD-2-Clause License.
|
||||
*/
|
||||
@@ -74,6 +74,7 @@ Licensed under the BSD-2-Clause License.
|
||||
__extends(Grid, _super);
|
||||
|
||||
function Grid(options) {
|
||||
this.hasToShow = __bind(this.hasToShow, this);
|
||||
this.resizeHandler = __bind(this.resizeHandler, this);
|
||||
var _this = this;
|
||||
if (typeof options.element === 'string') {
|
||||
@@ -197,7 +198,7 @@ Licensed under the BSD-2-Clause License.
|
||||
};
|
||||
|
||||
Grid.prototype.setData = function(data, redraw) {
|
||||
var e, idx, index, maxGoal, minGoal, ret, row, step, total, y, ykey, ymax, ymin, yval, _ref;
|
||||
var e, flatEvents, from, idx, index, maxGoal, minGoal, ret, row, step, to, total, y, ykey, ymax, ymin, yval, _i, _len, _ref, _ref1;
|
||||
if (redraw == null) {
|
||||
redraw = true;
|
||||
}
|
||||
@@ -254,7 +255,7 @@ Licensed under the BSD-2-Clause License.
|
||||
if ((yval != null) && typeof yval !== 'number') {
|
||||
yval = null;
|
||||
}
|
||||
if (yval != null) {
|
||||
if ((yval != null) && this.hasToShow(idx)) {
|
||||
if (this.cumulative) {
|
||||
total += yval;
|
||||
} else {
|
||||
@@ -288,21 +289,24 @@ Licensed under the BSD-2-Clause License.
|
||||
this.events = [];
|
||||
if (this.options.events.length > 0) {
|
||||
if (this.options.parseTime) {
|
||||
this.events = (function() {
|
||||
var _i, _len, _ref, _results;
|
||||
_ref = this.options.events;
|
||||
_results = [];
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
e = _ref[_i];
|
||||
_results.push(Morris.parseDate(e));
|
||||
_ref = this.options.events;
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
e = _ref[_i];
|
||||
if (e instanceof Array) {
|
||||
from = e[0], to = e[1];
|
||||
this.events.push([Morris.parseDate(from), Morris.parseDate(to)]);
|
||||
} else {
|
||||
this.events.push(Morris.parseDate(e));
|
||||
}
|
||||
return _results;
|
||||
}).call(this);
|
||||
}
|
||||
} else {
|
||||
this.events = this.options.events;
|
||||
}
|
||||
this.xmax = Math.max(this.xmax, Math.max.apply(Math, this.events));
|
||||
this.xmin = Math.min(this.xmin, Math.min.apply(Math, this.events));
|
||||
flatEvents = $.map(this.events, function(e) {
|
||||
return e;
|
||||
});
|
||||
this.xmax = Math.max(this.xmax, Math.max.apply(Math, flatEvents));
|
||||
this.xmin = Math.min(this.xmin, Math.min.apply(Math, flatEvents));
|
||||
}
|
||||
if (this.xmin === this.xmax) {
|
||||
this.xmin -= 1;
|
||||
@@ -316,7 +320,7 @@ Licensed under the BSD-2-Clause License.
|
||||
}
|
||||
this.ymax += 1;
|
||||
}
|
||||
if (((_ref = this.options.axes) === true || _ref === 'both' || _ref === 'y') || this.options.grid === true) {
|
||||
if (((_ref1 = this.options.axes) === true || _ref1 === 'both' || _ref1 === 'y') || this.options.grid === true) {
|
||||
if (this.options.ymax === this.gridDefaults.ymax && this.options.ymin === this.gridDefaults.ymin) {
|
||||
this.grid = this.autoGridLines(this.ymin, this.ymax, this.options.numLines);
|
||||
this.ymin = Math.min(this.ymin, this.grid[0]);
|
||||
@@ -324,9 +328,9 @@ Licensed under the BSD-2-Clause License.
|
||||
} else {
|
||||
step = (this.ymax - this.ymin) / (this.options.numLines - 1);
|
||||
this.grid = (function() {
|
||||
var _i, _ref1, _ref2, _results;
|
||||
var _j, _ref2, _ref3, _results;
|
||||
_results = [];
|
||||
for (y = _i = _ref1 = this.ymin, _ref2 = this.ymax; step > 0 ? _i <= _ref2 : _i >= _ref2; y = _i += step) {
|
||||
for (y = _j = _ref2 = this.ymin, _ref3 = this.ymax; step > 0 ? _j <= _ref3 : _j >= _ref3; y = _j += step) {
|
||||
_results.push(y);
|
||||
}
|
||||
return _results;
|
||||
@@ -405,7 +409,7 @@ Licensed under the BSD-2-Clause License.
|
||||
};
|
||||
|
||||
Grid.prototype._calc = function() {
|
||||
var bottomOffsets, gridLine, h, i, w, yLabelWidths, _ref, _ref1;
|
||||
var angle, bottomOffsets, gridLine, h, i, w, yLabelWidths, _ref, _ref1;
|
||||
w = this.el.width();
|
||||
h = this.el.height();
|
||||
if (this.elementWidth !== w || this.elementHeight !== h || this.dirty) {
|
||||
@@ -427,23 +431,53 @@ Licensed under the BSD-2-Clause License.
|
||||
}
|
||||
return _results;
|
||||
}).call(this);
|
||||
this.left += Math.max.apply(Math, yLabelWidths);
|
||||
if (!this.options.horizontal) {
|
||||
this.left += Math.max.apply(Math, yLabelWidths);
|
||||
} else {
|
||||
this.bottom -= Math.max.apply(Math, yLabelWidths);
|
||||
}
|
||||
}
|
||||
if ((_ref1 = this.options.axes) === true || _ref1 === 'both' || _ref1 === 'x') {
|
||||
if (!this.options.horizontal) {
|
||||
angle = -this.options.xLabelAngle;
|
||||
} else {
|
||||
angle = -90;
|
||||
}
|
||||
bottomOffsets = (function() {
|
||||
var _i, _ref2, _results;
|
||||
_results = [];
|
||||
for (i = _i = 0, _ref2 = this.data.length; 0 <= _ref2 ? _i < _ref2 : _i > _ref2; i = 0 <= _ref2 ? ++_i : --_i) {
|
||||
_results.push(this.measureText(this.data[i].text, -this.options.xLabelAngle).height);
|
||||
_results.push(this.measureText(this.data[i].label, angle).height);
|
||||
}
|
||||
return _results;
|
||||
}).call(this);
|
||||
this.bottom -= Math.max.apply(Math, bottomOffsets);
|
||||
if (!this.options.horizontal) {
|
||||
this.bottom -= Math.max.apply(Math, bottomOffsets);
|
||||
} else {
|
||||
this.left += Math.max.apply(Math, bottomOffsets);
|
||||
}
|
||||
}
|
||||
this.width = Math.max(1, this.right - this.left);
|
||||
this.height = Math.max(1, this.bottom - this.top);
|
||||
this.dx = this.width / (this.xmax - this.xmin);
|
||||
this.dy = this.height / (this.ymax - this.ymin);
|
||||
if (!this.options.horizontal) {
|
||||
this.dx = this.width / (this.xmax - this.xmin);
|
||||
this.dy = this.height / (this.ymax - this.ymin);
|
||||
this.yStart = this.bottom;
|
||||
this.yEnd = this.top;
|
||||
this.xStart = this.left;
|
||||
this.xEnd = this.right;
|
||||
this.xSize = this.width;
|
||||
this.ySize = this.height;
|
||||
} else {
|
||||
this.dx = this.height / (this.xmax - this.xmin);
|
||||
this.dy = this.width / (this.ymax - this.ymin);
|
||||
this.yStart = this.left;
|
||||
this.yEnd = this.right;
|
||||
this.xStart = this.top;
|
||||
this.xEnd = this.bottom;
|
||||
this.xSize = this.height;
|
||||
this.ySize = this.width;
|
||||
}
|
||||
if (this.calc) {
|
||||
return this.calc();
|
||||
}
|
||||
@@ -451,14 +485,18 @@ Licensed under the BSD-2-Clause License.
|
||||
};
|
||||
|
||||
Grid.prototype.transY = function(y) {
|
||||
return this.bottom - (y - this.ymin) * this.dy;
|
||||
if (!this.options.horizontal) {
|
||||
return this.bottom - (y - this.ymin) * this.dy;
|
||||
} else {
|
||||
return this.left + (y - this.ymin) * this.dy;
|
||||
}
|
||||
};
|
||||
|
||||
Grid.prototype.transX = function(x) {
|
||||
if (this.data.length === 1) {
|
||||
return (this.left + this.right) / 2;
|
||||
return (this.xStart + this.xEnd) / 2;
|
||||
} else {
|
||||
return this.left + (x - this.xmin) * this.dx;
|
||||
return this.xStart + (x - this.xmin) * this.dx;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -485,32 +523,50 @@ Licensed under the BSD-2-Clause License.
|
||||
};
|
||||
|
||||
Grid.prototype.yAxisFormat = function(label) {
|
||||
return this.yLabelFormat(label);
|
||||
return this.yLabelFormat(label, 0);
|
||||
};
|
||||
|
||||
Grid.prototype.yLabelFormat = function(label) {
|
||||
Grid.prototype.yLabelFormat = function(label, i) {
|
||||
if (typeof this.options.yLabelFormat === 'function') {
|
||||
return this.options.yLabelFormat(label);
|
||||
return this.options.yLabelFormat(label, i);
|
||||
} else {
|
||||
return "" + this.options.preUnits + (Morris.commas(label)) + this.options.postUnits;
|
||||
}
|
||||
};
|
||||
|
||||
Grid.prototype.getYAxisLabelX = function() {
|
||||
return this.left - this.options.padding / 2;
|
||||
};
|
||||
|
||||
Grid.prototype.drawGrid = function() {
|
||||
var lineY, y, _i, _len, _ref, _ref1, _ref2, _results;
|
||||
var basePos, lineY, pos, _i, _len, _ref, _ref1, _ref2, _results;
|
||||
if (this.options.grid === false && ((_ref = this.options.axes) !== true && _ref !== 'both' && _ref !== 'y')) {
|
||||
return;
|
||||
}
|
||||
if (!this.options.horizontal) {
|
||||
basePos = this.getYAxisLabelX();
|
||||
} else {
|
||||
basePos = this.getXAxisLabelY();
|
||||
}
|
||||
_ref1 = this.grid;
|
||||
_results = [];
|
||||
for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
|
||||
lineY = _ref1[_i];
|
||||
y = this.transY(lineY);
|
||||
pos = this.transY(lineY);
|
||||
if ((_ref2 = this.options.axes) === true || _ref2 === 'both' || _ref2 === 'y') {
|
||||
this.drawYAxisLabel(this.left - this.options.padding / 2, y, this.yAxisFormat(lineY));
|
||||
if (!this.options.horizontal) {
|
||||
this.drawYAxisLabel(basePos, pos, this.yAxisFormat(lineY));
|
||||
} else {
|
||||
this.drawXAxisLabel(pos, basePos, this.yAxisFormat(lineY));
|
||||
}
|
||||
}
|
||||
if (this.options.grid) {
|
||||
_results.push(this.drawGridLine("M" + this.left + "," + y + "H" + (this.left + this.width)));
|
||||
pos = Math.floor(pos) + 0.5;
|
||||
if (!this.options.horizontal) {
|
||||
_results.push(this.drawGridLine("M" + this.xStart + "," + pos + "H" + this.xEnd));
|
||||
} else {
|
||||
_results.push(this.drawGridLine("M" + pos + "," + this.xStart + "V" + this.xEnd));
|
||||
}
|
||||
} else {
|
||||
_results.push(void 0);
|
||||
}
|
||||
@@ -543,11 +599,42 @@ Licensed under the BSD-2-Clause License.
|
||||
};
|
||||
|
||||
Grid.prototype.drawGoal = function(goal, color) {
|
||||
return this.raphael.path("M" + this.left + "," + (this.transY(goal)) + "H" + this.right).attr('stroke', color).attr('stroke-width', this.options.goalStrokeWidth);
|
||||
var path, y;
|
||||
y = Math.floor(this.transY(goal)) + 0.5;
|
||||
if (!this.options.horizontal) {
|
||||
path = "M" + this.xStart + "," + y + "H" + this.xEnd;
|
||||
} else {
|
||||
path = "M" + y + "," + this.xStart + "V" + this.xEnd;
|
||||
}
|
||||
return this.raphael.path(path).attr('stroke', color).attr('stroke-width', this.options.goalStrokeWidth);
|
||||
};
|
||||
|
||||
Grid.prototype.drawEvent = function(event, color) {
|
||||
return this.raphael.path("M" + (this.transX(event)) + "," + this.bottom + "V" + this.top).attr('stroke', color).attr('stroke-width', this.options.eventStrokeWidth);
|
||||
var from, path, to, x;
|
||||
if (event instanceof Array) {
|
||||
from = event[0], to = event[1];
|
||||
from = Math.floor(this.transX(from)) + 0.5;
|
||||
to = Math.floor(this.transX(to)) + 0.5;
|
||||
if (!this.options.horizontal) {
|
||||
return this.raphael.rect(from, this.yEnd, to - from, this.yStart - this.yEnd).attr({
|
||||
fill: color,
|
||||
stroke: false
|
||||
}).toBack();
|
||||
} else {
|
||||
return this.raphael.rect(this.yStart, from, this.yEnd - this.yStart, to - from).attr({
|
||||
fill: color,
|
||||
stroke: false
|
||||
}).toBack();
|
||||
}
|
||||
} else {
|
||||
x = Math.floor(this.transX(event)) + 0.5;
|
||||
if (!this.options.horizontal) {
|
||||
path = "M" + x + "," + this.yStart + "V" + this.yEnd;
|
||||
} else {
|
||||
path = "M" + this.yStart + "," + x + "H" + this.yEnd;
|
||||
}
|
||||
return this.raphael.path(path).attr('stroke', color).attr('stroke-width', this.options.eventStrokeWidth);
|
||||
}
|
||||
};
|
||||
|
||||
Grid.prototype.drawYAxisLabel = function(xPos, yPos, text) {
|
||||
@@ -586,6 +673,10 @@ Licensed under the BSD-2-Clause License.
|
||||
return this.redraw();
|
||||
};
|
||||
|
||||
Grid.prototype.hasToShow = function(i) {
|
||||
return this.options.shown === true || this.options.shown[i] === true;
|
||||
};
|
||||
|
||||
return Grid;
|
||||
|
||||
})(Morris.EventEmitter);
|
||||
@@ -662,13 +753,13 @@ Licensed under the BSD-2-Clause License.
|
||||
this.options.parent.append(this.el);
|
||||
}
|
||||
|
||||
Hover.prototype.update = function(html, x, y) {
|
||||
Hover.prototype.update = function(html, x, y, centre_y) {
|
||||
if (!html) {
|
||||
return this.hide();
|
||||
} else {
|
||||
this.html(html);
|
||||
this.show();
|
||||
return this.moveTo(x, y);
|
||||
return this.moveTo(x, y, centre_y);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -676,7 +767,7 @@ Licensed under the BSD-2-Clause License.
|
||||
return this.el.html(content);
|
||||
};
|
||||
|
||||
Hover.prototype.moveTo = function(x, y) {
|
||||
Hover.prototype.moveTo = function(x, y, centre_y) {
|
||||
var hoverHeight, hoverWidth, left, parentHeight, parentWidth, top;
|
||||
parentWidth = this.options.parent.innerWidth();
|
||||
parentHeight = this.options.parent.innerHeight();
|
||||
@@ -684,11 +775,18 @@ Licensed under the BSD-2-Clause License.
|
||||
hoverHeight = this.el.outerHeight();
|
||||
left = Math.min(Math.max(0, x - hoverWidth / 2), parentWidth - hoverWidth);
|
||||
if (y != null) {
|
||||
top = y - hoverHeight - 10;
|
||||
if (top < 0) {
|
||||
top = y + 10;
|
||||
if (top + hoverHeight > parentHeight) {
|
||||
top = parentHeight / 2 - hoverHeight / 2;
|
||||
if (centre_y === true) {
|
||||
top = y - hoverHeight / 2;
|
||||
if (top < 0) {
|
||||
top = 0;
|
||||
}
|
||||
} else {
|
||||
top = y - hoverHeight - 10;
|
||||
if (top < 0) {
|
||||
top = y + 10;
|
||||
if (top + hoverHeight > parentHeight) {
|
||||
top = parentHeight / 2 - hoverHeight / 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -745,10 +843,14 @@ Licensed under the BSD-2-Clause License.
|
||||
pointStrokeColors: ['#ffffff'],
|
||||
pointFillColors: [],
|
||||
smooth: true,
|
||||
shown: true,
|
||||
xLabels: 'auto',
|
||||
xLabelFormat: null,
|
||||
xLabelMargin: 24,
|
||||
hideHover: false
|
||||
hideHover: false,
|
||||
trendLine: false,
|
||||
trendLineWidth: 2,
|
||||
trendLineColors: ['#689bc3', '#a2b3bf', '#64b764']
|
||||
};
|
||||
|
||||
Line.prototype.calc = function() {
|
||||
@@ -840,11 +942,15 @@ Licensed under the BSD-2-Clause License.
|
||||
Line.prototype.hoverContentForRow = function(index) {
|
||||
var content, j, row, y, _i, _len, _ref;
|
||||
row = this.data[index];
|
||||
content = "<div class='morris-hover-row-label'>" + row.label + "</div>";
|
||||
content = $("<div class='morris-hover-row-label'>").text(row.label);
|
||||
content = content.prop('outerHTML');
|
||||
_ref = row.y;
|
||||
for (j = _i = 0, _len = _ref.length; _i < _len; j = ++_i) {
|
||||
y = _ref[j];
|
||||
content += "<div class='morris-hover-point' style='color: " + (this.colorFor(row, j, 'label')) + "'>\n " + this.options.labels[j] + ":\n " + (this.yLabelFormat(y)) + "\n</div>";
|
||||
if (this.options.labels[j] === false) {
|
||||
continue;
|
||||
}
|
||||
content += "<div class='morris-hover-point' style='color: " + (this.colorFor(row, j, 'label')) + "'>\n " + this.options.labels[j] + ":\n " + (this.yLabelFormat(y, j)) + "\n</div>";
|
||||
}
|
||||
if (typeof this.options.hoverCallback === 'function') {
|
||||
content = this.options.hoverCallback(index, this.options, content, row.src);
|
||||
@@ -954,11 +1060,20 @@ Licensed under the BSD-2-Clause License.
|
||||
var i, _i, _j, _ref, _ref1, _results;
|
||||
this.seriesPoints = [];
|
||||
for (i = _i = _ref = this.options.ykeys.length - 1; _ref <= 0 ? _i <= 0 : _i >= 0; i = _ref <= 0 ? ++_i : --_i) {
|
||||
this._drawLineFor(i);
|
||||
if (this.hasToShow(i)) {
|
||||
if (this.options.trendLine !== false && this.options.trendLine === true || this.options.trendLine[i] === true) {
|
||||
this._drawTrendLine(i);
|
||||
}
|
||||
this._drawLineFor(i);
|
||||
}
|
||||
}
|
||||
_results = [];
|
||||
for (i = _j = _ref1 = this.options.ykeys.length - 1; _ref1 <= 0 ? _j <= 0 : _j >= 0; i = _ref1 <= 0 ? ++_j : --_j) {
|
||||
_results.push(this._drawPointFor(i));
|
||||
if (this.hasToShow(i)) {
|
||||
_results.push(this._drawPointFor(i));
|
||||
} else {
|
||||
_results.push(void 0);
|
||||
}
|
||||
}
|
||||
return _results;
|
||||
};
|
||||
@@ -987,6 +1102,38 @@ Licensed under the BSD-2-Clause License.
|
||||
}
|
||||
};
|
||||
|
||||
Line.prototype._drawTrendLine = function(index) {
|
||||
var a, b, data, datapoints, path, sum_x, sum_xx, sum_xy, sum_y, val, x, y, _i, _len, _ref;
|
||||
sum_x = 0;
|
||||
sum_y = 0;
|
||||
sum_xx = 0;
|
||||
sum_xy = 0;
|
||||
datapoints = 0;
|
||||
_ref = this.data;
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
val = _ref[_i];
|
||||
x = val.x;
|
||||
y = val.y[index];
|
||||
if (y === void 0) {
|
||||
continue;
|
||||
}
|
||||
datapoints += 1;
|
||||
sum_x += x;
|
||||
sum_y += y;
|
||||
sum_xx += x * x;
|
||||
sum_xy += x * y;
|
||||
}
|
||||
a = (datapoints * sum_xy - sum_x * sum_y) / (datapoints * sum_xx - sum_x * sum_x);
|
||||
b = (sum_y / datapoints) - ((a * sum_x) / datapoints);
|
||||
data = [{}, {}];
|
||||
data[0].x = this.transX(this.data[0].x);
|
||||
data[0].y = this.transY(this.data[0].x * a + b);
|
||||
data[1].x = this.transX(this.data[this.data.length - 1].x);
|
||||
data[1].y = this.transY(this.data[this.data.length - 1].x * a + b);
|
||||
path = Morris.Line.createPath(data, false, this.bottom);
|
||||
return path = this.raphael.path(path).attr('stroke', this.colorFor(null, index, 'trendLine')).attr('stroke-width', this.options.trendLineWidth);
|
||||
};
|
||||
|
||||
Line.createPath = function(coords, smooth, bottom) {
|
||||
var coord, g, grads, i, ix, lg, path, prevCoord, x1, x2, y1, y2, _i, _len;
|
||||
path = "";
|
||||
@@ -1078,8 +1225,10 @@ Licensed under the BSD-2-Clause License.
|
||||
return this.options.lineColors.call(this, row, sidx, type);
|
||||
} else if (type === 'point') {
|
||||
return this.options.pointFillColors[sidx % this.options.pointFillColors.length] || this.options.lineColors[sidx % this.options.lineColors.length];
|
||||
} else {
|
||||
} else if (type === 'line') {
|
||||
return this.options.lineColors[sidx % this.options.lineColors.length];
|
||||
} else {
|
||||
return this.options.trendLineColors[sidx % this.options.trendLineColors.length];
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1120,6 +1269,9 @@ Licensed under the BSD-2-Clause License.
|
||||
};
|
||||
|
||||
Line.prototype.pointGrowSeries = function(index) {
|
||||
if (this.pointSizeForSeries(index) === 0) {
|
||||
return;
|
||||
}
|
||||
return Raphael.animation({
|
||||
r: this.pointSizeForSeries(index) + 3
|
||||
}, 25, 'linear');
|
||||
@@ -1409,7 +1561,9 @@ Licensed under the BSD-2-Clause License.
|
||||
barColors: ['#0b62a4', '#7a92a3', '#4da74d', '#afd8f8', '#edc240', '#cb4b4b', '#9440ed'],
|
||||
barOpacity: 1.0,
|
||||
barRadius: [0, 0, 0, 0],
|
||||
xLabelMargin: 50
|
||||
xLabelMargin: 50,
|
||||
horizontal: false,
|
||||
shown: true
|
||||
};
|
||||
|
||||
Bar.prototype.calc = function() {
|
||||
@@ -1426,7 +1580,7 @@ Licensed under the BSD-2-Clause License.
|
||||
_results = [];
|
||||
for (idx = _i = 0, _len = _ref.length; _i < _len; idx = ++_i) {
|
||||
row = _ref[idx];
|
||||
row._x = this.left + this.width * (idx + 0.5) / this.data.length;
|
||||
row._x = this.xStart + this.xSize * (idx + 0.5) / this.data.length;
|
||||
_results.push(row._y = (function() {
|
||||
var _j, _len1, _ref1, _results1;
|
||||
_ref1 = row.y;
|
||||
@@ -1454,28 +1608,54 @@ Licensed under the BSD-2-Clause License.
|
||||
};
|
||||
|
||||
Bar.prototype.drawXAxis = function() {
|
||||
var i, label, labelBox, margin, offset, prevAngleMargin, prevLabelMargin, row, textBox, ypos, _i, _ref, _results;
|
||||
ypos = this.bottom + (this.options.xAxisLabelTopPadding || this.options.padding / 2);
|
||||
var angle, basePos, i, label, labelBox, margin, maxSize, offset, prevAngleMargin, prevLabelMargin, row, size, startPos, textBox, _i, _ref, _results;
|
||||
if (!this.options.horizontal) {
|
||||
basePos = this.getXAxisLabelY();
|
||||
} else {
|
||||
basePos = this.getYAxisLabelX();
|
||||
}
|
||||
prevLabelMargin = null;
|
||||
prevAngleMargin = null;
|
||||
_results = [];
|
||||
for (i = _i = 0, _ref = this.data.length; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) {
|
||||
row = this.data[this.data.length - 1 - i];
|
||||
label = this.drawXAxisLabel(row._x, ypos, row.label);
|
||||
if (!this.options.horizontal) {
|
||||
label = this.drawXAxisLabel(row._x, basePos, row.label);
|
||||
} else {
|
||||
label = this.drawYAxisLabel(basePos, row._x - 0.5 * this.options.gridTextSize, row.label);
|
||||
}
|
||||
if (!this.options.horizontal) {
|
||||
angle = this.options.xLabelAngle;
|
||||
} else {
|
||||
angle = 0;
|
||||
}
|
||||
textBox = label.getBBox();
|
||||
label.transform("r" + (-this.options.xLabelAngle));
|
||||
label.transform("r" + (-angle));
|
||||
labelBox = label.getBBox();
|
||||
label.transform("t0," + (labelBox.height / 2) + "...");
|
||||
if (this.options.xLabelAngle !== 0) {
|
||||
offset = -0.5 * textBox.width * Math.cos(this.options.xLabelAngle * Math.PI / 180.0);
|
||||
if (angle !== 0) {
|
||||
offset = -0.5 * textBox.width * Math.cos(angle * Math.PI / 180.0);
|
||||
label.transform("t" + offset + ",0...");
|
||||
}
|
||||
if (((prevLabelMargin == null) || prevLabelMargin >= labelBox.x + labelBox.width || (prevAngleMargin != null) && prevAngleMargin >= labelBox.x) && labelBox.x >= 0 && (labelBox.x + labelBox.width) < this.el.width()) {
|
||||
if (this.options.xLabelAngle !== 0) {
|
||||
margin = 1.25 * this.options.gridTextSize / Math.sin(this.options.xLabelAngle * Math.PI / 180.0);
|
||||
prevAngleMargin = labelBox.x - margin;
|
||||
if (!this.options.horizontal) {
|
||||
startPos = labelBox.x;
|
||||
size = labelBox.width;
|
||||
maxSize = this.el.width();
|
||||
} else {
|
||||
startPos = labelBox.y;
|
||||
size = labelBox.height;
|
||||
maxSize = this.el.height();
|
||||
}
|
||||
if (((prevLabelMargin == null) || prevLabelMargin >= startPos + size || (prevAngleMargin != null) && prevAngleMargin >= startPos) && startPos >= 0 && (startPos + size) < maxSize) {
|
||||
if (angle !== 0) {
|
||||
margin = 1.25 * this.options.gridTextSize / Math.sin(angle * Math.PI / 180.0);
|
||||
prevAngleMargin = startPos - margin;
|
||||
}
|
||||
if (!this.options.horizontal) {
|
||||
_results.push(prevLabelMargin = startPos - this.options.xLabelMargin);
|
||||
} else {
|
||||
_results.push(prevLabelMargin = startPos);
|
||||
}
|
||||
_results.push(prevLabelMargin = labelBox.x - this.options.xLabelMargin);
|
||||
} else {
|
||||
_results.push(label.remove());
|
||||
}
|
||||
@@ -1483,10 +1663,23 @@ Licensed under the BSD-2-Clause License.
|
||||
return _results;
|
||||
};
|
||||
|
||||
Bar.prototype.getXAxisLabelY = function() {
|
||||
return this.bottom + (this.options.xAxisLabelTopPadding || this.options.padding / 2);
|
||||
};
|
||||
|
||||
Bar.prototype.drawSeries = function() {
|
||||
var barWidth, bottom, groupWidth, idx, lastTop, left, leftPadding, numBars, row, sidx, size, spaceLeft, top, ypos, zeroPos;
|
||||
groupWidth = this.width / this.options.data.length;
|
||||
numBars = this.options.stacked ? 1 : this.options.ykeys.length;
|
||||
var barWidth, bottom, groupWidth, i, idx, lastTop, left, leftPadding, numBars, row, sidx, size, spaceLeft, top, ypos, zeroPos, _i, _ref;
|
||||
groupWidth = this.xSize / this.options.data.length;
|
||||
if (this.options.stacked) {
|
||||
numBars = 1;
|
||||
} else {
|
||||
numBars = 0;
|
||||
for (i = _i = 0, _ref = this.options.ykeys.length - 1; 0 <= _ref ? _i <= _ref : _i >= _ref; i = 0 <= _ref ? ++_i : --_i) {
|
||||
if (this.hasToShow(i)) {
|
||||
numBars += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
barWidth = (groupWidth * this.options.barSizeRatio - this.options.barGap * (numBars - 1)) / numBars;
|
||||
if (this.options.barSize) {
|
||||
barWidth = Math.min(barWidth, this.options.barSize);
|
||||
@@ -1495,18 +1688,21 @@ Licensed under the BSD-2-Clause License.
|
||||
leftPadding = spaceLeft / 2;
|
||||
zeroPos = this.ymin <= 0 && this.ymax >= 0 ? this.transY(0) : null;
|
||||
return this.bars = (function() {
|
||||
var _i, _len, _ref, _results;
|
||||
_ref = this.data;
|
||||
var _j, _len, _ref1, _results;
|
||||
_ref1 = this.data;
|
||||
_results = [];
|
||||
for (idx = _i = 0, _len = _ref.length; _i < _len; idx = ++_i) {
|
||||
row = _ref[idx];
|
||||
for (idx = _j = 0, _len = _ref1.length; _j < _len; idx = ++_j) {
|
||||
row = _ref1[idx];
|
||||
lastTop = 0;
|
||||
_results.push((function() {
|
||||
var _j, _len1, _ref1, _results1;
|
||||
_ref1 = row._y;
|
||||
var _k, _len1, _ref2, _results1;
|
||||
_ref2 = row._y;
|
||||
_results1 = [];
|
||||
for (sidx = _j = 0, _len1 = _ref1.length; _j < _len1; sidx = ++_j) {
|
||||
ypos = _ref1[sidx];
|
||||
for (sidx = _k = 0, _len1 = _ref2.length; _k < _len1; sidx = ++_k) {
|
||||
ypos = _ref2[sidx];
|
||||
if (!this.hasToShow(sidx)) {
|
||||
continue;
|
||||
}
|
||||
if (ypos !== null) {
|
||||
if (zeroPos) {
|
||||
top = Math.min(ypos, zeroPos);
|
||||
@@ -1515,19 +1711,28 @@ Licensed under the BSD-2-Clause License.
|
||||
top = ypos;
|
||||
bottom = this.bottom;
|
||||
}
|
||||
left = this.left + idx * groupWidth + leftPadding;
|
||||
left = this.xStart + idx * groupWidth + leftPadding;
|
||||
if (!this.options.stacked) {
|
||||
left += sidx * (barWidth + this.options.barGap);
|
||||
}
|
||||
size = bottom - top;
|
||||
if (this.options.verticalGridCondition && this.options.verticalGridCondition(row.x)) {
|
||||
this.drawBar(this.left + idx * groupWidth, this.top, groupWidth, Math.abs(this.top - this.bottom), this.options.verticalGridColor, this.options.verticalGridOpacity, this.options.barRadius);
|
||||
if (!this.options.horizontal) {
|
||||
this.drawBar(this.xStart + idx * groupWidth, this.yEnd, groupWidth, this.ySize, this.options.verticalGridColor, this.options.verticalGridOpacity, this.options.barRadius);
|
||||
} else {
|
||||
this.drawBar(this.yStart, this.xStart + idx * groupWidth, this.ySize, groupWidth, this.options.verticalGridColor, this.options.verticalGridOpacity, this.options.barRadius);
|
||||
}
|
||||
}
|
||||
if (this.options.stacked) {
|
||||
top -= lastTop;
|
||||
}
|
||||
this.drawBar(left, top, barWidth, size, this.colorFor(row, sidx, 'bar'), this.options.barOpacity, this.options.barRadius);
|
||||
_results1.push(lastTop += size);
|
||||
if (!this.options.horizontal) {
|
||||
this.drawBar(left, top, barWidth, size, this.colorFor(row, sidx, 'bar'), this.options.barOpacity, this.options.barRadius);
|
||||
_results1.push(lastTop += size);
|
||||
} else {
|
||||
this.drawBar(top, left, size, barWidth, this.colorFor(row, sidx, 'bar'), this.options.barOpacity, this.options.barRadius);
|
||||
_results1.push(lastTop -= size);
|
||||
}
|
||||
} else {
|
||||
_results1.push(null);
|
||||
}
|
||||
@@ -1558,23 +1763,29 @@ Licensed under the BSD-2-Clause License.
|
||||
}
|
||||
};
|
||||
|
||||
Bar.prototype.hitTest = function(x) {
|
||||
Bar.prototype.hitTest = function(x, y) {
|
||||
var pos;
|
||||
if (this.data.length === 0) {
|
||||
return null;
|
||||
}
|
||||
x = Math.max(Math.min(x, this.right), this.left);
|
||||
return Math.min(this.data.length - 1, Math.floor((x - this.left) / (this.width / this.data.length)));
|
||||
if (!this.options.horizontal) {
|
||||
pos = x;
|
||||
} else {
|
||||
pos = y;
|
||||
}
|
||||
pos = Math.max(Math.min(pos, this.xEnd), this.xStart);
|
||||
return Math.min(this.data.length - 1, Math.floor((pos - this.xStart) / (this.xSize / this.data.length)));
|
||||
};
|
||||
|
||||
Bar.prototype.onGridClick = function(x, y) {
|
||||
var index;
|
||||
index = this.hitTest(x);
|
||||
index = this.hitTest(x, y);
|
||||
return this.fire('click', index, this.data[index].src, x, y);
|
||||
};
|
||||
|
||||
Bar.prototype.onHoverMove = function(x, y) {
|
||||
var index, _ref;
|
||||
index = this.hitTest(x);
|
||||
index = this.hitTest(x, y);
|
||||
return (_ref = this.hover).update.apply(_ref, this.hoverContentForRow(index));
|
||||
};
|
||||
|
||||
@@ -1587,17 +1798,27 @@ Licensed under the BSD-2-Clause License.
|
||||
Bar.prototype.hoverContentForRow = function(index) {
|
||||
var content, j, row, x, y, _i, _len, _ref;
|
||||
row = this.data[index];
|
||||
content = "<div class='morris-hover-row-label'>" + row.label + "</div>";
|
||||
content = $("<div class='morris-hover-row-label'>").text(row.label);
|
||||
content = content.prop('outerHTML');
|
||||
_ref = row.y;
|
||||
for (j = _i = 0, _len = _ref.length; _i < _len; j = ++_i) {
|
||||
y = _ref[j];
|
||||
content += "<div class='morris-hover-point' style='color: " + (this.colorFor(row, j, 'label')) + "'>\n " + this.options.labels[j] + ":\n " + (this.yLabelFormat(y)) + "\n</div>";
|
||||
if (this.options.labels[j] === false) {
|
||||
continue;
|
||||
}
|
||||
content += "<div class='morris-hover-point' style='color: " + (this.colorFor(row, j, 'label')) + "'>\n " + this.options.labels[j] + ":\n " + (this.yLabelFormat(y, j)) + "\n</div>";
|
||||
}
|
||||
if (typeof this.options.hoverCallback === 'function') {
|
||||
content = this.options.hoverCallback(index, this.options, content, row.src);
|
||||
}
|
||||
x = this.left + (index + 0.5) * this.width / this.data.length;
|
||||
return [content, x];
|
||||
if (!this.options.horizontal) {
|
||||
x = this.left + (index + 0.5) * this.width / this.data.length;
|
||||
return [content, x];
|
||||
} else {
|
||||
x = this.left + 0.5 * this.width;
|
||||
y = this.top + (index + 0.5) * this.height / this.data.length;
|
||||
return [content, x, y, true];
|
||||
}
|
||||
};
|
||||
|
||||
Bar.prototype.drawXAxisLabel = function(xPos, yPos, text) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* Preload images */
|
||||
body:after {
|
||||
content: url(../images/close.png) url(../images/loading.gif) url(../images/prev.png) url(../images/next.png);
|
||||
content: url(static('lightbox/images/close.png')) url(static('lightbox/images/loading.gif')) url(static('lightbox/images/prev.png')) url(static('lightbox/images/next.png'));
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ body:after {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
margin: 0 auto;
|
||||
background: url(../images/loading.gif) no-repeat;
|
||||
background: url(static('lightbox/images/loading.gif')) no-repeat;
|
||||
}
|
||||
|
||||
.lb-nav {
|
||||
@@ -110,7 +110,7 @@ body:after {
|
||||
width: 34%;
|
||||
left: 0;
|
||||
float: left;
|
||||
background: url(../images/prev.png) left 48% no-repeat;
|
||||
background: url(static('lightbox/images/prev.png')) left 48% no-repeat;
|
||||
filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=0);
|
||||
opacity: 0;
|
||||
-webkit-transition: opacity 0.6s;
|
||||
@@ -128,7 +128,7 @@ body:after {
|
||||
width: 64%;
|
||||
right: 0;
|
||||
float: right;
|
||||
background: url(../images/next.png) right 48% no-repeat;
|
||||
background: url(static('lightbox/images/next.png')) right 48% no-repeat;
|
||||
filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=0);
|
||||
opacity: 0;
|
||||
-webkit-transition: opacity 0.6s;
|
||||
@@ -192,7 +192,7 @@ body:after {
|
||||
float: right;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
background: url(../images/close.png) top right no-repeat;
|
||||
background: url(static('lightbox/images/close.png')) top right no-repeat;
|
||||
text-align: right;
|
||||
outline: none;
|
||||
filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=70);
|
||||
@@ -29,6 +29,11 @@ nav.navbar {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
#side-menu img.fa-img {
|
||||
height: auto;
|
||||
width: 1.28571em;
|
||||
}
|
||||
|
||||
nav.navbar .danger, nav.navbar .danger:hover, nav.navbar .danger:active {
|
||||
background: $brand-danger !important;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ function typocheck() {
|
||||
|
||||
$sources.each(function () {
|
||||
$.each($(this).val().toLowerCase().replace('-', '').split(' '), function (i, w) {
|
||||
if (w) {
|
||||
if (w && w.length > 5) {
|
||||
words.push(w);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
django-debug-toolbar==1.5
|
||||
django-debug-toolbar==1.8
|
||||
sqlparse==0.2.1 # pinned due to difficulties with django-debug-toolbar
|
||||
# Testing requirements
|
||||
pep8==1.5.7 # exact requirement by flake8 2.4.0
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
# Functional requirements
|
||||
Django>=1.10.7,<1.11
|
||||
Django>=1.11.*
|
||||
python-dateutil
|
||||
pytz
|
||||
django-bootstrap3==8.0.*
|
||||
django-bootstrap3==8.2.*
|
||||
django-formset-js-improved==0.5.0.1
|
||||
django-compressor==2.1
|
||||
django-compressor==2.1.1
|
||||
django-hierarkey==1.0.*
|
||||
reportlab==3.2.*
|
||||
PyPDF2==1.26.*
|
||||
easy-thumbnails==2.*
|
||||
easy-thumbnails==2.4.*
|
||||
django-libsass
|
||||
libsass
|
||||
django-otp==0.3.*
|
||||
python-u2flib-server==4.*
|
||||
django-formtools==1.0
|
||||
django-formtools==2.0
|
||||
celery==4.0.2
|
||||
kombu==4.0.2
|
||||
django-statici18n==1.3.*
|
||||
@@ -28,14 +28,15 @@ django-markup
|
||||
markdown
|
||||
bleach==2.*
|
||||
raven
|
||||
django-i18nfield
|
||||
django-i18nfield>=1.0.1
|
||||
# Stripe
|
||||
stripe==1.22.*
|
||||
# PayPal
|
||||
paypalrestsdk==1.12.*
|
||||
pycparser==2.13 # https://github.com/eliben/pycparser/issues/147
|
||||
# Banktransfer
|
||||
chardet>=2.3,<3
|
||||
chardet<3.1.0,>=3.0.2
|
||||
mt-940==3.2
|
||||
vobject==0.9.*
|
||||
pycountry
|
||||
defusedcsv>=1.0.1
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
django-redis==4.1.*
|
||||
django-redis==4.8.*
|
||||
redis==2.10.5
|
||||
|
||||
21
src/setup.py
21
src/setup.py
@@ -62,21 +62,21 @@ setup(
|
||||
|
||||
keywords='tickets web shop ecommerce',
|
||||
install_requires=[
|
||||
'Django==1.10.*',
|
||||
'Django==1.11.*',
|
||||
'python-dateutil==2.4.*',
|
||||
'pytz',
|
||||
'django-bootstrap3==7.1.*',
|
||||
'django-bootstrap3==8.2.*',
|
||||
'django-formset-js-improved==0.5.0.1',
|
||||
'django-compressor==2.1',
|
||||
'django-hierarkey==1.0.*',
|
||||
'reportlab==3.2.*',
|
||||
'easy-thumbnails==2.*',
|
||||
'easy-thumbnails==2.4.*',
|
||||
'PyPDF2==1.26.*',
|
||||
'django-libsass',
|
||||
'libsass',
|
||||
'django-otp==0.3.*',
|
||||
'python-u2flib-server==4.*',
|
||||
'django-formtools==1.0',
|
||||
'django-formtools==2.0',
|
||||
'celery==4.0.2',
|
||||
'kombu==4.0.2',
|
||||
'django-statici18n==1.3.*',
|
||||
@@ -93,18 +93,19 @@ setup(
|
||||
'raven',
|
||||
'paypalrestsdk==1.12.*',
|
||||
'pycparser==2.13',
|
||||
'django-redis==4.1.*',
|
||||
'django-redis==4.7.*',
|
||||
'redis==2.10.5',
|
||||
'stripe==1.22.*',
|
||||
'chardet>=2.3,<3',
|
||||
'mt-940==3.2',
|
||||
'django-i18nfield',
|
||||
'chardet<3.1.0,>=3.0.2',
|
||||
'mt-940==4.7',
|
||||
'django-i18nfield>=1.0.1',
|
||||
'vobject==0.9.*',
|
||||
'pycountry'
|
||||
'pycountry',
|
||||
'defusedcsv'
|
||||
],
|
||||
extras_require={
|
||||
'dev': [
|
||||
'django-debug-toolbar==1.5',
|
||||
'django-debug-toolbar==1.7',
|
||||
'sqlparse==0.2.1',
|
||||
'pep8==1.5.7',
|
||||
'pyflakes==1.1.0',
|
||||
|
||||
@@ -2,10 +2,9 @@ from django.db import DEFAULT_DB_ALIAS, connections
|
||||
from django.test.utils import CaptureQueriesContext
|
||||
|
||||
|
||||
# Inspired by /django/test/testcases.py
|
||||
# but copied over to work without the unit test module
|
||||
|
||||
class _AssertNumQueriesContext(CaptureQueriesContext):
|
||||
# Inspired by /django/test/testcases.py
|
||||
# but copied over to work without the unit test module
|
||||
def __init__(self, num, connection):
|
||||
self.num = num
|
||||
super(_AssertNumQueriesContext, self).__init__(connection)
|
||||
|
||||
@@ -92,7 +92,6 @@ class I18nFieldTest(TestCase):
|
||||
"""
|
||||
This test case tests the I18n*Field classes
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
o = Organizer.objects.create(name='Dummy', slug='dummy')
|
||||
@@ -102,8 +101,8 @@ class I18nFieldTest(TestCase):
|
||||
)
|
||||
|
||||
def test_save_load_cycle_plain_string(self):
|
||||
obj = ItemCategory.all.create(event=self.event, name="Hello")
|
||||
obj = ItemCategory.all.get(id=obj.id)
|
||||
obj = ItemCategory.objects.create(event=self.event, name="Hello")
|
||||
obj = ItemCategory.objects.get(id=obj.id)
|
||||
self.assertIsInstance(obj.name, LazyI18nString)
|
||||
translation.activate('en')
|
||||
self.assertEqual(str(obj.name), "Hello")
|
||||
@@ -111,14 +110,14 @@ class I18nFieldTest(TestCase):
|
||||
self.assertEqual(str(obj.name), "Hello")
|
||||
|
||||
def test_save_load_cycle_i18n_string(self):
|
||||
obj = ItemCategory.all.create(event=self.event,
|
||||
name=LazyI18nString(
|
||||
{
|
||||
'de': 'Hallo',
|
||||
'en': 'Hello'
|
||||
}
|
||||
))
|
||||
obj = ItemCategory.all.get(id=obj.id)
|
||||
obj = ItemCategory.objects.create(event=self.event,
|
||||
name=LazyI18nString(
|
||||
{
|
||||
'de': 'Hallo',
|
||||
'en': 'Hello'
|
||||
}
|
||||
))
|
||||
obj = ItemCategory.objects.get(id=obj.id)
|
||||
self.assertIsInstance(obj.name, LazyI18nString)
|
||||
translation.activate('en')
|
||||
self.assertEqual(str(obj.name), "Hello")
|
||||
|
||||
@@ -687,8 +687,8 @@ class ItemCategoryTest(TestCase):
|
||||
)
|
||||
|
||||
def test_sorting(self):
|
||||
c1 = ItemCategory.all.create(event=self.event)
|
||||
c2 = ItemCategory.all.create(event=self.event)
|
||||
c1 = ItemCategory.objects.create(event=self.event)
|
||||
c2 = ItemCategory.objects.create(event=self.event)
|
||||
assert c1 < c2
|
||||
c1.position = 2
|
||||
c2.position = 1
|
||||
|
||||
@@ -210,12 +210,12 @@ def checkin_list_env():
|
||||
('code', ['A1Ticket', 'A1Mascot', 'A2Ticket', 'A3Ticket']),
|
||||
('-email', ['A3Ticket', 'A2Ticket', 'A1Ticket', 'A1Mascot']),
|
||||
('email', ['A1Ticket', 'A1Mascot', 'A2Ticket', 'A3Ticket']),
|
||||
# ('-status', ['A3Ticket', 'A1Ticket', 'A1Mascot', 'A2Ticket']),
|
||||
# ('status', ['A1Mascot', 'A2Ticket', 'A1Ticket', 'A3Ticket']),
|
||||
# ('-timestamp', ['A1Ticket', 'A3Ticket', 'A1Mascot', 'A2Ticket']), # A1 checkin date > A3 checkin date
|
||||
# ('timestamp', ['A1Mascot', 'A2Ticket', 'A3Ticket', 'A1Ticket']),
|
||||
# ('-name', ['A3Ticket', 'A2Ticket', 'A1Ticket', 'A1Mascot']),
|
||||
# ('name', ['A1Mascot', 'A1Ticket', 'A2Ticket', 'A3Ticket']), # mascot doesn't include attendee name
|
||||
('-status', ['A3Ticket', 'A1Ticket', 'A1Mascot', 'A2Ticket']),
|
||||
('status', ['A1Mascot', 'A2Ticket', 'A1Ticket', 'A3Ticket']),
|
||||
('-timestamp', ['A1Ticket', 'A3Ticket', 'A1Mascot', 'A2Ticket']), # A1 checkin date > A3 checkin date
|
||||
('timestamp', ['A1Mascot', 'A2Ticket', 'A3Ticket', 'A1Ticket']),
|
||||
('-name', ['A3Ticket', 'A2Ticket', 'A1Ticket', 'A1Mascot']),
|
||||
('name', ['A1Mascot', 'A1Ticket', 'A2Ticket', 'A3Ticket']), # mascot doesn't include attendee name
|
||||
('-item', ['A1Ticket', 'A2Ticket', 'A3Ticket', 'A1Mascot']),
|
||||
('item', ['A1Mascot', 'A1Ticket', 'A2Ticket', 'A3Ticket']),
|
||||
])
|
||||
@@ -259,8 +259,7 @@ def test_checkins_item_filter(client, checkin_list_env):
|
||||
@pytest.mark.parametrize("query, expected", [
|
||||
('status=&item=&user=&ordering=', ['A1Ticket', 'A1Mascot', 'A2Ticket', 'A3Ticket']),
|
||||
('status=1&item=&user=&ordering=timestamp', ['A3Ticket', 'A1Ticket']),
|
||||
# ('status=0&item=&user=&ordering=-name', ['A2Ticket', 'A1Mascot']),
|
||||
# ('status=&item=Ticket&user=&ordering=checkins__datetime', ['A2Ticket', 'A3Ticket', 'A1Ticket']),
|
||||
('status=0&item=&user=&ordering=-name', ['A2Ticket', 'A1Mascot']),
|
||||
])
|
||||
def test_checkins_list_mixed(client, checkin_list_env, query, expected):
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
@@ -289,8 +288,8 @@ def checkin_list_with_addon_env():
|
||||
team.limit_events.add(event)
|
||||
|
||||
# item
|
||||
cat_adm = ItemCategory.all.create(event=event, name="Admission")
|
||||
cat_workshop = ItemCategory.all.create(event=event, name="Admission", is_addon=True)
|
||||
cat_adm = ItemCategory.objects.create(event=event, name="Admission")
|
||||
cat_workshop = ItemCategory.objects.create(event=event, name="Admission", is_addon=True)
|
||||
item_ticket = Item.objects.create(event=event, name="Ticket", default_price=23, admission=True, category=cat_adm)
|
||||
item_workshop = Item.objects.create(event=event, name="Workshop", default_price=10, admission=False,
|
||||
category=cat_workshop)
|
||||
|
||||
@@ -68,7 +68,7 @@ class EventsTest(SoupTest):
|
||||
assert len(doc.select(".alert-success")) > 0
|
||||
# date_to should not be changed even though the timezone is changed
|
||||
assert doc.select("[name=date_to]")[0]['value'] == "2013-12-30 17:00:00"
|
||||
assert doc.find('option', {"value": "Asia/Tokyo"})['selected'] == "selected"
|
||||
assert 'selected' in doc.find('option', {"value": "Asia/Tokyo"}).attrs
|
||||
assert doc.select("[name=settings-max_items_per_order]")[0]['value'] == "12"
|
||||
|
||||
self.event1.refresh_from_db()
|
||||
|
||||
@@ -37,7 +37,7 @@ class CategoriesTest(ItemFormTest):
|
||||
self.assertIn("Entry tickets", doc.select("#page-wrapper table")[0].text)
|
||||
|
||||
def test_update(self):
|
||||
c = ItemCategory.all.create(event=self.event1, name="Entry tickets")
|
||||
c = ItemCategory.objects.create(event=self.event1, name="Entry tickets")
|
||||
doc = self.get_doc('/control/event/%s/%s/categories/%s/' % (self.orga1.slug, self.event1.slug, c.id))
|
||||
form_data = extract_form_fields(doc.select('.container-fluid form')[0])
|
||||
form_data['name_0'] = 'T-Shirts'
|
||||
@@ -46,11 +46,11 @@ class CategoriesTest(ItemFormTest):
|
||||
assert doc.select(".alert-success")
|
||||
self.assertIn("T-Shirts", doc.select("#page-wrapper table")[0].text)
|
||||
self.assertNotIn("Entry tickets", doc.select("#page-wrapper table")[0].text)
|
||||
assert str(ItemCategory.all.get(id=c.id).name) == 'T-Shirts'
|
||||
assert str(ItemCategory.objects.get(id=c.id).name) == 'T-Shirts'
|
||||
|
||||
def test_sort(self):
|
||||
c1 = ItemCategory.all.create(event=self.event1, name="Entry tickets", position=0)
|
||||
ItemCategory.all.create(event=self.event1, name="T-Shirts", position=1)
|
||||
c1 = ItemCategory.objects.create(event=self.event1, name="Entry tickets", position=0)
|
||||
ItemCategory.objects.create(event=self.event1, name="T-Shirts", position=1)
|
||||
doc = self.get_doc('/control/event/%s/%s/categories/' % (self.orga1.slug, self.event1.slug))
|
||||
self.assertIn("Entry tickets", doc.select("table > tbody > tr")[0].text)
|
||||
self.assertIn("T-Shirts", doc.select("table > tbody > tr")[1].text)
|
||||
@@ -66,14 +66,14 @@ class CategoriesTest(ItemFormTest):
|
||||
self.assertIn("T-Shirts", doc.select("table > tbody > tr")[1].text)
|
||||
|
||||
def test_delete(self):
|
||||
c = ItemCategory.all.create(event=self.event1, name="Entry tickets")
|
||||
c = ItemCategory.objects.create(event=self.event1, name="Entry tickets")
|
||||
doc = self.get_doc('/control/event/%s/%s/categories/%s/delete' % (self.orga1.slug, self.event1.slug, c.id))
|
||||
form_data = extract_form_fields(doc.select('.container-fluid form')[0])
|
||||
doc = self.post_doc('/control/event/%s/%s/categories/%s/delete' % (self.orga1.slug, self.event1.slug, c.id),
|
||||
form_data)
|
||||
assert doc.select(".alert-success")
|
||||
self.assertNotIn("Entry tickets", doc.select("#page-wrapper")[0].text)
|
||||
assert not ItemCategory.all.filter(id=c.id).exists()
|
||||
assert not ItemCategory.objects.filter(id=c.id).exists()
|
||||
|
||||
|
||||
class QuestionsTest(ItemFormTest):
|
||||
@@ -264,7 +264,7 @@ class ItemsTest(ItemFormTest):
|
||||
require_voucher=True, allow_cancel=False)
|
||||
self.var1 = ItemVariation.objects.create(item=self.item2, value="Silver")
|
||||
self.var2 = ItemVariation.objects.create(item=self.item2, value="Gold")
|
||||
self.addoncat = ItemCategory.all.create(event=self.event1, name="Item category")
|
||||
self.addoncat = ItemCategory.objects.create(event=self.event1, name="Item category")
|
||||
|
||||
def test_move(self):
|
||||
self.client.post('/control/event/%s/%s/items/%s/down' % (self.orga1.slug, self.event1.slug, self.item1.id),)
|
||||
|
||||
@@ -141,7 +141,7 @@ class MailSettingPreviewTest(SoupTest):
|
||||
assert res['msgs']['en'] == self.locale_event.name['en']
|
||||
|
||||
def test_mail_text_order_placed(self):
|
||||
text = '{event}{total}{currency}{date}{paymentinfo}{url}{invoice_name}{invoice_company}'
|
||||
text = '{event}{total}{currency}{date}{payment_info}{url}{invoice_name}{invoice_company}'
|
||||
response = self.client.post(self.target.format(
|
||||
self.orga1.slug, self.event1.slug), {
|
||||
'item': 'mail_text_order_placed',
|
||||
|
||||
@@ -26,7 +26,7 @@ def item(event):
|
||||
|
||||
@pytest.fixture
|
||||
def item_category(event):
|
||||
return ItemCategory.all.create(event=event)
|
||||
return ItemCategory.objects.create(event=event)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
||||
@@ -15,7 +15,7 @@ class CsvImportTest(TestCase):
|
||||
with open(os.path.join(DATA_DIR, filename), 'rb') as f:
|
||||
data = csvimport.get_rows_from_file(f)
|
||||
self.assertEqual(data, expected)
|
||||
parsed = csvimport.parse(data, hint)
|
||||
parsed, good = csvimport.parse(data, hint)
|
||||
self.assertEqual(parsed, expected_parsed)
|
||||
|
||||
def test_sample_file_bbbank(self):
|
||||
@@ -125,7 +125,7 @@ class CsvImportTest(TestCase):
|
||||
def test_sample_file_postbank(self):
|
||||
expected = [
|
||||
['Buchungstag', 'Wertstellung', 'Umsatzart', 'Buchungsdetails', 'Auftraggeber', 'Empfänger',
|
||||
'Betrag (\x80)', 'Saldo (\x80)'],
|
||||
'Betrag (€)', 'Saldo (€)'],
|
||||
['07.08.2016', '01.08.2016', 'Gutschrift', 'Verwendungszweck 2015ABCDE', 'Karla Kundin',
|
||||
'Fiktive Veranstaltungsgesellschaft mbH', '\xA4 42,00', '\xA4 1.337,42'],
|
||||
['29.07.2016', '29.07.2016', 'Gutschrift', 'Referenz NOTPROVIDED', 'Lars Lieferant',
|
||||
|
||||
@@ -18,7 +18,7 @@ def env(client):
|
||||
plugins='pretix.plugins.paypal',
|
||||
live=True
|
||||
)
|
||||
category = ItemCategory.all.create(event=event, name="Everything", position=0)
|
||||
category = ItemCategory.objects.create(event=event, name="Everything", position=0)
|
||||
quota_tickets = Quota.objects.create(event=event, name='Tickets', size=5)
|
||||
ticket = Item.objects.create(event=event, name='Early-bird ticket',
|
||||
category=category, default_price=23, admission=True)
|
||||
|
||||
@@ -27,7 +27,7 @@ def env(client):
|
||||
plugins='pretix.plugins.stripe',
|
||||
live=True
|
||||
)
|
||||
category = ItemCategory.all.create(event=event, name="Everything", position=0)
|
||||
category = ItemCategory.objects.create(event=event, name="Everything", position=0)
|
||||
quota_tickets = Quota.objects.create(event=event, name='Tickets', size=5)
|
||||
ticket = Item.objects.create(event=event, name='Early-bird ticket',
|
||||
category=category, default_price=23, admission=True)
|
||||
|
||||
@@ -30,7 +30,7 @@ def item(event):
|
||||
@pytest.fixture
|
||||
def item_category(event):
|
||||
"""Returns an item category instance"""
|
||||
return ItemCategory.all.create(event=event)
|
||||
return ItemCategory.objects.create(event=event)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
||||
@@ -24,7 +24,7 @@ class CartTestMixin:
|
||||
date_from=datetime.datetime(2013, 12, 26, tzinfo=datetime.timezone.utc),
|
||||
live=True
|
||||
)
|
||||
self.category = ItemCategory.all.create(event=self.event, name="Everything", position=0)
|
||||
self.category = ItemCategory.objects.create(event=self.event, name="Everything", position=0)
|
||||
self.quota_shirts = Quota.objects.create(event=self.event, name='Shirts', size=2)
|
||||
self.shirt = Item.objects.create(event=self.event, name='T-Shirt', category=self.category, default_price=12)
|
||||
self.quota_shirts.items.add(self.shirt)
|
||||
@@ -1013,7 +1013,7 @@ class CartTest(CartTestMixin, TestCase):
|
||||
class CartAddonTest(CartTestMixin, TestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.workshopcat = ItemCategory.all.create(name="Workshops", is_addon=True, event=self.event)
|
||||
self.workshopcat = ItemCategory.objects.create(name="Workshops", is_addon=True, event=self.event)
|
||||
self.workshopquota = Quota.objects.create(event=self.event, name='Workshop 1', size=5)
|
||||
self.workshop1 = Item.objects.create(event=self.event, name='Workshop 1',
|
||||
category=self.workshopcat, default_price=12)
|
||||
|
||||
@@ -24,7 +24,7 @@ class CheckoutTestCase(TestCase):
|
||||
plugins='pretix.plugins.stripe,pretix.plugins.banktransfer',
|
||||
live=True
|
||||
)
|
||||
self.category = ItemCategory.all.create(event=self.event, name="Everything", position=0)
|
||||
self.category = ItemCategory.objects.create(event=self.event, name="Everything", position=0)
|
||||
self.quota_tickets = Quota.objects.create(event=self.event, name='Tickets', size=5)
|
||||
self.ticket = Item.objects.create(event=self.event, name='Early-bird ticket',
|
||||
category=self.category, default_price=23, admission=True)
|
||||
@@ -36,7 +36,7 @@ class CheckoutTestCase(TestCase):
|
||||
self.session_key = self.client.cookies.get(settings.SESSION_COOKIE_NAME).value
|
||||
self._set_session('email', 'admin@localhost')
|
||||
|
||||
self.workshopcat = ItemCategory.all.create(name="Workshops", is_addon=True, event=self.event)
|
||||
self.workshopcat = ItemCategory.objects.create(name="Workshops", is_addon=True, event=self.event)
|
||||
self.workshopquota = Quota.objects.create(event=self.event, name='Workshop 1', size=5)
|
||||
self.workshop1 = Item.objects.create(event=self.event, name='Workshop 1',
|
||||
category=self.workshopcat, default_price=12)
|
||||
|
||||
@@ -66,6 +66,18 @@ class EventMiddlewareTest(EventTestMixin, SoupTest):
|
||||
|
||||
|
||||
class ItemDisplayTest(EventTestMixin, SoupTest):
|
||||
def test_link_rewrite(self):
|
||||
q = Quota.objects.create(event=self.event, name='Quota', size=2)
|
||||
item = Item.objects.create(event=self.event, name='Early-bird ticket', default_price=0, active=True,
|
||||
description="http://example.org [Sample](http://example.net)")
|
||||
q.items.add(item)
|
||||
html = self.client.get('/%s/%s/' % (self.orga.slug, self.event.slug)).rendered_content
|
||||
|
||||
self.assertNotIn('href="http://example.org', html)
|
||||
self.assertNotIn('href="http://example.net', html)
|
||||
self.assertIn('href="/redirect/?url=http%3A//example.org%3A', html)
|
||||
self.assertIn('href="/redirect/?url=http%3A//example.net%3A', html)
|
||||
|
||||
def test_not_active(self):
|
||||
q = Quota.objects.create(event=self.event, name='Quota', size=2)
|
||||
item = Item.objects.create(event=self.event, name='Early-bird ticket', default_price=0, active=False)
|
||||
@@ -116,7 +128,7 @@ class ItemDisplayTest(EventTestMixin, SoupTest):
|
||||
self.assertNotIn("Early-bird", html)
|
||||
|
||||
def test_simple_with_category(self):
|
||||
c = ItemCategory.all.create(event=self.event, name="Entry tickets", position=0)
|
||||
c = ItemCategory.objects.create(event=self.event, name="Entry tickets", position=0)
|
||||
q = Quota.objects.create(event=self.event, name='Quota', size=2)
|
||||
item = Item.objects.create(event=self.event, name='Early-bird ticket', category=c, default_price=0)
|
||||
q.items.add(item)
|
||||
@@ -125,13 +137,13 @@ class ItemDisplayTest(EventTestMixin, SoupTest):
|
||||
self.assertIn("Early-bird", doc.select("section:nth-of-type(1) div:nth-of-type(1)")[0].text)
|
||||
|
||||
def test_simple_without_quota(self):
|
||||
c = ItemCategory.all.create(event=self.event, name="Entry tickets", position=0)
|
||||
c = ItemCategory.objects.create(event=self.event, name="Entry tickets", position=0)
|
||||
Item.objects.create(event=self.event, name='Early-bird ticket', category=c, default_price=0)
|
||||
resp = self.client.get('/%s/%s/' % (self.orga.slug, self.event.slug))
|
||||
self.assertNotIn("Early-bird", resp.rendered_content)
|
||||
|
||||
def test_no_variations_in_quota(self):
|
||||
c = ItemCategory.all.create(event=self.event, name="Entry tickets", position=0)
|
||||
c = ItemCategory.objects.create(event=self.event, name="Entry tickets", position=0)
|
||||
q = Quota.objects.create(event=self.event, name='Quota', size=2)
|
||||
item = Item.objects.create(event=self.event, name='Early-bird ticket', category=c, default_price=0)
|
||||
ItemVariation.objects.create(item=item, value='Blue')
|
||||
@@ -140,7 +152,7 @@ class ItemDisplayTest(EventTestMixin, SoupTest):
|
||||
self.assertNotIn("Early-bird", resp.rendered_content)
|
||||
|
||||
def test_one_variation_in_quota(self):
|
||||
c = ItemCategory.all.create(event=self.event, name="Entry tickets", position=0)
|
||||
c = ItemCategory.objects.create(event=self.event, name="Entry tickets", position=0)
|
||||
q = Quota.objects.create(event=self.event, name='Quota', size=2)
|
||||
item = Item.objects.create(event=self.event, name='Early-bird ticket', category=c, default_price=0)
|
||||
var1 = ItemVariation.objects.create(item=item, value='Red')
|
||||
@@ -150,7 +162,7 @@ class ItemDisplayTest(EventTestMixin, SoupTest):
|
||||
self._assert_variation_found()
|
||||
|
||||
def test_one_variation_in_unlimited_quota(self):
|
||||
c = ItemCategory.all.create(event=self.event, name="Entry tickets", position=0)
|
||||
c = ItemCategory.objects.create(event=self.event, name="Entry tickets", position=0)
|
||||
q = Quota.objects.create(event=self.event, name='Quota', size=None)
|
||||
item = Item.objects.create(event=self.event, name='Early-bird ticket', category=c, default_price=0)
|
||||
var1 = ItemVariation.objects.create(item=item, value='Red')
|
||||
@@ -166,7 +178,7 @@ class ItemDisplayTest(EventTestMixin, SoupTest):
|
||||
self.assertNotIn("Black", doc.select("section:nth-of-type(1)")[0].text)
|
||||
|
||||
def test_variation_prices_in_quota(self):
|
||||
c = ItemCategory.all.create(event=self.event, name="Entry tickets", position=0)
|
||||
c = ItemCategory.objects.create(event=self.event, name="Entry tickets", position=0)
|
||||
q = Quota.objects.create(event=self.event, name='Quota', size=2)
|
||||
item = Item.objects.create(event=self.event, name='Early-bird ticket', category=c, default_price=12)
|
||||
var1 = ItemVariation.objects.create(item=item, value='Red', default_price=14, position=1)
|
||||
|
||||
@@ -25,7 +25,7 @@ class OrdersTest(TestCase):
|
||||
self.event.settings.set('payment_banktransfer__enabled', True)
|
||||
self.event.settings.set('ticketoutput_testdummy__enabled', True)
|
||||
|
||||
self.category = ItemCategory.all.create(event=self.event, name="Everything", position=0)
|
||||
self.category = ItemCategory.objects.create(event=self.event, name="Everything", position=0)
|
||||
self.quota_shirts = Quota.objects.create(event=self.event, name='Shirts', size=2)
|
||||
self.shirt = Item.objects.create(event=self.event, name='T-Shirt', category=self.category, default_price=12)
|
||||
self.quota_shirts.items.add(self.shirt)
|
||||
|
||||
Reference in New Issue
Block a user