forked from CGM_Public/pretix_original
Group identical add-ons in cart (#2500)
Co-authored-by: Richard Schreiber <schreiber@rami.io>
This commit is contained in:
@@ -177,7 +177,7 @@ class TemplateBasedMailRenderer(BaseHTMLMailRenderer):
|
|||||||
op.variation,
|
op.variation,
|
||||||
op.subevent,
|
op.subevent,
|
||||||
op.attendee_name,
|
op.attendee_name,
|
||||||
(op.pk if op.addon_to_id else None),
|
op.addon_to_id,
|
||||||
(op.pk if op.has_addons else None)
|
(op.pk if op.has_addons else None)
|
||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
|
|||||||
@@ -1330,6 +1330,10 @@ class AbstractPosition(models.Model):
|
|||||||
else:
|
else:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def item_and_variation(self):
|
||||||
|
return self.item, self.variation
|
||||||
|
|
||||||
@meta_info_data.setter
|
@meta_info_data.setter
|
||||||
def meta_info_data(self, d):
|
def meta_info_data(self, d):
|
||||||
self.meta_info = json.dumps(d)
|
self.meta_info = json.dumps(d)
|
||||||
|
|||||||
@@ -90,13 +90,16 @@
|
|||||||
{% for groupkey, positions in cart %}
|
{% for groupkey, positions in cart %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
{% if not groupkey.4 %} {# is addon #}
|
{% if not groupkey.4 %} {# is not addon #}
|
||||||
{{ positions|length }}x
|
{{ positions|length }}x
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{% if groupkey.4 %} {# is addon #}
|
{% if groupkey.4 %} {# is addon #}
|
||||||
+
|
+
|
||||||
|
{% if positions|length > 1 %}
|
||||||
|
{{ positions|length }}x
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{{ groupkey.0.name }}{% if groupkey.1 %} – {{ groupkey.1.value }}{% endif %}
|
{{ groupkey.0.name }}{% if groupkey.1 %} – {{ groupkey.1.value }}{% endif %}
|
||||||
{% if groupkey.2 %} {# subevent #}
|
{% if groupkey.2 %} {# subevent #}
|
||||||
|
|||||||
@@ -121,8 +121,9 @@
|
|||||||
</label>
|
</label>
|
||||||
<div class="col-md-9 form-control-text">
|
<div class="col-md-9 form-control-text">
|
||||||
<ul class="addon-list">
|
<ul class="addon-list">
|
||||||
{% for a in pos.addons.all %}
|
{% regroup pos.addons.all by item_and_variation as addons_by_itemvar %}
|
||||||
<li>{{ a.item.name }}{% if a.variation %} – {{ a.variation.value }}{% endif %}</li>
|
{% for group in addons_by_itemvar %}
|
||||||
|
<li>{% if group.list|length > 1 %}{{ group.list|length }}× {% endif %}{{ group.grouper.0.name }}{% if group.grouper.1 %} – {{ group.grouper.1.value }}{% endif %}</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -205,7 +205,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% elif line.addon_to %}
|
{% elif line.addon_to %}
|
||||||
<div role="cell" class="count"> </div>
|
<div role="cell" class="count">{% if line.count == 1 %} {% else %}{{ line.count }}{% endif %}</div>
|
||||||
<div role="cell" class="singleprice price">
|
<div role="cell" class="singleprice price">
|
||||||
{% if event.settings.display_net_prices %}
|
{% if event.settings.display_net_prices %}
|
||||||
{{ line.net_price|money:event.currency }}
|
{{ line.net_price|money:event.currency }}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from functools import wraps
|
from functools import partial, wraps
|
||||||
from itertools import groupby
|
from itertools import groupby
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
@@ -145,7 +145,7 @@ class CartMixin:
|
|||||||
# Group items of the same variation
|
# Group items of the same variation
|
||||||
# We do this by list manipulations instead of a GROUP BY query, as
|
# We do this by list manipulations instead of a GROUP BY query, as
|
||||||
# Django is unable to join related models in a .values() query
|
# 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 isinstance(pos, OrderPosition):
|
||||||
if pos.addon_to_id:
|
if pos.addon_to_id:
|
||||||
i = pos.addon_to.positionid
|
i = pos.addon_to.positionid
|
||||||
@@ -167,12 +167,11 @@ class CartMixin:
|
|||||||
|
|
||||||
if downloads \
|
if downloads \
|
||||||
or pos.pk in has_addons \
|
or pos.pk in has_addons \
|
||||||
or pos.addon_to_id \
|
|
||||||
or pos.item.issue_giftcard \
|
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
|
or (answers and (has_attendee_data or bool(pos.item.questions.all()))): # do not use .exists() to re-use prefetch cache
|
||||||
return (
|
return (
|
||||||
# standalone positions are grouped by main product position id, addons below them also sorted by position id
|
# 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.
|
# all other places are only used for positions that can be grouped. We just put zeros.
|
||||||
) + (0, ) * 10
|
) + (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)
|
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
|
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)
|
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 (
|
return (
|
||||||
# These are grouped by attributes so we don't put any position ids
|
# These are grouped by attributes so we don't put any position ids
|
||||||
0, 0, 0,
|
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 = []
|
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)
|
g = list(g)
|
||||||
group = g[0]
|
group = g[0]
|
||||||
group.count = len(g)
|
group.count = len(g)
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
.count form {
|
.count form {
|
||||||
display: inline;
|
display: inline;
|
||||||
}
|
}
|
||||||
.price, .count, .download-desktop {
|
.price, .download-desktop {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
.price small,
|
.price small,
|
||||||
@@ -55,6 +55,7 @@
|
|||||||
}
|
}
|
||||||
.count {
|
.count {
|
||||||
width: percentage((2 / $grid-columns));
|
width: percentage((2 / $grid-columns));
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
.singleprice, .totalprice {
|
.singleprice, .totalprice {
|
||||||
width: percentage((3 / $grid-columns));
|
width: percentage((3 / $grid-columns));
|
||||||
|
|||||||
Reference in New Issue
Block a user