Merge branch 'master' of github.com:pretix/pretix

This commit is contained in:
Raphael Michel
2015-07-06 15:01:09 +02:00
28 changed files with 630 additions and 341 deletions
+28
View File
@@ -0,0 +1,28 @@
before_script:
tests:
script:
- git submodule init
- git submodule update
- pyvenv-3.4 --without-pip env
- source env/bin/activate
- curl https://bootstrap.pypa.io/get-pip.py | python
- cd src
- pip3 install -q -r requirements.txt
- flake8 --ignore=E123,F403,F401,N802,C901,W503 .
- python3 manage.py check
- make
- make compress
- coverage run -m py.test tests
tags:
- python3
- selenium
build:
type: deploy
script:
- cd deployment/docker/standalone/
- docker login -u ciuser -p $DOCKERPW -e admin@rami.io docker.rami.io
- docker build -t pretix/standalone .
- docker tag pretix/standalone docker.rami.io/pretix/standalone
- docker push docker.rami.io/pretix/standalone
tags:
- docker
+1
View File
@@ -27,6 +27,7 @@ RUN pip3 install -r requirements.txt
RUN pip3 install -r requirements/mysql.txt
RUN pip3 install -r requirements/postgres.txt
RUN pip3 install -r requirements/memcached.txt
RUN pip3 install -r requirements/redis.txt
RUN pip3 install gunicorn
RUN make
+21
View File
@@ -177,4 +177,25 @@ You can use an existing memcached server as pretix's caching backend::
If no memcached is configures, pretix will use Django's built-in local-memory caching method.
Redis
-----
If a redis server is configured, pretix can use it for locking, caching and session storage
to speed up various operations::
[redis]
location=redis://127.0.0.1:6379/1
sessions=false
``location``
The location of memcached, as an URL of the form ``redis://[:password]@localhost:6379/0``
or ``unix://[:password]@/path/to/socket.sock?db=0``
``session``
When this is set to true, redis will be used as the session storage.
If no redis is configured, pretix will store sessions and locks in the database. If memcached
is configured, memcached will be used for caching instead of redis.
.. _Python documentation: https://docs.python.org/3/library/configparser.html?highlight=configparser#supported-ini-file-structure
+13
View File
@@ -0,0 +1,13 @@
include LICENSE
include README.rst
recursive-include base/static *
recursive-include base/templates *
recursive-include control/static *
recursive-include control/templates *
recursive-include presale/static *
recursive-include presale/templates *
recursive-include plugins/banktransfer/templates *
recursive-include plugins/paypal/templates *
recursive-include plugins/stripe/templates *
recursive-include plugins/stripe/static *
+4 -27
View File
@@ -1408,22 +1408,8 @@ class Quota(Versionable):
:raises Quota.LockTimeoutException: if the quota is locked every time we try
to obtain the lock
"""
retries = 5
for i in range(retries):
dt = now()
updated = Quota.objects.current.filter(
Q(identity=self.identity)
& Q(Q(locked__lt=dt - timedelta(seconds=120)) | Q(locked__isnull=True))
& Q(version_end_date__isnull=True)
).update(
locked=dt
)
if updated:
self.locked_here = dt
self.locked = dt
return True
time.sleep(2 ** i / 100)
raise Quota.LockTimeoutException()
from .services import locking
return locking.lock_quota(self)
def release(self, force=False):
"""
@@ -1431,17 +1417,8 @@ class Quota(Versionable):
the lock will only be released if it was issued in _this_ python
representation of the database object.
"""
if not self.locked_here and not force:
return False
updated = Quota.objects.current.filter(
identity=self.identity,
version_end_date__isnull=True
).update(
locked=None
)
self.locked_here = None
self.locked = None
return updated
from .services import locking
return locking.release_quota(self, force)
class Order(Versionable):
+111
View File
@@ -0,0 +1,111 @@
from datetime import timedelta
import logging
import time
from django.db.models import Q
from django.utils.timezone import now
from pretix import settings
from pretix.base.models import Quota
logger = logging.getLogger('pretix.base.locking')
def lock_quota(quota):
"""
Issue a lock on this quota so nobody can take tickets from this quota until
you release the lock. Will retry 5 times on failure.
:raises Quota.LockTimeoutException: if the quota is locked every time we try
to obtain the lock
"""
if settings.HAS_REDIS:
return lock_quota_redis(quota)
else:
return lock_quota_db(quota)
def lock_quota_db(quota):
retries = 5
for i in range(retries):
dt = now()
updated = Quota.objects.current.filter(
Q(identity=quota.identity)
& Q(Q(locked__lt=dt - timedelta(seconds=120)) | Q(locked__isnull=True))
& Q(version_end_date__isnull=True)
).update(
locked=dt
)
if updated:
quota.locked_here = dt
quota.locked = dt
return True
time.sleep(2 ** i / 100)
raise Quota.LockTimeoutException()
def release_quota(quota, force=False):
"""
Release a lock placed by :py:meth:`lock()`. If the parameter force is not set to ``True``,
the lock will only be released if it was issued in _this_ python
representation of the database object.
"""
if not quota.locked_here and not force:
return False
if settings.HAS_REDIS:
return release_quota_redis(quota)
else:
return release_quota_db(quota)
def release_quota_db(quota):
updated = Quota.objects.current.filter(
identity=quota.identity,
version_end_date__isnull=True
).update(
locked=None
)
quota.locked_here = None
quota.locked = None
return updated
def redis_lock_from_quota(quota):
from django_redis import get_redis_connection
from redis.lock import Lock
if not hasattr(quota, '_redis_lock'):
rc = get_redis_connection("redis")
quota._redis_lock = Lock(redis=rc, name='pretix_quota_%s' % quota.identity, timeout=120)
return quota._redis_lock
def lock_quota_redis(quota):
from redis.exceptions import RedisError
lock = redis_lock_from_quota(quota)
retries = 5
for i in range(retries):
dt = now()
try:
if lock.acquire(False):
quota.locked_here = dt
quota.locked = dt
return True
except RedisError:
logger.exception('Error locking a quota')
raise Quota.LockTimeoutException()
time.sleep(2 ** i / 100)
raise Quota.LockTimeoutException()
def release_quota_redis(quota):
from redis import RedisError
lock = redis_lock_from_quota(quota)
try:
lock.release()
except RedisError:
logger.exception('Error releasing a quota lock')
raise Quota.LockTimeoutException()
quota.locked_here = None
quota.locked = None
return True
@@ -18,6 +18,11 @@ nav.navbar {
margin: 0;
}
.navbar-header .navbar-events {
color: white;
padding-top: 6px;
padding-bottom: 6px;
}
.nav-pills {
margin-bottom: 10px;
}
@@ -66,4 +71,9 @@ nav.navbar {
transform: rotateX(180deg);
-ms-transform: rotateX(180deg); /* IE 9 */
-webkit-transform: rotateX(180deg); /* Safari and Chrome */
}
}
@media (max-width: @screen-sm-max) {
.navbar-nav {
margin-left: 0;
}
}
@@ -8,6 +8,7 @@
{% compress css %}
<link rel="stylesheet" type="text/less" href="{% static "pretixcontrol/less/auth.less" %}" />
{% endcompress %}
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<div class="container">
@@ -18,20 +18,26 @@
<script type="text/javascript" src="{% static "pretixcontrol/js/ui/main.js" %}"></script>
{% endcompress %}
{{ html_head|safe }}
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<div id="#wrapper">
<nav class="navbar navbar-inverse navbar-static-top" role="navigation">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<button type="button" class="navbar-toggle"
data-toggle="collapse" data-target=".navbar-nav-collapse">
<span class="sr-only">{% trans "Toggle navigation" %}</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<button type="button" class="navbar-toggle navbar-events"
data-toggle="collapse" data-target=".navbar-events-collapse">
<i class="fa fa-calendar"></i><span class="caret"></span>
</button>
<a class="navbar-brand" href="{% url "control:index" %}">{{ settings.PRETIX_INSTANCE_NAME }}</a>
</div>
<ul class="nav navbar-nav navbar-top-links navbar-left">
<ul class="nav navbar-nav navbar-top-links navbar-left hidden-xs">
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><i class="fa fa-calendar"></i>
{{ request.event }} <span class="caret"></span></a>
@@ -61,7 +67,19 @@
</li>
</ul>
<div class="navbar-default sidebar" role="navigation">
<div class="sidebar-nav navbar-collapse">
<div class="sidebar-nav navbar-events-collapse navbar-collapse hidden-md hidden-lg">
<ul class="nav">
<li><a href="{% url "control:events" %}">{% trans "Event overview" %}</a></li>
{% for e in request.user.events_cache %}
<li>
<a href="{% url "control:event.index" organizer=e.organizer.slug event=e.slug %}">
{{ e.name }}
</a>
</li>
{% endfor %}
</ul>
</div>
<div class="sidebar-nav navbar-nav-collapse navbar-collapse">
<ul class="nav" id="side-menu">
{% block nav %}
<li>
@@ -7,46 +7,48 @@
<fieldset>
<legend>{% trans "Permissions" %}</legend>
{{ formset.management_form }}
<table class="table table-striped table-condensed">
<thead>
<tr>
<th>{% trans "User" %}</th>
<th>{% trans "Change settings" %}</th>
<th>{% trans "Change products" %}</th>
<th>{% trans "View orders" %}</th>
<th>{% trans "Change orders" %}</th>
<th>{% trans "Change permissions" %}</th>
<th>{% trans "Delete" %}</th>
</tr>
</thead>
<tbody>
{% for form in formset %}
<div class="table-responsive">
<table class="table table-striped table-condensed">
<thead>
<tr>
<td>{{ form.id }}{{ form.instance.user }}</td>
<td>{{ form.can_change_settings }}</td>
<td>{{ form.can_change_items }}</td>
<td>{{ form.can_view_orders }}</td>
<td>{{ form.can_change_orders }}</td>
<td>{{ form.can_change_permissions }}</td>
<td>{{ form.DELETE }}</td>
<th>{% trans "User" %}</th>
<th>{% trans "Change settings" %}</th>
<th>{% trans "Change products" %}</th>
<th>{% trans "View orders" %}</th>
<th>{% trans "Change orders" %}</th>
<th>{% trans "Change permissions" %}</th>
<th>{% trans "Delete" %}</th>
</tr>
{% endfor %}
<tr>
<td>
<div class="row-fluid">
<div class="col-sm-12">
{% bootstrap_field add_form.user layout='inline' %}
</thead>
<tbody>
{% for form in formset %}
<tr>
<td>{{ form.id }}{{ form.instance.user }}</td>
<td>{{ form.can_change_settings }}</td>
<td>{{ form.can_change_items }}</td>
<td>{{ form.can_view_orders }}</td>
<td>{{ form.can_change_orders }}</td>
<td>{{ form.can_change_permissions }}</td>
<td>{{ form.DELETE }}</td>
</tr>
{% endfor %}
<tr>
<td>
<div class="row-fluid">
<div class="col-sm-12">
{% bootstrap_field add_form.user layout='inline' %}
</div>
</div>
</div>
</td>
<td>{{ add_form.can_change_settings }}</td>
<td>{{ add_form.can_change_items }}</td>
<td>{{ add_form.can_view_orders }}</td>
<td>{{ add_form.can_change_orders }}</td>
<td>{{ add_form.can_change_permissions }}</td>
</tr>
</tbody>
</table>
</td>
<td>{{ add_form.can_change_settings }}</td>
<td>{{ add_form.can_change_items }}</td>
<td>{{ add_form.can_view_orders }}</td>
<td>{{ add_form.can_change_orders }}</td>
<td>{{ add_form.can_change_permissions }}</td>
</tr>
</tbody>
</table>
</div>
</fieldset>
<div class="form-group submit-group">
<button type="submit" class="btn btn-primary btn-save">
@@ -4,25 +4,27 @@
{% block inside %}
<form action="" method="post">
{% csrf_token %}
<table class="table">
<thead>
<tr>
<th>{{ properties.0 }}</th>
<th>{% trans "Active" %}</th>
<th>{% trans "Price" %}</th>
</tr>
</thead>
<tbody>
{% for form in forms %}
{% bootstrap_form_errors form type='all' layout='inline' %}
<tr>
<td>{{ form.values.0 }}</td>
<td>{% bootstrap_field form.active layout='inline' %}</td>
<td>{% bootstrap_field form.default_price layout='inline' %} {{ form.default_price.errors }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th>{{ properties.0 }}</th>
<th>{% trans "Active" %}</th>
<th>{% trans "Price" %}</th>
</tr>
</thead>
<tbody>
{% for form in forms %}
{% bootstrap_form_errors form type='all' layout='inline' %}
<tr>
<td>{{ form.values.0 }}</td>
<td>{% bootstrap_field form.active layout='inline' %}</td>
<td>{% bootstrap_field form.default_price layout='inline' %} {{ form.default_price.errors }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="form-group submit-group">
<button type="submit" class="btn btn-primary btn-save">
{% trans "Save" %}
@@ -8,36 +8,38 @@
{% if major.row %}
<h3>{{ major.row }}</h3>
{% endif %}
<table class="table variation-matrix">
<thead>
<tr>
<th></th>
{% for val in properties.1.values.all %}
<th>{{ val.value }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for sub in major.forms %}
<div class="table-responsive">
<table class="table variation-matrix">
<thead>
<tr>
<td>{{ sub.row.value }}</td>
{% for form in sub.forms %}
<td>
<div class="row">
<div class="col-sm-5">
{% bootstrap_field form.active layout='inline' %}
</div>
<div class="col-sm-7">
{% bootstrap_field form.default_price layout='inline' %}
</div>
</div>
{{ form.default_price.errors }}
</td>
<th></th>
{% for val in properties.1.values.all %}
<th>{{ val.value }}</th>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</thead>
<tbody>
{% for sub in major.forms %}
<tr>
<td>{{ sub.row.value }}</td>
{% for form in sub.forms %}
<td>
<div class="row">
<div class="col-sm-5">
{% bootstrap_field form.active layout='inline' %}
</div>
<div class="col-sm-7">
{% bootstrap_field form.default_price layout='inline' %}
</div>
</div>
{{ form.default_price.errors }}
</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endfor %}
<div class="form-group submit-group">
<button type="submit" class="btn btn-primary btn-save">
@@ -6,26 +6,28 @@
<p>
<a href="{% url "control:event.items.categories.add" organizer=request.event.organizer.slug event=request.event.slug %}" class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create new category" %}</a>
</p>
<table class="table table-hover">
<thead>
<tr>
<th>{% trans "Product categories" %}</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
{% for c in categories %}
<tr>
<td><strong><a href="{% url "control:event.items.categories.edit" organizer=request.event.organizer.slug event=request.event.slug category=c.identity %}">{{ c.name }}</a></strong></td>
<td>
<a href="{% url "control:event.items.categories.up" organizer=request.event.organizer.slug event=request.event.slug category=c.identity %}" class="btn btn-default btn-sm {% if forloop.counter0 == 0 %}disabled{% endif %}"><i class="fa fa-arrow-up"></i></a>
<a href="{% url "control:event.items.categories.down" organizer=request.event.organizer.slug event=request.event.slug category=c.identity %}" class="btn btn-default btn-sm {% if forloop.revcounter0 == 0 %}disabled{% endif %}"><i class="fa fa-arrow-down"></i></a>
</td>
<td class="text-right"><a href="{% url "control:event.items.categories.delete" organizer=request.event.organizer.slug event=request.event.slug category=c.identity %}" class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a></td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>{% trans "Product categories" %}</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
{% for c in categories %}
<tr>
<td><strong><a href="{% url "control:event.items.categories.edit" organizer=request.event.organizer.slug event=request.event.slug category=c.identity %}">{{ c.name }}</a></strong></td>
<td>
<a href="{% url "control:event.items.categories.up" organizer=request.event.organizer.slug event=request.event.slug category=c.identity %}" class="btn btn-default btn-sm {% if forloop.counter0 == 0 %}disabled{% endif %}"><i class="fa fa-arrow-up"></i></a>
<a href="{% url "control:event.items.categories.down" organizer=request.event.organizer.slug event=request.event.slug category=c.identity %}" class="btn btn-default btn-sm {% if forloop.revcounter0 == 0 %}disabled{% endif %}"><i class="fa fa-arrow-down"></i></a>
</td>
<td class="text-right"><a href="{% url "control:event.items.categories.delete" organizer=request.event.organizer.slug event=request.event.slug category=c.identity %}" class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% include "pretixcontrol/pagination.html" %}
{% endblock %}
@@ -14,30 +14,32 @@
<a href="{% url "control:event.items.add" organizer=request.event.organizer.slug event=request.event.slug %}"
class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create new product" %}</a>
</p>
<table class="table table-condensed table-hover">
<thead>
<tr>
<th>{% trans "Product name" %}</th>
<th>{% trans "Category" %}</th>
<th></th>
</tr>
</thead>
<tbody>
{% regroup items by category as cat_list %}
{% for c in cat_list %}
{% for i in c.list %}
<div class="table-responsive">
<table class="table table-condensed table-hover">
<thead>
<tr>
<td><strong><a href="
{% url "control:event.item" organizer=request.event.organizer.slug event=request.event.slug item=i.identity %}">{{ i.name }}</a></strong></td>
<td>{% if i.category %}{{ i.category.name }}{% endif %}</td>
<td>
<a href="{% url "control:event.items.up" organizer=request.event.organizer.slug event=request.event.slug item=i.identity %}" class="btn btn-default btn-sm {% if forloop.counter0 == 0 %}disabled{% endif %}"><i class="fa fa-arrow-up"></i></a>
<a href="{% url "control:event.items.down" organizer=request.event.organizer.slug event=request.event.slug item=i.identity %}" class="btn btn-default btn-sm {% if forloop.revcounter0 == 0 %}disabled{% endif %}"><i class="fa fa-arrow-down"></i></a>
</td>
<th>{% trans "Product name" %}</th>
<th>{% trans "Category" %}</th>
<th></th>
</tr>
</thead>
<tbody>
{% regroup items by category as cat_list %}
{% for c in cat_list %}
{% for i in c.list %}
<tr>
<td><strong><a href="
{% url "control:event.item" organizer=request.event.organizer.slug event=request.event.slug item=i.identity %}">{{ i.name }}</a></strong></td>
<td>{% if i.category %}{{ i.category.name }}{% endif %}</td>
<td>
<a href="{% url "control:event.items.up" organizer=request.event.organizer.slug event=request.event.slug item=i.identity %}" class="btn btn-default btn-sm {% if forloop.counter0 == 0 %}disabled{% endif %}"><i class="fa fa-arrow-up"></i></a>
<a href="{% url "control:event.items.down" organizer=request.event.organizer.slug event=request.event.slug item=i.identity %}" class="btn btn-default btn-sm {% if forloop.revcounter0 == 0 %}disabled{% endif %}"><i class="fa fa-arrow-down"></i></a>
</td>
</tr>
{% endfor %}
{% endfor %}
{% endfor %}
</tbody>
</table>
</tbody>
</table>
</div>
{% include "pretixcontrol/pagination.html" %}
{% endblock %}
@@ -6,23 +6,25 @@
<p>
<a href="{% url "control:event.items.properties.add" organizer=request.event.organizer.slug event=request.event.slug %}" class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create new property" %}</a>
</p>
<table class="table table-hover">
<thead>
<tr>
<th>{% trans "Product properties" %}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for p in properties %}
<tr>
<td><strong><a href="
{% url "control:event.items.properties.edit" organizer=request.event.organizer.slug event=request.event.slug property=p.identity %}">{{ p.name }}</a></strong></td>
<td class="text-right"><a href="
{% url "control:event.items.properties.delete" organizer=request.event.organizer.slug event=request.event.slug property=p.identity %}" class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a></td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>{% trans "Product properties" %}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for p in properties %}
<tr>
<td><strong><a href="
{% url "control:event.items.properties.edit" organizer=request.event.organizer.slug event=request.event.slug property=p.identity %}">{{ p.name }}</a></strong></td>
<td class="text-right"><a href="
{% url "control:event.items.properties.delete" organizer=request.event.organizer.slug event=request.event.slug property=p.identity %}" class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% include "pretixcontrol/pagination.html" %}
{% endblock %}
@@ -6,25 +6,27 @@
<p>
<a href="{% url "control:event.items.questions.add" organizer=request.event.organizer.slug event=request.event.slug %}" class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create new question" %}</a>
</p>
<table class="table table-hover">
<thead>
<tr>
<th>{% trans "Question" %}</th>
<th>{% trans "Type" %}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for q in questions %}
<tr>
<td><strong><a href="
{% url "control:event.items.questions.edit" organizer=request.event.organizer.slug event=request.event.slug question=q.identity %}">{{ q.question }}</a></strong></td>
<td>{{ q.get_type_display }}</td>
<td class="text-right"><a href="
{% url "control:event.items.questions.delete" organizer=request.event.organizer.slug event=request.event.slug question=q.identity %}" class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a></td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>{% trans "Question" %}</th>
<th>{% trans "Type" %}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for q in questions %}
<tr>
<td><strong><a href="
{% url "control:event.items.questions.edit" organizer=request.event.organizer.slug event=request.event.slug question=q.identity %}">{{ q.question }}</a></strong></td>
<td>{{ q.get_type_display }}</td>
<td class="text-right"><a href="
{% url "control:event.items.questions.delete" organizer=request.event.organizer.slug event=request.event.slug question=q.identity %}" class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% include "pretixcontrol/pagination.html" %}
{% endblock %}
@@ -6,35 +6,37 @@
<p>
<a href="{% url "control:event.items.quotas.add" organizer=request.event.organizer.slug event=request.event.slug %}" class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new quota" %}</a>
</p>
<table class="table table-hover table-quotas">
<thead>
<tr>
<th>{% trans "Quota name" %}</th>
<th>{% trans "Products" %}</th>
<th>{% trans "Total capacity" %}</th>
<th>{% trans "Capacity left" %}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for q in quotas %}
<tr>
<td><strong><a href="{% url "control:event.items.quotas.edit" organizer=request.event.organizer.slug event=request.event.slug quota=q.identity %}">{{ q.name }}</a></strong></td>
<td>
<ul>
{% for item in q.items.all %}
<li><a href="
{% url "control:event.item" organizer=request.event.organizer.slug event=request.event.slug item=item.identity %}"
>{{ item.name }}</a></li>
{% endfor %}
</ul>
</td>
<td>{{ q.size }}</td>
<td></td>
<td class="text-right"><a href="{% url "control:event.items.quotas.delete" organizer=request.event.organizer.slug event=request.event.slug quota=q.identity %}" class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a></td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="table-responsive">
<table class="table table-hover table-quotas">
<thead>
<tr>
<th>{% trans "Quota name" %}</th>
<th>{% trans "Products" %}</th>
<th>{% trans "Total capacity" %}</th>
<th>{% trans "Capacity left" %}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for q in quotas %}
<tr>
<td><strong><a href="{% url "control:event.items.quotas.edit" organizer=request.event.organizer.slug event=request.event.slug quota=q.identity %}">{{ q.name }}</a></strong></td>
<td>
<ul>
{% for item in q.items.all %}
<li><a href="
{% url "control:event.item" organizer=request.event.organizer.slug event=request.event.slug item=item.identity %}"
>{{ item.name }}</a></li>
{% endfor %}
</ul>
</td>
<td>{{ q.size }}</td>
<td></td>
<td class="text-right"><a href="{% url "control:event.items.quotas.delete" organizer=request.event.organizer.slug event=request.event.slug quota=q.identity %}" class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% include "pretixcontrol/pagination.html" %}
{% endblock %}
@@ -26,29 +26,31 @@
<button class="btn btn-primary" type="submit">{% trans "Filter" %}</button>
</form>
</p>
<table class="table table-condensed table-hover">
<thead>
<tr>
<th>{% trans "Order code" %}</th>
<th>{% trans "User" %}</th>
<th>{% trans "Order total" %}</th>
<th>{% trans "Order date" %}</th>
<th>{% trans "Status" %}</th>
</tr>
</thead>
<tbody>
{% for o in orders %}
<tr>
<td><strong><a
href="{% url "control:event.order" event=request.event.slug organizer=request.event.organizer.slug code=o.code%}"
>{{ o.code }}</a></strong></td>
<td>{{ o.user.get_short_name }}</td>
<td>{{ o.total|floatformat:2 }} {{ request.event.currency }}</td>
<td>{{ o.datetime|date:"SHORT_DATETIME_FORMAT" }}</td>
<td>{% include "pretixcontrol/orders/fragment_order_status.html" with order=o %}</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="table-responsive">
<table class="table table-condensed table-hover">
<thead>
<tr>
<th>{% trans "Order code" %}</th>
<th>{% trans "User" %}</th>
<th>{% trans "Order total" %}</th>
<th>{% trans "Order date" %}</th>
<th>{% trans "Status" %}</th>
</tr>
</thead>
<tbody>
{% for o in orders %}
<tr>
<td><strong><a
href="{% url "control:event.order" event=request.event.slug organizer=request.event.organizer.slug code=o.code%}"
>{{ o.code }}</a></strong></td>
<td>{{ o.user.get_short_name }}</td>
<td>{{ o.total|floatformat:2 }} {{ request.event.currency }}</td>
<td>{{ o.datetime|date:"SHORT_DATETIME_FORMAT" }}</td>
<td>{% include "pretixcontrol/orders/fragment_order_status.html" with order=o %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% include "pretixcontrol/pagination.html" %}
{% endblock %}
@@ -3,62 +3,64 @@
{% block title %}{% trans "Order overview" %}{% endblock %}
{% block content %}
<h1>{% trans "Order overview" %}</h1>
<table class="table table-condensed table-hover table-product-overview">
<thead>
<tr>
<th>{% trans "Product" %}</th>
<th>{% trans "Total orders" %}</th>
<th>{% trans "Payment pending" %}</th>
<th>{% trans "Cancelled" %}</th>
<th>{% trans "Refunded" %}</th>
<th>{% trans "Paid" %}</th>
</tr>
</thead>
<tbody>
{% for tup in items_by_category %}
{% if tup.0 %}
<tr class="category">
<th>{{ tup.0.name }}</th>
<th>{{ tup.0.num_total }}</th>
<th>{{ tup.0.num_pending }}</th>
<th>{{ tup.0.num_cancelled }}</th>
<th>{{ tup.0.num_refunded }}</th>
<th>{{ tup.0.num_paid }}</th>
</tr>
{% endif %}
{% for item in tup.1 %}
<tr class="item {% if tup.0 %}categorized{% endif %}">
<td>{{ item.name }}</td>
<td>{{ item.num_total }}</td>
<td>{{ item.num_pending }}</td>
<td>{{ item.num_cancelled }}</td>
<td>{{ item.num_refunded }}</td>
<td>{{ item.num_paid }}</td>
</tr>
{% if item.has_variations %}
{% for var in item.all_variations %}
<tr class="variation {% if tup.0 %}categorized{% endif %}">
<td>{{ var }}</td>
<td>{{ var.num_total }}</td>
<td>{{ var.num_pending }}</td>
<td>{{ var.num_cancelled }}</td>
<td>{{ var.num_refunded }}</td>
<td>{{ var.num_paid }}</td>
</tr>
{% endfor %}
<div class="table-responsive">
<table class="table table-condensed table-hover table-product-overview">
<thead>
<tr>
<th>{% trans "Product" %}</th>
<th>{% trans "Total orders" %}</th>
<th>{% trans "Payment pending" %}</th>
<th>{% trans "Cancelled" %}</th>
<th>{% trans "Refunded" %}</th>
<th>{% trans "Paid" %}</th>
</tr>
</thead>
<tbody>
{% for tup in items_by_category %}
{% if tup.0 %}
<tr class="category">
<th>{{ tup.0.name }}</th>
<th>{{ tup.0.num_total }}</th>
<th>{{ tup.0.num_pending }}</th>
<th>{{ tup.0.num_cancelled }}</th>
<th>{{ tup.0.num_refunded }}</th>
<th>{{ tup.0.num_paid }}</th>
</tr>
{% endif %}
{% for item in tup.1 %}
<tr class="item {% if tup.0 %}categorized{% endif %}">
<td>{{ item.name }}</td>
<td>{{ item.num_total }}</td>
<td>{{ item.num_pending }}</td>
<td>{{ item.num_cancelled }}</td>
<td>{{ item.num_refunded }}</td>
<td>{{ item.num_paid }}</td>
</tr>
{% if item.has_variations %}
{% for var in item.all_variations %}
<tr class="variation {% if tup.0 %}categorized{% endif %}">
<td>{{ var }}</td>
<td>{{ var.num_total }}</td>
<td>{{ var.num_pending }}</td>
<td>{{ var.num_cancelled }}</td>
<td>{{ var.num_refunded }}</td>
<td>{{ var.num_paid }}</td>
</tr>
{% endfor %}
{% endif %}
{% endfor %}
{% endfor %}
{% endfor %}
</tbody>
<tfoot>
<tr class="total">
<th>{% trans "Total" %}</th>
<th>{{ total.num_total }}</th>
<th>{{ total.num_pending }}</th>
<th>{{ total.num_cancelled }}</th>
<th>{{ total.num_refunded }}</th>
<th>{{ total.num_paid }}</th>
</tr>
</tfoot>
</table>
</tbody>
<tfoot>
<tr class="total">
<th>{% trans "Total" %}</th>
<th>{{ total.num_total }}</th>
<th>{{ total.num_pending }}</th>
<th>{{ total.num_cancelled }}</th>
<th>{{ total.num_refunded }}</th>
<th>{{ total.num_paid }}</th>
</tr>
</tfoot>
</table>
</div>
{% endblock %}
@@ -53,7 +53,6 @@ class BankTransfer(BasePaymentProvider):
def order_control_render(self, request, order) -> str:
if order.payment_info:
payment_info = json.loads(order.payment_info)
payment_info['amount'] /= 100
else:
payment_info = None
template = get_template('pretixplugins/banktransfer/control.html')
@@ -66,3 +66,11 @@
.checkout-button-row {
padding: 15px 0;
}
@media (max-width: @screen-sm-max) {
.checkout-button-row div {
margin-bottom: 10px;
}
.page-header h1 small {
display: block;
}
}
@@ -41,4 +41,4 @@ footer {
a:hover .panel-primary > .panel-heading {
background-color: darken(@btn-primary-bg, 10%);
border-color: darken(@btn-primary-border, 12%);
}
}
@@ -15,6 +15,7 @@
<script type="text/javascript" src="{% static "pretixpresale/js/ui/main.js" %}"></script>
{% endcompress %}
{{ html_head|safe }}
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<div class="container event">
@@ -8,6 +8,7 @@
Your order: {{ code }}
{% endblocktrans %}
{% include "pretixpresale/event/fragment_order_status.html" with order=order class="pull-right" %}
<div class="clearfix"></div>
</h2>
{% if order.status == "n" %}
<div class="panel panel-danger">
@@ -4,34 +4,37 @@
{% block content %}
<h2>{% trans "Your orders" %}</h2>
<table class="table">
<thead>
<th>{% trans "Order code" %}</th>
<th>{% trans "Date" %}</th>
<th>{% trans "Total" %}</th>
<th>{% trans "Status" %}</th>
<th></th>
</thead>
<tbody>
{% for order in orders %}
<tr>
<td>{{ order.code }}</td>
<td>{{ order.datetime|date:"SHORT_DATE_FORMAT" }}</td>
<td>{{ event.currency }} {{ order.total|floatformat:2 }}</td>
<td>{% include "pretixpresale/event/fragment_order_status.html" with order=order %}</td>
<td><a href="{% url "presale:event.order" event=request.event.slug organizer=request.event.organizer.slug order=order.code %}">
{% trans "View details" %}
</a></td>
</tr>
{% empty %}
<tr>
<td colspan="5">
<em>{% trans "You did not yet place any orders." %}</em>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="table-responsive">
<table class="table">
<thead>
<th>{% trans "Order code" %}</th>
<th>{% trans "Date" %}</th>
<th>{% trans "Total" %}</th>
<th>{% trans "Status" %}</th>
<th></th>
</thead>
<tbody>
{% for order in orders %}
<tr>
<td><a href="{% url "presale:event.order" event=request.event.slug organizer=request.event.organizer.slug order=order.code %}">
<strong>{{ order.code }}</strong></td>
<td>{{ order.datetime|date:"SHORT_DATE_FORMAT" }}</td>
<td>{{ event.currency }} {{ order.total|floatformat:2 }}</td>
<td>{% include "pretixpresale/event/fragment_order_status.html" with order=order %}</td>
<td><a href="{% url "presale:event.order" event=request.event.slug organizer=request.event.organizer.slug order=order.code %}">
{% trans "View details" %}
</a></td>
</tr>
{% empty %}
<tr>
<td colspan="5">
<em>{% trans "You did not yet place any orders." %}</em>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<a href="{% url "presale:event.index" event=request.event.slug organizer=request.event.organizer.slug %}"
class="btn btn-primary btn-lg">
<span class="fa fa-plus"></span>
+27 -6
View File
@@ -38,7 +38,7 @@ DEBUG = TEMPLATE_DEBUG = config.getboolean('django', 'debug', fallback=False)
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.' + config.get('database', 'backend', fallback='sqlite3'),
'NAME': config.get('database', 'name', fallback=os.path.join(BASE_DIR, 'db.sqlite3')),
'NAME': config.get('database', 'name', fallback=os.path.join(DATA_DIR, 'db.sqlite3')),
'USER': config.get('database', 'user', fallback=''),
'PASSWORD': config.get('database', 'password', fallback=''),
'HOST': config.get('database', 'host', fallback=''),
@@ -76,13 +76,34 @@ SESSION_COOKIE_SECURE = SESSION_COOKIE_HTTPONLY = config.getboolean(
LANGUAGE_COOKIE_DOMAIN = SESSION_COOKIE_DOMAIN = CSRF_COOKIE_DOMAIN = config.get(
'pretix', 'cookiedomain', fallback=None)
if config.has_option('memcached', 'location'):
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache',
'LOCATION': config.get('memcached', 'location'),
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': 'unique-snowflake',
}
}
HAS_MEMCACHED = config.has_option('memcached', 'location')
if HAS_MEMCACHED:
CACHES['default'] = {
'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache',
'LOCATION': config.get('memcached', 'location'),
}
HAS_REDIS = config.has_option('redis', 'location')
if HAS_REDIS:
CACHES['redis'] = {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": config.get('redis', 'location'),
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
}
}
if not HAS_MEMCACHED:
CACHES['default'] = CACHES['redis']
if config.getboolean('redis', 'sessions', fallback=False):
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
SESSION_CACHE_ALIAS = "redis"
# Internal settings
+2
View File
@@ -0,0 +1,2 @@
django-redis>=4.1,<4.2
redis>=2.10,<2.11
+54
View File
@@ -0,0 +1,54 @@
# Always prefer setuptools over distutils
from setuptools import setup, find_packages
# To use a consistent encoding
from codecs import open
from os import path
here = path.abspath(path.dirname(__file__))
# Get the long description from the relevant file
with open(path.join(here, '../README.md'), encoding='utf-8') as f:
long_description = f.read()
setup(
name='pretix',
version='0.0.0',
description='Reinventing ticket presales',
long_description=long_description,
url='http://pretix.eu',
author='Raphael Michel',
author_email='mail@raphaelmichel.de',
license='Apache License 2.0',
classifiers=[
'Development Status :: 3 - Alpha',
'Intended Audience :: Developers',
'Intended Audience :: Other Audience',
'License :: OSI Approved :: Apache Software License',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Framework :: Django :: 1.8'
],
# What does your project relate to?
keywords='tickets web shop ecommerce',
packages=['pretix'],
install_requires=[
'Django>=1.8,<1.9', 'python-dateutil>=2.4,<2.5',
'pytz', 'django-bootstrap3>=6.1,<6.2', 'django-formset-js',
'cleanerversion>=1.5,<1.7', 'django-compressor>=1.6,<2.0',
'reportlab>=3.1.44,<3.2', 'PyPDF2', 'BeautifulSoup4', 'html5lib',
'slimit', 'lxml', 'static3==0.6.1', 'dj-static', 'chardet'
],
extras_require={
'dev': ['django-debug-toolbar>=1.3.0,<2.0'],
'test': ['pep8==1.5.7', 'pyflakes', 'pep8-naming', 'flake8', 'coverage',
'selenium', 'pytest', 'pytest-django'],
'memcached': ['pylibmc'],
'mysql': ['mysqlclient'],
'paypal': ['paypalrestsdk>=1.9,<1.10,<2.0'],
'postgres': ['psycopg2'],
'redis': ['django-redis>=4.1,<4.2', 'django-redis>=4.1,<4.2'],
'stripe': ['stripe>=1.22,<1.23']
},
include_package_data=True,
)