diff --git a/src/pretix/base/email.py b/src/pretix/base/email.py
index 7d0252de78..3413fe377c 100644
--- a/src/pretix/base/email.py
+++ b/src/pretix/base/email.py
@@ -177,7 +177,7 @@ class TemplateBasedMailRenderer(BaseHTMLMailRenderer):
op.variation,
op.subevent,
op.attendee_name,
- (op.pk if op.addon_to_id else None),
+ op.addon_to_id,
(op.pk if op.has_addons else None)
)
)]
diff --git a/src/pretix/base/models/orders.py b/src/pretix/base/models/orders.py
index 051888728b..6af6f739a3 100644
--- a/src/pretix/base/models/orders.py
+++ b/src/pretix/base/models/orders.py
@@ -1330,6 +1330,10 @@ class AbstractPosition(models.Model):
else:
return {}
+ @property
+ def item_and_variation(self):
+ return self.item, self.variation
+
@meta_info_data.setter
def meta_info_data(self, d):
self.meta_info = json.dumps(d)
diff --git a/src/pretix/base/templates/pretixbase/email/order_details.html b/src/pretix/base/templates/pretixbase/email/order_details.html
index 538bff2f89..6511e15ed0 100644
--- a/src/pretix/base/templates/pretixbase/email/order_details.html
+++ b/src/pretix/base/templates/pretixbase/email/order_details.html
@@ -90,13 +90,16 @@
{% for groupkey, positions in cart %}
|
- {% if not groupkey.4 %} {# is addon #}
+ {% if not groupkey.4 %} {# is not addon #}
{{ positions|length }}x
{% endif %}
|
{% if groupkey.4 %} {# is addon #}
+
+ {% if positions|length > 1 %}
+ {{ positions|length }}x
+ {% endif %}
{% endif %}
{{ groupkey.0.name }}{% if groupkey.1 %} – {{ groupkey.1.value }}{% endif %}
{% if groupkey.2 %} {# subevent #}
diff --git a/src/pretix/presale/templates/pretixpresale/event/checkout_questions.html b/src/pretix/presale/templates/pretixpresale/event/checkout_questions.html
index 6075e16fb1..dc3fb28528 100644
--- a/src/pretix/presale/templates/pretixpresale/event/checkout_questions.html
+++ b/src/pretix/presale/templates/pretixpresale/event/checkout_questions.html
@@ -121,8 +121,9 @@
- {% for a in pos.addons.all %}
- - {{ a.item.name }}{% if a.variation %} – {{ a.variation.value }}{% endif %}
+ {% regroup pos.addons.all by item_and_variation as addons_by_itemvar %}
+ {% for group in addons_by_itemvar %}
+ - {% if group.list|length > 1 %}{{ group.list|length }}× {% endif %}{{ group.grouper.0.name }}{% if group.grouper.1 %} – {{ group.grouper.1.value }}{% endif %}
{% endfor %}
diff --git a/src/pretix/presale/templates/pretixpresale/event/fragment_cart.html b/src/pretix/presale/templates/pretixpresale/event/fragment_cart.html
index 2c3dd117c4..0a39fabfc5 100644
--- a/src/pretix/presale/templates/pretixpresale/event/fragment_cart.html
+++ b/src/pretix/presale/templates/pretixpresale/event/fragment_cart.html
@@ -205,7 +205,7 @@
{% endif %}
{% elif line.addon_to %}
-
+ {% if line.count == 1 %} {% else %}{{ line.count }}{% endif %}
{% if event.settings.display_net_prices %}
{{ line.net_price|money:event.currency }}
diff --git a/src/pretix/presale/views/__init__.py b/src/pretix/presale/views/__init__.py
index f841a26af4..8f6b58d543 100644
--- a/src/pretix/presale/views/__init__.py
+++ b/src/pretix/presale/views/__init__.py
@@ -35,7 +35,7 @@
from collections import defaultdict
from datetime import datetime, timedelta
from decimal import Decimal
-from functools import wraps
+from functools import partial, wraps
from itertools import groupby
from django.conf import settings
@@ -145,7 +145,7 @@ class CartMixin:
# Group items of the same variation
# We do this by list manipulations instead of a GROUP BY query, as
# Django is unable to join related models in a .values() query
- def keyfunc(pos):
+ def keyfunc(pos, for_sorting=False):
if isinstance(pos, OrderPosition):
if pos.addon_to_id:
i = pos.addon_to.positionid
@@ -167,12 +167,11 @@ class CartMixin:
if downloads \
or pos.pk in has_addons \
- or pos.addon_to_id \
or pos.item.issue_giftcard \
or (answers and (has_attendee_data or bool(pos.item.questions.all()))): # do not use .exists() to re-use prefetch cache
return (
# standalone positions are grouped by main product position id, addons below them also sorted by position id
- i, addon_penalty, pos.pk,
+ i, addon_penalty, pos.positionid if isinstance(pos, OrderPosition) else pos.pk,
# all other places are only used for positions that can be grouped. We just put zeros.
) + (0, ) * 10
@@ -180,13 +179,22 @@ class CartMixin:
category_key = (pos.item.category.position, pos.item.category.id) if pos.item.category is not None else (0, 0)
item_key = pos.item.position, pos.item_id
variation_key = (pos.variation.position, pos.variation.id) if pos.variation is not None else (0, 0)
+ grp = category_key + item_key + variation_key + (pos.price, (pos.voucher_id or 0), (pos.subevent_id or 0), (pos.seat_id or 0))
+ if pos.addon_to_id:
+ if for_sorting:
+ ii = pos.positionid if isinstance(pos, OrderPosition) else pos.pk
+ else:
+ ii = 0
+ return (
+ i, addon_penalty, ii,
+ ) + category_key + item_key + variation_key + (pos.price, (pos.voucher_id or 0), (pos.subevent_id or 0), (pos.seat_id or 0))
return (
# These are grouped by attributes so we don't put any position ids
0, 0, 0,
- ) + category_key + item_key + variation_key + (pos.price, (pos.voucher_id or 0), (pos.subevent_id or 0), (pos.seat_id or 0))
+ ) + grp
positions = []
- for k, g in groupby(sorted(lcp, key=keyfunc), key=keyfunc):
+ for k, g in groupby(sorted(lcp, key=partial(keyfunc, for_sorting=True)), key=keyfunc):
g = list(g)
group = g[0]
group.count = len(g)
diff --git a/src/pretix/static/pretixpresale/scss/_cart.scss b/src/pretix/static/pretixpresale/scss/_cart.scss
index 3b5877781b..8c9e18531c 100644
--- a/src/pretix/static/pretixpresale/scss/_cart.scss
+++ b/src/pretix/static/pretixpresale/scss/_cart.scss
@@ -12,7 +12,7 @@
.count form {
display: inline;
}
- .price, .count, .download-desktop {
+ .price, .download-desktop {
text-align: right;
}
.price small,
@@ -55,6 +55,7 @@
}
.count {
width: percentage((2 / $grid-columns));
+ text-align: center;
}
.singleprice, .totalprice {
width: percentage((3 / $grid-columns));
|