Compare commits

..

46 Commits

Author SHA1 Message Date
Raphael Michel
3acc29f51a Bump release 2017-08-21 15:12:05 +02:00
Raphael Michel
4bae0d1b81 [SECURITY] Rewrite all links in rich texts 2017-08-21 15:11:54 +02:00
Raphael Michel
c38a850294 [SECURITY] Fix XSS vulnerability in Lightbox caption 2017-08-21 15:09:44 +02:00
Raphael Michel
a7ec475c40 [SECURITY] Do not allow SVG files for logos 2017-08-21 15:08:39 +02:00
Raphael Michel
ac1467bd4b [SECURITY] Fix XSS injection vulnerabilities in question answers, event, quota and product names 2017-08-21 15:08:26 +02:00
Raphael Michel
3f0af67345 [SECURITY] Update to morris.js master to fix a XSS vulnerability 2017-08-21 15:07:56 +02:00
Raphael Michel
4d14b6c096 [SECURITY] Use defusedcsv for exports 2017-08-21 15:06:58 +02:00
Raphael Michel
cb789bc06c Ignore style in tests, change import order 2017-06-01 13:28:28 +02:00
Raphael Michel
17dad33f8b Bump version to 1.4.0 2017-06-01 12:57:29 +02:00
Raphael Michel
1d5c160e1d Document contact_form_fields 2017-06-01 12:57:11 +02:00
Raphael Michel
f04b7fa365 Update translations 2017-06-01 12:56:53 +02:00
Raphael Michel
fa011fbdce Update django-redis 2017-05-31 07:51:16 +02:00
Raphael Michel
759c5374d9 Fix chardet pinning in setup.py 2017-05-30 19:22:28 +02:00
Raphael Michel
f631acdf18 Force chardet update 2017-05-30 19:12:44 +02:00
Raphael Michel
b2dfd8ab11 Bankimport: Force re-annotation for badly parsed files 2017-05-30 18:43:53 +02:00
Raphael Michel
43f4803da7 Fix name being used instead of email in CSV files 2017-05-30 18:00:21 +02:00
Raphael Michel
019d8220b8 Fix wrong text with banktransfer orders manually marked as paid 2017-05-30 17:57:50 +02:00
Raphael Michel
b946010bdb Add event date range to ticket editor 2017-05-30 17:18:03 +02:00
Raphael Michel
c3097b12c3 Fix typo 2017-05-30 17:14:17 +02:00
Raphael Michel
12a53710c3 Fix test_csvparser case that changed with newer chardet 2017-05-27 14:28:39 +02:00
Raphael Michel
983326e610 Stop pinning chardet/setuptools versions 2017-05-27 12:50:18 +02:00
Claudio Luck
b4eb707b38 bump required mt940 to v4.7 2017-05-27 11:59:21 +02:00
Claudio Luck
e44f34f0a9 mt940: also consider non-SWIFT field NS 2017-05-27 11:59:21 +02:00
Claudio Luck
3c762adbf4 mt940: payer is not always available 2017-05-27 11:59:21 +02:00
Raphael Michel
ebabd20d09 [Django 1.11] Refs #481 -- Explicit sorting of NULLs 2017-05-26 09:44:11 +02:00
Raphael Michel
8694e1901a [Django 1.11] Port AddOnVariationField 2017-05-26 09:44:11 +02:00
Tobias Kunze
0f2875e89a [Django 1.11] Upgrade to Django 1.11 2017-05-26 09:44:11 +02:00
Raphael Michel
74d9921be1 Fix error 500 during building error 400 responses 2017-05-26 09:37:48 +02:00
Tobias Kunze
41e56adfdb Make checkin lists available by default (#508) 2017-05-25 16:47:09 +02:00
Raphael Michel
4ff1d302d9 Fix missing argument 2017-05-25 14:55:04 +02:00
Raphael Michel
d6e213d51a Disable inline pdfs again due to Safari issues 2017-05-25 14:43:31 +02:00
Raphael Michel
2a4deeba55 List commercial plugins 2017-05-24 14:34:20 +02:00
Raphael Michel
24b1d2afcb Disable CssAbsoluteFilter 2017-05-24 14:34:20 +02:00
Leah Oswald
5cea3d824a Fix some typos in the backup and monitoring documentation (#506)
* fix missing word typo in backup and monitoring documentation

* fix another typo in backup and monitoring documentation
2017-05-24 13:40:12 +02:00
Raphael Michel
78a8a7d744 Add Order.meta_info_data 2017-05-23 12:38:00 +02:00
Raphael Michel
a3bf85754a Add signal contact_form_fields 2017-05-23 11:43:05 +02:00
Raphael Michel
006b6fd5e8 Add support for image icons in navigation 2017-05-23 11:42:42 +02:00
Raphael Michel
8ff2c42070 Fix order changing with zero-priced products 2017-05-22 21:13:05 +02:00
Raphael Michel
c5d18c6884 Clear settings cache after migration 2017-05-22 14:17:52 +02:00
Heok Hong Low
48b3621f1e Fix #499 -- Refactor paymentinfo to payment_info (#501)
* Refactor paymentinfo to payment_info, resolve #499

* Fix calling of object attribute on tuple

* Minor update to setup documentation

* Do not use short words for typochecks

* Text clarification

* Refactor paymentinfo to payment_info, resolve #499

* Include data migration for existing event settings, resolve #499
2017-05-22 14:06:19 +02:00
Raphael Michel
fb716eb498 Add add-ons to ticket editor 2017-05-22 14:04:14 +02:00
Tobias Kunze
5d8e294350 Fix typo 2017-05-22 09:25:15 +02:00
Raphael Michel
8a96d8c24e Text clarification 2017-05-21 10:47:12 +02:00
Raphael Michel
6396d2f922 Do not use short words for typochecks 2017-05-21 10:47:12 +02:00
lhhong
ed7e90451b Minor update to setup documentation 2017-05-21 10:29:56 +02:00
lhhong
f5990dd5c4 Fix calling of object attribute on tuple 2017-05-21 10:26:58 +02:00
82 changed files with 1323 additions and 914 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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`_

View File

@@ -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(

View File

@@ -1 +1 @@
__version__ = "1.4.0-dev0"
__version__ = "1.4.1"

View File

@@ -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

View 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)
]

View File

@@ -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

View File

@@ -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

View File

@@ -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")

View File

@@ -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:

View File

@@ -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):
"""

View File

@@ -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())

View File

@@ -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}

View 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)

View File

@@ -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)

View File

@@ -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'):

View File

@@ -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.')

View File

@@ -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.')

View File

@@ -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 }}

View File

@@ -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">

View File

@@ -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>

View File

@@ -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')}),
}

View File

@@ -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',

View File

@@ -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:

View File

@@ -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()

View File

@@ -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

View File

@@ -1,6 +0,0 @@
from debug_toolbar.middleware import DebugToolbarMiddleware
from django.utils.deprecation import MiddlewareMixin
class DebugMiddlewareCompatibilityShim(MiddlewareMixin, DebugToolbarMiddleware):
pass

View 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

View File

@@ -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

View File

@@ -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."

View File

@@ -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

View File

@@ -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):

View 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()
})

View File

@@ -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'])

View File

@@ -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>

View File

@@ -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):

View File

@@ -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)

View File

@@ -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

View File

@@ -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 %}

View File

@@ -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) {

View File

@@ -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"

View File

@@ -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],

View File

@@ -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:

View File

@@ -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)

View File

@@ -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),
)

View File

@@ -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"]
)

View File

@@ -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 }}"/>

View 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>

View File

@@ -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 }}"/>

View File

@@ -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 }}"/>

View File

@@ -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 %}

View File

@@ -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

View File

@@ -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',
)

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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);
}
});

View File

@@ -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

View File

@@ -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

View File

@@ -1,2 +1,2 @@
django-redis==4.1.*
django-redis==4.8.*
redis==2.10.5

View File

@@ -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',

View File

@@ -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)

View File

@@ -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")

View File

@@ -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

View File

@@ -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)

View File

@@ -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()

View File

@@ -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),)

View File

@@ -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',

View File

@@ -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

View File

@@ -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',

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)