Use fragment_product_list in voucher redemption view (Z#23246929) (#6567)

* Use fragment_product_list in voucher redemption view (Z#23246929)

* Formatting

* Fix usage in templates

* Review comments

* handle form prefix in fragment_product_list.html

---------

Co-authored-by: Raphael Michel <michel@pretix.eu>
This commit is contained in:
pajowu
2026-09-28 13:01:15 +02:00
committed by GitHub
co-authored by Raphael Michel
parent 7e1e472691
commit 558bf910fd
7 changed files with 139 additions and 418 deletions
+13
View File
@@ -459,3 +459,16 @@ def _get_variant_unavailability_reason(variant, now_dt: Optional[datetime]=None,
return 'available_until'
else:
return None
def _item_num_options(item):
if item.current_unavailability_reason:
return 0
if item.has_variations:
return len([v for v in item.available_variations if not v.current_unavailability_reason])
else:
return 1
def get_item_option_count(items):
return sum(_item_num_options(item) for item in items)
@@ -31,7 +31,7 @@
{% endif %}
{% for item in items %}
{% if item.has_variations %}
<article aria-labelledby="{{ form_prefix }}item-{{ item.pk }}-legend"{% if item.description %} aria-describedby="{{ form_prefix }}item-{{ item.pk }}-description"{% endif %} class="item-with-variations{% if event.settings.show_variations_expanded %} details-open{% endif %}" id="{{ form_prefix }}item-{{ item.pk }}">
<article aria-labelledby="{{ form_prefix }}item-{{ item.pk }}-legend"{% if item.description %} aria-describedby="{{ form_prefix }}item-{{ item.pk }}-description"{% endif %} class="item-with-variations{% if event.settings.show_variations_expanded or always_show_open %} details-open{% endif %}" id="{{ form_prefix }}item-{{ item.pk }}">
<div class="row product-row headline">
<div class="col-md-8 col-sm-6 col-xs-12">
{% if item.picture %}
@@ -85,7 +85,7 @@
{% endif %}
</div>
<div class="col-md-2 col-sm-3 col-xs-6 availability-box">
{% if not event.settings.show_variations_expanded %}
{% if not event.settings.show_variations_expanded and not always_show_open %}
{% if item.best_variation_availability <= 10 %}
{% if not item.min_price %}
<strong class="gone">{% trans "FULLY BOOKED" %}</strong>
@@ -116,7 +116,7 @@
</div>
<div class="clearfix"></div>
</div>
<div class="variations {% if not event.settings.show_variations_expanded %}variations-collapsed{% endif %}" id="{{ form_prefix }}item-{{ item.pk }}-variations">
<div class="variations {% if not event.settings.show_variations_expanded and not always_show_open %}variations-collapsed{% endif %}" id="{{ form_prefix }}item-{{ item.pk }}-variations">
{% for var in item.available_variations %}
<article aria-labelledby="{{ form_prefix }}item-{{ item.pk }}-{{ var.pk }}-legend"{% if var.description %} aria-describedby="{{ form_prefix }}item-{{ item.pk }}-{{ var.pk }}-description"{% endif %} class="row product-row variation" id="{{ form_prefix }}item-{{ item.pk }}-{{ var.pk }}"
{% if not item.free_price %}
@@ -222,37 +222,54 @@
</div>
{% if var.cached_availability.0 == 100 and not item.current_unavailability_reason and not var.current_unavailability_reason %}
<div class="col-md-2 col-sm-3 col-xs-6 availability-box available">
{% if var.order_max == 1 %}
<label class="btn btn-default btn-checkbox{% if not ev.presale_is_running %} disabled{% endif %}">
<input type="checkbox" value="1"
{% if item.free_price %}
data-checked-onchange="price-variation-{{ item.pk }}-{{ var.pk }}"
{% endif %}
{% if not ev.presale_is_running %}disabled{% endif %}
id="{{ form_prefix }}variation_{{ item.id }}_{{ var.id }}"
name="{{ form_prefix }}variation_{{ item.id }}_{{ var.id }}"
aria-labelledby="{{ form_prefix }}item-{{ item.pk }}-{{ var.pk }}-legend {{ form_prefix }}item-{{ item.pk }}-legend"
{% if var.description %} aria-describedby="{{ form_prefix }}item-{{ item.pk }}-{{ var.pk }}-description"{% endif %}>
<i class="fa fa-shopping-cart" aria-hidden="true"></i>
{% trans "Select" context "checkbox" %}
{% if max_choices == 1 and not form_prefix %}
{# items_from_post_data which parses _selected_item does not support form_prefix at the moment #}
<label>
<input type="radio" name="{{ form_prefix }}_selected_item"
{% if itemnum == 1 %}checked="checked"{% endif %}
{% if not ev.presale_is_running %}disabled{% endif %}
id="{{ form_prefix }}variation_{{ item.id }}_{{ var.id }}"
value="{{ form_prefix }}variation_{{ item.id }}_{{ var.id }}"
aria-labelledby="{{ form_prefix }}item-{{ item.pk }}-{{ var.pk }}-legend"
{% if var.description %}aria-describedby="{{ form_prefix }}item-{{ item.pk }}-{{ var.pk }}-description"{% endif %}
{% if item.free_price %}data-checked-onchange="{{ form_prefix }}price-variation-{{ item.pk }}-{{ var.pk }}"{% endif %}
>
</label>
{% else %}
<fieldset class="input-item-count-group">
<legend class="sr-only">{% trans "Quantity" %}</legend>
<button type="button" data-step="-1" data-controls="{{ form_prefix }}variation_{{ item.id }}_{{ var.id }}" class="btn btn-default input-item-count-dec" aria-label="- {{ item }}, {{ var }}: {% trans "Decrease quantity" %}"
{% if not ev.presale_is_running %}disabled{% endif %}>-</button>
<input type="number" class="form-control input-item-count" placeholder="0" min="0"
{% if not ev.presale_is_running %}disabled{% endif %}
{% if var.order_max == 1 %}
<label class="btn btn-default btn-checkbox{% if not ev.presale_is_running %} disabled{% endif %}">
<input type="checkbox" value="1"
{% if item.free_price %}
data-checked-onchange="price-variation-{{ item.pk }}-{{ var.pk }}"
data-checked-onchange="price-variation-{{ item.pk }}-{{ var.pk }}"
{% endif %}
max="{{ var.order_max }}"
id="{{ form_prefix }}variation_{{ item.id }}_{{ var.id }}"
name="{{ form_prefix }}variation_{{ item.id }}_{{ var.id }}"
aria-labelledby="{{ form_prefix }}item-{{ item.pk }}-{{ var.pk }}-legend {{ form_prefix }}item-{{ item.pk }}-legend">
<button type="button" data-step="1" data-controls="{{ form_prefix }}variation_{{ item.id }}_{{ var.id }}" class="btn btn-default input-item-count-inc" aria-label="+ {{ item }}, {{ var }}: {% trans "Increase quantity" %}"
{% if not ev.presale_is_running %}disabled{% endif %}>+</button>
</fieldset>
{% if not ev.presale_is_running %}disabled{% endif %}
{% if itemnum == 1 %}checked="checked"{% endif %}
id="{{ form_prefix }}variation_{{ item.id }}_{{ var.id }}"
name="{{ form_prefix }}variation_{{ item.id }}_{{ var.id }}"
aria-labelledby="{{ form_prefix }}item-{{ item.pk }}-{{ var.pk }}-legend {{ form_prefix }}item-{{ item.pk }}-legend"
{% if var.description %}aria-describedby="{{ form_prefix }}item-{{ item.pk }}-{{ var.pk }}-description"{% endif %}>
<i class="fa fa-shopping-cart" aria-hidden="true"></i>
{% trans "Select" context "checkbox" %}
</label>
{% else %}
<fieldset class="input-item-count-group">
<legend class="sr-only">{% trans "Quantity" %}</legend>
<button type="button" data-step="-1" data-controls="{{ form_prefix }}variation_{{ item.id }}_{{ var.id }}" class="btn btn-default input-item-count-dec" aria-label="- {{ item }}, {{ var }}: {% trans "Decrease quantity" %}"
{% if not ev.presale_is_running %}disabled{% endif %}>-</button>
<input type="number" class="form-control input-item-count" placeholder="0" min="0"
{% if not ev.presale_is_running %}disabled{% endif %}
{% if item.free_price %}
data-checked-onchange="price-variation-{{ item.pk }}-{{ var.pk }}"
{% endif %}
max="{{ var.order_max }}"
{% if itemnum == 1 %}checked="checked"{% endif %}
id="{{ form_prefix }}variation_{{ item.id }}_{{ var.id }}"
name="{{ form_prefix }}variation_{{ item.id }}_{{ var.id }}"
aria-labelledby="{{ form_prefix }}item-{{ item.pk }}-{{ var.pk }}-legend {{ form_prefix }}item-{{ item.pk }}-legend">
<button type="button" data-step="1" data-controls="{{ form_prefix }}variation_{{ item.id }}_{{ var.id }}" class="btn btn-default input-item-count-inc" aria-label="+ {{ item }}, {{ var }}: {% trans "Increase quantity" %}"
{% if not ev.presale_is_running %}disabled{% endif %}>+</button>
</fieldset>
{% endif %}
{% endif %}
</div>
{% else %}
@@ -387,37 +404,52 @@
</div>
{% if item.cached_availability.0 == 100 and not item.current_unavailability_reason %}
<div class="col-md-2 col-sm-3 col-xs-6 availability-box available">
{% if item.order_max == 1 %}
<label class="btn btn-default btn-checkbox{% if not ev.presale_is_running %} disabled{% endif %}">
<input type="checkbox" value="1" {% if itemnum == 1 %}checked{% endif %}
{% if item.free_price %}
data-checked-onchange="price-item-{{ item.pk }}"
{% endif %}
{% if not ev.presale_is_running %}disabled{% endif %}
name="{{ form_prefix }}item_{{ item.id }}" id="{{ form_prefix }}item_{{ item.id }}"
aria-labelledby="{{ form_prefix }}item-{{ item.pk }}-legend"
{% if item.description %} aria-describedby="{{ form_prefix }}item-{{ item.id }}-description"{% endif %}>
<i class="fa fa-shopping-cart" aria-hidden="true"></i>
{% trans "Select" context "checkbox" %}
{% if max_choices == 1 and not form_prefix %}
{# items_from_post_data which parses _selected_item does not support form_prefix at the moment #}
<label>
<input type="radio" name="{{ form_prefix }}_selected_item"
{% if itemnum == 1 %}checked="checked"{% endif %}
{% if not ev.presale_is_running %}disabled{% endif %}
id="{{ form_prefix }}item_{{ item.id }}"
value="{{ form_prefix }}item_{{ item.id }}"
aria-labelledby="{{ form_prefix }}item-{{ item.pk }}-legend"
{% if item.description %}aria-describedby="{{ form_prefix }}item-{{ item.id }}-description"{% endif %}
{% if item.free_price %}data-checked-onchange="{{ form_prefix }}price-item-{{ item.pk }}"{% endif %}
>
</label>
{% else %}
<fieldset class="input-item-count-group">
<legend class="sr-only">{% trans "Quantity" %}</legend>
<button type="button" data-step="-1" data-controls="{{ form_prefix }}item_{{ item.id }}" class="btn btn-default input-item-count-dec" aria-label="- {{ item }}: {% trans "Decrease quantity" %}"
{% if not ev.presale_is_running %}disabled{% endif %}>-</button>
<input type="number" class="form-control input-item-count" placeholder="0" min="0"
{% if not ev.presale_is_running %}disabled{% endif %}
{% if itemnum == 1 %}value="1"{% endif %}
{% if item.order_max == 1 %}
<label class="btn btn-default btn-checkbox{% if not ev.presale_is_running %} disabled{% endif %}">
<input type="checkbox" value="1" {% if itemnum == 1 %}checked{% endif %}
{% if item.free_price %}
data-checked-onchange="price-item-{{ item.pk }}"
data-checked-onchange="price-item-{{ item.pk }}"
{% endif %}
max="{{ item.order_max }}"
name="{{ form_prefix }}item_{{ item.id }}"
id="{{ form_prefix }}item_{{ item.id }}"
aria-labelledby="{{ form_prefix }}item-{{ item.pk }}-legend">
<button type="button" data-step="1" data-controls="{{ form_prefix }}item_{{ item.id }}" class="btn btn-default input-item-count-inc" aria-label="+ {{ item }}: {% trans "Increase quantity" %}"
{% if not ev.presale_is_running %}disabled{% endif %}>+</button>
</fieldset>
{% if not ev.presale_is_running %}disabled{% endif %}
name="{{ form_prefix }}item_{{ item.id }}" id="{{ form_prefix }}item_{{ item.id }}"
aria-labelledby="{{ form_prefix }}item-{{ item.pk }}-legend"
{% if item.description %}aria-describedby="{{ form_prefix }}item-{{ item.id }}-description"{% endif %}>
<i class="fa fa-shopping-cart" aria-hidden="true"></i>
{% trans "Select" context "checkbox" %}
</label>
{% else %}
<fieldset class="input-item-count-group">
<legend class="sr-only">{% trans "Quantity" %}</legend>
<button type="button" data-step="-1" data-controls="{{ form_prefix }}item_{{ item.id }}" class="btn btn-default input-item-count-dec" aria-label="- {{ item }}: {% trans "Decrease quantity" %}"
{% if not ev.presale_is_running %}disabled{% endif %}>-</button>
<input type="number" class="form-control input-item-count" placeholder="0" min="0"
{% if not ev.presale_is_running %}disabled{% endif %}
{% if itemnum == 1 %}value="1"{% endif %}
{% if item.free_price %}
data-checked-onchange="price-item-{{ item.pk }}"
{% endif %}
max="{{ item.order_max }}"
name="{{ form_prefix }}item_{{ item.id }}"
id="{{ form_prefix }}item_{{ item.id }}"
aria-labelledby="{{ form_prefix }}item-{{ item.pk }}-legend">
<button type="button" data-step="1" data-controls="{{ form_prefix }}item_{{ item.id }}" class="btn btn-default input-item-count-inc" aria-label="+ {{ item }}: {% trans "Increase quantity" %}"
{% if not ev.presale_is_running %}disabled{% endif %}>+</button>
</fieldset>
{% endif %}
{% endif %}
</div>
{% else %}
@@ -38,7 +38,7 @@
{% endif %}
<p>
{% if options == 0 and not seating_available %}
{% if itemnum == 0 and not seating_available %}
{% if request.event.has_subevents and not voucher.subevent %}
{% blocktrans trimmed %}
For the selected date, there are currently no products available that can be bought with this voucher. Please try a different date or a different voucher.
@@ -73,353 +73,10 @@
{% endif %}
{% endif %}
{% for tup in items_by_category %}
<section {% if tup.0 %}aria-labelledby="category-{{ tup.0.id }}"{% else %}aria-label="{% trans "Uncategorized items" %}"{% endif %}{% if tup.0.description %} aria-describedby="category-info-{{ tup.0.id }}"{% endif %}>
{% if tup.0 %}
<h3 id="category-{{ tup.0.id }}">{{ tup.0.name }}</h3>
{% if tup.0.description %}
<div id="category-info-{{ tup.0.id }}">{{ tup.0.description|localize|rich_text }}</div>
{% endif %}
{% endif %}
{% for item in tup.1 %}
{% if item.has_variations %}
<article aria-labelledby="item-{{ item.pk }}-legend"{% if item.description %} aria-describedby="item-{{ item.pk }}-description"{% endif %} class="item-with-variations" id="item-{{ item.pk }}">
<div class="row product-row headline">
<div class="col-md-8 col-sm-6 col-xs-12">
{% if item.picture %}
<a href="{{ item.picture.url }}" class="productpicture"
data-title="{{ item.name|force_escape|force_escape }}"
{# Yes, double-escape to prevent XSS in lightbox #}
data-lightbox="{{ item.id }}"
aria-label="{% blocktrans trimmed with item=item.name %}Show full-size image of {{ item }}{% endblocktrans %}">
<img src="{{ item.picture|thumb:'60x60^' }}"
alt="{{ item.name }}"/>
</a>
{% endif %}
<div class="product-description {% if item.picture %}with-picture{% endif %}">
<h4 id="item-{{ item.pk }}-legend">
{{ item.name }}
</h4>
{% if item.description %}
<div id="item-{{ item.pk }}-description" class="product-description">
{{ item.description|localize|rich_text }}
</div>
{% endif %}
</div>
</div>
<div class="col-md-2 col-sm-3 col-xs-6 price">
{% if item.min_price != item.max_price or item.free_price %}
{% blocktrans trimmed with minprice=item.min_price|money:event.currency %}
from {{ minprice }}
{% endblocktrans %}
{% elif not item.min_price and not item.max_price %}
{% if not item.mandatory_priced_addons or voucher.all_addons_included %}
<span class="text-uppercase">{% trans "free" context "price" %}</span>
{% endif %}
{% else %}
{{ item.min_price|money:event.currency }}
{% endif %}
</div>
<div class="col-md-2 col-sm-3 col-xs-6 availability-box">
</div>
<div class="clearfix"></div>
</div>
<div class="variations">
{% for var in item.available_variations %}
<article aria-labelledby="item-{{ item.pk }}-{{ var.pk }}-legend"{% if var.description %} aria-describedby="item-{{ item.pk }}-{{ var.pk }}-description"{% endif %} class="row product-row variation" id="item-{{ item.pk }}-{{ var.pk }}"
{% if not item.free_price %}
data-price="{% if event.settings.display_net_prices %}{{ var.display_price.net|unlocalize }}{% else %}{{ var.display_price.gross|unlocalize }}{% endif %}"
{% endif %}>
<div class="col-md-8 col-sm-6 col-xs-12">
<h5 id="item-{{ item.pk }}-{{ var.pk }}-legend">{{ var }}</h5>
{% if var.description %}
<div id="item-{{ item.pk }}-{{ var.pk }}-description" class="variation-description">
{{ var.description|localize|rich_text }}
</div>
{% endif %}
{% if item.do_show_quota_left %}
{% include "pretixpresale/event/fragment_quota_left.html" with avail=var.cached_availability %}
{% endif %}
</div>
<div class="col-md-2 col-sm-3 col-xs-6 price">
{% if var.original_price %}
<p>
<del><span class="sr-only">{% trans "Original price:" %}</span>
{% if event.settings.display_net_prices %}
{{ var.original_price.net|money:event.currency }}
{% else %}
{{ var.original_price.gross|money:event.currency }}
{% endif %}
</del>
{% if item.free_price %}
</p>
{% else %}
<ins><span class="sr-only">{% trans "New price:" %}</span>
{% endif %}
{% elif not item.free_price %}
<p>
{% endif %}
{% if item.free_price %}
<label class="sr-only" for="price-variation-{{ item.pk }}-{{ var.pk }}">{% blocktrans trimmed with item=var.value %}Modify price for {{ item }}{% endblocktrans %}</label>
<div class="input-group input-group-price">
<span class="input-group-addon">{{ event.currency }}</span>
<input type="number" class="form-control input-item-price"
id="price-variation-{{ item.pk }}-{{ var.pk }}"
placeholder="0"
min="{% if event.settings.display_net_prices %}{{ var.display_price.net|stringformat:"0.2f" }}{% else %}{{ var.display_price.gross|stringformat:"0.2f" }}{% endif %}"
name="price_{{ item.id }}_{{ var.id }}"
{% if var.suggested_price.gross != var.display_price.gross %}
{% if event.settings.display_net_prices %}
title="{% blocktrans trimmed with item=var.value price=var.display_price.net|money:event.currency %}Modify price for {{ item }}, at least {{ price }}{% endblocktrans %}"
{% else %}
title="{% blocktrans trimmed with item=var.value price=var.display_price.gross|money:event.currency %}Modify price for {{ item }}, at least {{ price }}{% endblocktrans %}"
{% endif %}
{% else %}
title="{% blocktrans trimmed with item=var.value %}Modify price for {{ item }}{% endblocktrans %}"
{% endif %}
value="{% if event.settings.display_net_prices %}{{var.suggested_price.net|stringformat:"0.2f" }}{% else %}{{ var.suggested_price.gross|stringformat:"0.2f" }}{% endif %}"
step="any">
</div>
<p>
{% elif not var.display_price.gross %}
{% if not item.mandatory_priced_addons or var.original_price or voucher.all_addons_included %}
<span class="text-uppercase">{% trans "free" context "price" %}</span>
{% endif %}
{% elif event.settings.display_net_prices %}
{{ var.display_price.net|money:event.currency }}
{% else %}
{{ var.display_price.gross|money:event.currency }}
{% endif %}
{% if item.original_price or var.original_price %}
</ins>
{% endif %}
{% if item.includes_mixed_tax_rate %}
{% if event.settings.display_net_prices %}
<small>{% trans "plus taxes" %}</small>
{% else %}
<small>{% trans "incl. taxes" %}</small>
{% endif %}
{% elif var.display_price.rate and var.display_price.gross and event.settings.display_net_prices %}
<small data-toggle="tooltip" title="{% blocktrans trimmed with value=var.display_price.gross|money:event.currency %}{{ value }} incl. taxes{% endblocktrans %}" data-placement="bottom">
{% blocktrans trimmed with rate=var.display_price.rate|tax_rate_format name=var.display_price.name %}
<strong>plus</strong> {{ rate }}% {{ name }}
{% endblocktrans %}
</small>
{% elif var.display_price.rate and var.display_price.gross %}
<small data-toggle="tooltip" title="{% blocktrans trimmed with value=var.display_price.net|money:event.currency %}{{ value }} without taxes{% endblocktrans %}" data-placement="bottom">
{% blocktrans trimmed with rate=var.display_price.rate|tax_rate_format name=var.display_price.name %}
incl. {{ rate }}% {{ name }}
{% endblocktrans %}
</small>
{% endif %}
</p>
</div>
{% if var.cached_availability.0 == 100 and not item.current_unavailability_reason %}
<div class="col-md-2 col-sm-3 col-xs-6 availability-box available radio-box">
{% if max_times > 1 %}
{% if var.order_max == 1 %}
<label class="btn btn-default btn-checkbox">
<input type="checkbox"
value="1"
id="variation_{{ item.id }}_{{ var.id }}"
name="variation_{{ item.id }}_{{ var.id }}"
{% if options == 1 %}checked{% endif %}
aria-labelledby="item-{{ item.pk }}-{{ var.pk }}-legend item-{{ item.pk }}-legend"
{% if var.description %} aria-describedby="item-{{ item.pk }}-{{ var.pk }}-description"{% endif %}>
<i class="fa fa-shopping-cart" aria-hidden="true"></i>
{% trans "Select" context "checkbox" %}
</label>
{% else %}
<fieldset class="input-item-count-group">
<legend class="sr-only">{% trans "Quantity" %}</legend>
<button type="button" data-step="-1" data-controls="variation_{{ item.id }}_{{ var.id }}" class="btn btn-default input-item-count-dec" aria-label="- {{ item }}, {{ var }}: {% trans "Decrease quantity" %}">-</button>
<input type="number" class="form-control input-item-count" placeholder="0" min="0"
max="{{ var.order_max }}"
id="variation_{{ item.id }}_{{ var.id }}"
name="variation_{{ item.id }}_{{ var.id }}"
{% if options == 1 %}value="1"{% endif %}
aria-labelledby="item-{{ item.pk }}-{{ var.pk }}-legend item-{{ item.pk }}-legend">
<button type="button" data-step="1" data-controls="variation_{{ item.id }}_{{ var.id }}" class="btn btn-default input-item-count-inc" aria-label="+ {{ item }}, {{ var }}: {% trans "Increase quantity" %}">+</button>
</fieldset>
{% endif %}
{% else %}
<label>
<input type="radio" name="_voucher_item"
{% if options == 1 %}checked="checked"{% endif %}
id="variation_{{ item.id }}_{{ var.id }}"
value="variation_{{ item.id }}_{{ var.id }}"
aria-labelledby="item-{{ item.pk }}-{{ var.pk }}-legend"
{% if var.description %} aria-describedby="item-{{ item.pk }}-{{ var.pk }}-description"{% endif %}>
</label>
{% endif %}
</div>
{% else %}
{% include "pretixpresale/event/fragment_availability.html" with price=var.display_price.gross original_price=var.original_price.gross avail=var.cached_availability.0 item=item %}
{% endif %}
<div class="clearfix"></div>
</article>
{% endfor %}
</div>
</article>
{% else %}
<article aria-labelledby="item-{{ item.pk }}-legend"{% if item.description %} aria-describedby="item-{{ item.pk }}-description"{% endif %} class="row product-row simple" id="item-{{ item.pk }}"
{% if not item.free_price %}
data-price="{% if event.settings.display_net_prices %}{{ item.display_price.net|unlocalize }}{% else %}{{ item.display_price.gross|unlocalize }}{% endif %}"
{% endif %}>
<div class="col-md-8 col-sm-6 col-xs-12">
{% if item.picture %}
<a href="{{ item.picture.url }}" class="productpicture"
data-title="{{ item.name|force_escape|force_escape }}"
{# Yes, double-escape to prevent XSS in lightbox #}
data-lightbox="{{ item.id }}"
aria-label="{% blocktrans trimmed with item=item.name %}Show full-size image of {{ item }}{% endblocktrans %}">
<img src="{{ item.picture|thumb:'60x60^' }}"
alt="{{ item.name }}"/>
</a>
{% endif %}
<div class="product-description {% if item.picture %}with-picture{% endif %}">
<h4 id="item-{{ item.pk }}-legend">{{ item.name }}</h4>
{% if item.description %}
<div id="item-{{ item.pk }}-description" class="product-description">
{{ item.description|localize|rich_text }}
</div>
{% endif %}
{% if item.do_show_quota_left %}
{% include "pretixpresale/event/fragment_quota_left.html" with avail=item.cached_availability %}
{% endif %}
{% if item.min_per_order and item.min_per_order > 1 %}
<p>
<small>
{% blocktrans trimmed with num=item.min_per_order %}
minimum amount to order: {{ num }}
{% endblocktrans %}
</small>
</p>
{% endif %}
</div>
</div>
<div class="col-md-2 col-sm-3 col-xs-6 price">
{% if item.original_price %}
<p>
<del><span class="sr-only">{% trans "Original price:" %}</span>
{% if event.settings.display_net_prices %}
{{ item.original_price.net|money:event.currency }}
{% else %}
{{ item.original_price.gross|money:event.currency }}
{% endif %}
</del>
{% if item.free_price %}
</p>
{% else %}
<ins><span class="sr-only">{% trans "New price:" %}</span>
{% endif %}
{% elif not item.free_price %}
<p>
{% endif %}
{% if item.free_price %}
<label class="sr-only" for="price-item-{{ item.pk }}">{% blocktrans trimmed with item=item.name currency=event.currency %}Set price in {{ currency }} for {{ item }}{% endblocktrans %}</label>
<div class="input-group input-group-price">
<span class="input-group-addon">{{ event.currency }}</span>
<input type="number" class="form-control input-item-price" placeholder="0"
min="{% if event.settings.display_net_prices %}{{ item.display_price.net|stringformat:"0.2f" }}{% else %}{{ item.display_price.gross|stringformat:"0.2f" }}{% endif %}"
name="price_{{ item.id }}"
{% if item.suggested_price.gross != item.display_price.gross %}
{% if event.settings.display_net_prices %}
title="{% blocktrans trimmed with item=item.name price=item.display_price.net|money:event.currency %}Modify price for {{ item }}, at least {{ price }}{% endblocktrans %}"
{% else %}
title="{% blocktrans trimmed with item=item.name price=item.display_price.gross|money:event.currency %}Modify price for {{ item }}, at least {{ price }}{% endblocktrans %}"
{% endif %}
{% else %}
title="{% blocktrans trimmed with item=item.name %}Modify price for {{ item }}{% endblocktrans %}"
{% endif %}
value="{% if event.settings.display_net_prices %}{{ item.suggested_price.net|stringformat:"0.2f" }}{% else %}{{ item.suggested_price.gross|stringformat:"0.2f" }}{% endif %}"
step="any">
</div>
<p>
{% elif not item.display_price.gross %}
{% if not item.mandatory_priced_addons or item.original_price or voucher.all_addons_included %}
<span class="text-uppercase">{% trans "free" context "price" %}</span>
{% endif %}
{% elif event.settings.display_net_prices %}
{{ item.display_price.net|money:event.currency }}
{% else %}
{{ item.display_price.gross|money:event.currency }}
{% endif %}
{% if item.original_price %}
</ins>
{% endif %}
{% if item.includes_mixed_tax_rate %}
{% if event.settings.display_net_prices %}
<small>{% trans "plus taxes" %}</small>
{% else %}
<small>{% trans "incl. taxes" %}</small>
{% endif %}
{% elif item.display_price.rate and item.display_price.gross and event.settings.display_net_prices %}
<small data-toggle="tooltip" title="{% blocktrans trimmed with value=item.display_price.gross|money:event.currency %}{{ value }} incl. taxes{% endblocktrans %}" data-placement="bottom">
{% blocktrans trimmed with rate=item.display_price.rate|tax_rate_format name=item.display_price.name %}
<strong>plus</strong> {{ rate }}% {{ name }}
{% endblocktrans %}
</small>
{% elif item.display_price.rate and item.display_price.gross %}
<small data-toggle="tooltip" title="{% blocktrans trimmed with value=item.display_price.net|money:event.currency %}{{ value }} without taxes{% endblocktrans %}" data-placement="bottom">
{% blocktrans trimmed with rate=item.display_price.rate|tax_rate_format name=item.display_price.name %}
incl. {{ rate }}% {{ name }}
{% endblocktrans %}
</small>
{% endif %}
</p>
</div>
{% if item.cached_availability.0 == 100 and not item.current_unavailability_reason %}
<div class="col-md-2 col-sm-3 col-xs-6 availability-box available radio-box">
{% if max_times > 1 %}
{% if item.order_max == 1 %}
<label class="btn btn-default btn-checkbox">
<input type="checkbox"
value="1"
id="item_{{ item.id }}"
name="item_{{ item.id }}"
{% if options == 1 %}checked{% endif %}
aria-labelledby="item-{{ item.pk }}-legend"
{% if item.description %} aria-describedby="item-{{ item.id }}-description"{% endif %}>
<i class="fa fa-shopping-cart" aria-hidden="true"></i>
{% trans "Select" context "checkbox" %}
</label>
{% else %}
<fieldset class="input-item-count-group">
<legend class="sr-only">{% trans "Quantity" %}</legend>
<button type="button" data-step="-1" data-controls="item_{{ item.id }}" class="btn btn-default input-item-count-dec" aria-label="- {{ item }}: {% trans "Decrease quantity" %}">-</button>
<input type="number" class="form-control input-item-count"
placeholder="0" min="0"
max="{{ item.order_max }}"
id="item_{{ item.id }}"
name="item_{{ item.id }}"
{% if options == 1 %}value="1"{% endif %}
aria-labelledby="item-{{ item.pk }}-legend">
<button type="button" data-step="1" data-controls="item_{{ item.id }}" class="btn btn-default input-item-count-inc" aria-label="+ {{ item }}: {% trans "Increase quantity" %}">+</button>
</fieldset>
{% endif %}
{% else %}
<label>
<input type="radio" name="_voucher_item"
{% if options == 1 %}checked="checked"{% endif %}
id="item_{{ item.id }}"
value="item_{{ item.id }}"
aria-labelledby="item-{{ item.pk }}-legend"
{% if item.description %} aria-describedby="item-{{ item.id }}-description"{% endif %}>
</label>
{% endif %}
</div>
{% else %}
{% include "pretixpresale/event/fragment_availability.html" with price=item.display_price.gross original_price=item.original_price.gross avail=item.cached_availability.0 item=item %}
{% endif %}
<div class="clearfix"></div>
</article>
{% endif %}
{% endfor %}
</section>
{% endfor %}
{% include "pretixpresale/event/fragment_product_list.html" with ev=event always_show_open=True %}
{% eventsignal event "pretix.presale.signals.voucher_redeem_info" voucher=voucher %}
{% if event.presale_is_running and options > 0 %}
{% if event.presale_is_running and itemnum > 0 %}
<div class="row checkout-button-row">
<div class="col-md-4 col-md-offset-8 col-xs-12 text-center">
{% if voucher.min_usages_remaining > 1 %}
+5 -6
View File
@@ -70,7 +70,7 @@ from pretix.helpers.http import redirect_to_url
from pretix.helpers.safedownload import check_token
from pretix.multidomain.urlreverse import eventreverse
from pretix.presale.productlist import (
item_group_by_category, prepare_item_list_for_shop,
get_item_option_count, item_group_by_category, prepare_item_list_for_shop,
)
from pretix.presale.views import (
CartMixin, EventViewMixin, allow_cors_if_namespaced,
@@ -239,9 +239,9 @@ def _items_from_post_data(request, warn_if_empty=True):
# Compatibility patch that makes the frontend code a lot easier
req_items = list(request.POST.lists())
if '_voucher_item' in request.POST and '_voucher_code' in request.POST:
if '_selected_item' in request.POST:
req_items.append((
'%s' % request.POST['_voucher_item'], ('1',)
'%s' % request.POST['_selected_item'], ('1',)
))
pass
@@ -669,7 +669,7 @@ class RedeemView(NoSearchIndexViewMixin, EventViewMixin, CartMixin, TemplateView
context = super().get_context_data(**kwargs)
context['voucher'] = self.voucher
context['max_times'] = self.voucher.max_usages - self.voucher.redeemed
context['max_choices'] = self.voucher.max_usages - self.voucher.redeemed
# Fetch all items
items, display_add_to_cart = prepare_item_list_for_shop(
@@ -687,8 +687,7 @@ class RedeemView(NoSearchIndexViewMixin, EventViewMixin, CartMixin, TemplateView
# Calculate how many options the user still has. If there is only one option, we can
# check the box right away ;)
context['options'] = sum([(len(item.available_variations) if item.has_variations else 1)
for item in items])
context['itemnum'] = get_item_option_count(items)
context['allfree'] = all(
item.display_price.gross == Decimal('0.00') and not item.mandatory_priced_addons
+2 -2
View File
@@ -73,7 +73,7 @@ from pretix.helpers.http import redirect_to_url
from pretix.multidomain.urlreverse import eventreverse
from pretix.presale.ical import get_public_ical
from pretix.presale.productlist import (
item_group_by_category, prepare_item_list_for_shop,
get_item_option_count, item_group_by_category, prepare_item_list_for_shop,
)
from pretix.presale.signals import seatingframe_html_head
from pretix.presale.views.organizer import (
@@ -228,7 +228,7 @@ class EventIndex(EventViewMixin, EventListMixin, CartMixin, TemplateView):
break
items = [i for i in items if not i.requires_seat]
context['itemnum'] = len(items)
context['itemnum'] = get_item_option_count(items)
context['allfree'] = all(
item.display_price.gross == Decimal('0.00') and not item.mandatory_priced_addons
for item in items if not item.has_variations
@@ -713,7 +713,7 @@ $(function () {
$('[data-checked-onchange]').each(function () {
let countInput = this
$('#' + this.getAttribute('data-checked-onchange')).on('input', function () {
if (countInput.type === 'checkbox') {
if (countInput.type === 'checkbox' || countInput.type == "radio") {
if (countInput.checked) return
countInput.checked = true
} else if (countInput.type === 'number' && !countInput.valueAsNumber) {
+25 -5
View File
@@ -913,7 +913,7 @@ class VoucherRedeemItemDisplayTest(EventTestMixin, SoupTest):
self.q.size = 0
self.q.save()
html = self.client.get('/%s/%s/redeem?voucher=%s' % (self.orga.slug, self.event.slug, self.v.code))
assert "_voucher_item" not in html.rendered_content
assert "_selected_item" not in html.rendered_content
def test_sold_out_blocking(self):
self.q.size = 0
@@ -921,7 +921,7 @@ class VoucherRedeemItemDisplayTest(EventTestMixin, SoupTest):
self.v.block_quota = True
self.v.save()
html = self.client.get('/%s/%s/redeem?voucher=%s' % (self.orga.slug, self.event.slug, self.v.code))
assert "_voucher_item" in html.rendered_content
assert "_selected_item" in html.rendered_content
def test_sold_out_ignore(self):
self.q.size = 0
@@ -929,7 +929,7 @@ class VoucherRedeemItemDisplayTest(EventTestMixin, SoupTest):
self.v.allow_ignore_quota = True
self.v.save()
html = self.client.get('/%s/%s/redeem?voucher=%s' % (self.orga.slug, self.event.slug, self.v.code))
assert "_voucher_item" in html.rendered_content
assert "_selected_item" in html.rendered_content
def test_variations_sold_out(self):
with scopes_disabled():
@@ -940,7 +940,7 @@ class VoucherRedeemItemDisplayTest(EventTestMixin, SoupTest):
self.q.size = 0
self.q.save()
html = self.client.get('/%s/%s/redeem?voucher=%s' % (self.orga.slug, self.event.slug, self.v.code))
assert "_voucher_item" not in html.rendered_content
assert "_selected_item" not in html.rendered_content
def test_variations_sold_out_blocking(self):
with scopes_disabled():
@@ -953,7 +953,27 @@ class VoucherRedeemItemDisplayTest(EventTestMixin, SoupTest):
self.v.block_quota = True
self.v.save()
html = self.client.get('/%s/%s/redeem?voucher=%s' % (self.orga.slug, self.event.slug, self.v.code))
assert "_voucher_item" in html.rendered_content
assert "_selected_item" in html.rendered_content
def test_variation_not_available(self):
with scopes_disabled():
var1 = ItemVariation.objects.create(item=self.item, value='Red', position=1)
var2 = ItemVariation.objects.create(item=self.item, value='Black', position=2, available_from=now() + datetime.timedelta(days=2))
var3 = ItemVariation.objects.create(item=self.item, value='Purple', position=3,
available_from=now() + datetime.timedelta(days=2), available_from_mode=Item.UNAVAIL_MODE_INFO)
self.q.variations.add(var1)
self.q.variations.add(var2)
self.q.variations.add(var3)
self.q.size = 0
self.q.save()
self.v.block_quota = True
self.v.save()
html = self.client.get('/%s/%s/redeem?voucher=%s' % (self.orga.slug, self.event.slug, self.v.code))
assert "_selected_item" in html.rendered_content
assert "Black" not in html.rendered_content
assert "Red" in html.rendered_content
assert "Purple" in html.rendered_content
assert "Not available yet" in html.rendered_content
def test_voucher_price(self):
self.v.value = Decimal("10.00")