mirror of
https://github.com/pretix/pretix.git
synced 2026-09-14 16:24:43 +00:00
.
This commit is contained in:
@@ -30,6 +30,8 @@ available_from datetime The first da
|
||||
(or ``null``).
|
||||
available_until datetime The last date time at which this discount can be applied
|
||||
(or ``null``).
|
||||
require_membership boolean If ``true``, booking this variation requires an active membership.
|
||||
require_membership_types list of integers Internal IDs of membership types valid if ``require_membership`` is ``true``
|
||||
subevent_mode strings Determines how the discount is handled when used in an
|
||||
event series. Can be ``"mixed"`` (no special effect),
|
||||
``"same"`` (discount is only applied for groups within
|
||||
@@ -108,6 +110,8 @@ Endpoints
|
||||
"sales_channels": ["web"],
|
||||
"available_from": null,
|
||||
"available_until": null,
|
||||
"require_membership_hidden": false,
|
||||
"require_membership_types": [],
|
||||
"subevent_mode": "mixed",
|
||||
"subevent_date_from": null,
|
||||
"subevent_date_until": null,
|
||||
@@ -168,6 +172,8 @@ Endpoints
|
||||
"sales_channels": ["web"],
|
||||
"available_from": null,
|
||||
"available_until": null,
|
||||
"require_membership_hidden": false,
|
||||
"require_membership_types": [],
|
||||
"subevent_mode": "mixed",
|
||||
"subevent_date_from": null,
|
||||
"subevent_date_until": null,
|
||||
@@ -214,6 +220,8 @@ Endpoints
|
||||
"sales_channels": ["web"],
|
||||
"available_from": null,
|
||||
"available_until": null,
|
||||
"require_membership_hidden": false,
|
||||
"require_membership_types": [],
|
||||
"subevent_mode": "mixed",
|
||||
"subevent_date_from": null,
|
||||
"subevent_date_until": null,
|
||||
@@ -249,6 +257,8 @@ Endpoints
|
||||
"sales_channels": ["web"],
|
||||
"available_from": null,
|
||||
"available_until": null,
|
||||
"require_membership_hidden": false,
|
||||
"require_membership_types": [],
|
||||
"subevent_mode": "mixed",
|
||||
"subevent_date_from": null,
|
||||
"subevent_date_until": null,
|
||||
@@ -313,6 +323,8 @@ Endpoints
|
||||
"sales_channels": ["web"],
|
||||
"available_from": null,
|
||||
"available_until": null,
|
||||
"require_membership_hidden": false,
|
||||
"require_membership_types": [],
|
||||
"subevent_mode": "mixed",
|
||||
"subevent_date_from": null,
|
||||
"subevent_date_until": null,
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
from django.db.models import QuerySet
|
||||
from django.utils.functional import lazy
|
||||
from rest_framework import serializers
|
||||
|
||||
from pretix.api.serializers import SalesChannelMigrationMixin
|
||||
@@ -38,7 +40,8 @@ class DiscountSerializer(SalesChannelMigrationMixin, I18nAwareModelSerializer):
|
||||
class Meta:
|
||||
model = Discount
|
||||
fields = ('id', 'active', 'internal_name', 'position', 'all_sales_channels', 'limit_sales_channels',
|
||||
'available_from', 'available_until', 'subevent_mode', 'subevent_date_from', 'subevent_date_until',
|
||||
'available_from', 'available_until', 'require_membership', 'require_membership_types',
|
||||
'subevent_mode', 'subevent_date_from', 'subevent_date_until',
|
||||
'condition_all_products', 'condition_limit_products', 'condition_apply_to_addons',
|
||||
'condition_min_count', 'condition_min_value', 'benefit_discount_matching_percent',
|
||||
'benefit_only_apply_to_cheapest_n_matches', 'benefit_same_products', 'benefit_limit_products',
|
||||
@@ -50,6 +53,7 @@ class DiscountSerializer(SalesChannelMigrationMixin, I18nAwareModelSerializer):
|
||||
self.fields['condition_limit_products'].queryset = self.context['event'].items.all()
|
||||
self.fields['benefit_limit_products'].queryset = self.context['event'].items.all()
|
||||
self.fields['limit_sales_channels'].child_relation.queryset = self.context['event'].organizer.sales_channels.all()
|
||||
self.fields['require_membership_types'].queryset = lazy(lambda: self.context['event'].organizer.membership_types.all(), QuerySet)
|
||||
|
||||
def validate(self, data):
|
||||
data = super().validate(data)
|
||||
|
||||
@@ -62,7 +62,7 @@ class DiscountViewSet(ConditionalListView, viewsets.ModelViewSet):
|
||||
|
||||
def get_queryset(self):
|
||||
return self.request.event.discounts.prefetch_related(
|
||||
'limit_sales_channels',
|
||||
'limit_sales_channels', 'require_membership_types',
|
||||
)
|
||||
|
||||
@transaction.atomic()
|
||||
|
||||
@@ -96,6 +96,16 @@ class Discount(LoggedModel):
|
||||
choices=SUBEVENT_MODE_CHOICES,
|
||||
)
|
||||
|
||||
require_membership = models.BooleanField(
|
||||
verbose_name=_('Require a valid membership'),
|
||||
default=False,
|
||||
)
|
||||
require_membership_types = models.ManyToManyField(
|
||||
'MembershipType',
|
||||
verbose_name=_('Allowed membership types'),
|
||||
blank=True,
|
||||
)
|
||||
|
||||
condition_all_products = models.BooleanField(
|
||||
default=True,
|
||||
verbose_name=_("Apply to all products (including newly created ones)")
|
||||
|
||||
@@ -1081,6 +1081,7 @@ class Event(EventMixin, LoggedModel):
|
||||
c_items = list(d.condition_limit_products.all())
|
||||
b_items = list(d.benefit_limit_products.all())
|
||||
limit_sales_channels = list(d.limit_sales_channels.all())
|
||||
require_membership_types = list(d.require_membership_types.all())
|
||||
d.pk = None
|
||||
d.event = self
|
||||
d._prefetched_objects_cache = {}
|
||||
@@ -1092,9 +1093,10 @@ class Event(EventMixin, LoggedModel):
|
||||
for i in b_items:
|
||||
if i.pk in item_map:
|
||||
d.benefit_limit_products.add(item_map[i.pk])
|
||||
|
||||
if not d.all_sales_channels:
|
||||
d.limit_sales_channels.set(self.organizer.sales_channels.filter(identifier__in=[s.identifier for s in limit_sales_channels]))
|
||||
if require_membership_types and not is_cross_organizer:
|
||||
d.require_membership_types.set(require_membership_types)
|
||||
|
||||
question_map = {}
|
||||
for q in Question.objects.filter(event=other).prefetch_related('items', 'options'):
|
||||
|
||||
@@ -86,7 +86,7 @@ def create_membership(customer: Customer, position: OrderPosition):
|
||||
def validate_memberships_in_order(customer: Customer, positions: List[AbstractPosition], event: Event, lock=False, ignored_order: Order = None, testmode=False,
|
||||
valid_from_not_chosen=False):
|
||||
"""
|
||||
Validate that a set of cart or order positions. This currently does not validate
|
||||
Validate that a set of cart or order positions.
|
||||
|
||||
:param customer: Customer to validate for
|
||||
:param positions: List of order or cart positions
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
from decimal import Decimal
|
||||
|
||||
from django import forms
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django_scopes.forms import SafeModelMultipleChoiceField
|
||||
|
||||
from pretix.base.channels import get_all_sales_channel_types
|
||||
@@ -45,6 +46,8 @@ class DiscountForm(I18nModelForm):
|
||||
'limit_sales_channels',
|
||||
'available_from',
|
||||
'available_until',
|
||||
'require_membership',
|
||||
'require_membership_types',
|
||||
'subevent_date_from',
|
||||
'subevent_date_until',
|
||||
'subevent_mode',
|
||||
@@ -107,6 +110,13 @@ class DiscountForm(I18nModelForm):
|
||||
self.fields['condition_min_value'].required = False
|
||||
self.fields['condition_min_value'].widget.is_required = False
|
||||
|
||||
qs = self.event.organizer.membership_types.all()
|
||||
if qs:
|
||||
self.fields['require_membership_types'].queryset = qs
|
||||
else:
|
||||
del self.fields['require_membership']
|
||||
del self.fields['require_membership_types']
|
||||
|
||||
if not self.event.has_subevents:
|
||||
del self.fields['subevent_mode']
|
||||
|
||||
@@ -123,4 +133,12 @@ class DiscountForm(I18nModelForm):
|
||||
d['condition_min_count'] = 0
|
||||
if d.get('condition_min_value') is None:
|
||||
d['condition_min_value'] = Decimal('0.00')
|
||||
|
||||
if d.get('require_membership') and not d.get('require_membership_types'):
|
||||
self.add_error(
|
||||
'require_membership_types',
|
||||
_(
|
||||
"If a valid membership is required, at least one valid membership type needs to be selected."
|
||||
)
|
||||
)
|
||||
return d
|
||||
|
||||
@@ -17,6 +17,12 @@
|
||||
{% bootstrap_field form.available_until layout="control" %}
|
||||
{% bootstrap_field form.all_sales_channels layout="control" %}
|
||||
{% bootstrap_field form.limit_sales_channels layout="control" %}
|
||||
{% if form.require_membership %}
|
||||
{% bootstrap_field form.require_membership layout="control" %}
|
||||
<div data-display-dependency="#{{ form.require_membership.id_for_label }}">
|
||||
{% bootstrap_field form.require_membership_types layout="control" %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Condition" context "discount" %}</legend>
|
||||
|
||||
@@ -46,6 +46,8 @@ TEST_DISCOUNT_RES = {
|
||||
"sales_channels": ["bar", "baz", "web"],
|
||||
"available_from": None,
|
||||
"available_until": None,
|
||||
"require_membership_hidden": False,
|
||||
"require_membership_types": [],
|
||||
"subevent_mode": "mixed",
|
||||
"subevent_date_from": None,
|
||||
"subevent_date_until": None,
|
||||
|
||||
Reference in New Issue
Block a user