diff --git a/src/pretix/base/services/orders.py b/src/pretix/base/services/orders.py
index b48eec48d1..2e08f9316c 100644
--- a/src/pretix/base/services/orders.py
+++ b/src/pretix/base/services/orders.py
@@ -3152,7 +3152,7 @@ def signal_listener_issue_memberships(sender: Event, order: Order, **kwargs):
if order.status != Order.STATUS_PAID or not order.customer:
return
for p in order.positions.all():
- if p.item.grant_membership_type_id:
+ if p.item.grant_membership_type_id and not p.granted_memberships.exists():
create_membership(order.customer, p)
diff --git a/src/pretix/control/templates/pretixcontrol/order/change.html b/src/pretix/control/templates/pretixcontrol/order/change.html
index 2d66c13b0c..92aa9bece7 100644
--- a/src/pretix/control/templates/pretixcontrol/order/change.html
+++ b/src/pretix/control/templates/pretixcontrol/order/change.html
@@ -151,6 +151,12 @@
{% bootstrap_field position.form.itemvar layout='inline' %}
+ {% if position.granted_memberships.all %}
+
+
+ {% trans "The sale of this position created a membership. Changing the product here will not affect the membership. Memberships can be managed in the customer account." %}
+
+ {% endif %}
@@ -254,6 +260,12 @@
{% trans "–" %}
{% bootstrap_field position.form.valid_until layout='inline' %}
+ {% if position.granted_memberships.all %}
+
+
+ {% trans "The sale of this position created a membership. Changing the validity of the ticket here will not affect the membership. Memberships can be managed in the customer account." %}
+
+ {% endif %}
diff --git a/src/pretix/control/views/orders.py b/src/pretix/control/views/orders.py
index f5d13b13f2..e86fafc417 100644
--- a/src/pretix/control/views/orders.py
+++ b/src/pretix/control/views/orders.py
@@ -1900,7 +1900,7 @@ class OrderChange(OrderView):
positions = list(self.order.positions.select_related(
'item', 'item__tax_rule', 'used_membership', 'used_membership__membership_type', 'tax_rule',
'seat', 'subevent',
- ))
+ ).prefetch_related('granted_memberships'))
for p in positions:
p.form = OrderPositionChangeForm(prefix='op-{}'.format(p.pk), instance=p, items=self.items,
initial={'seat': p.seat.seat_guid if p.seat else None},
diff --git a/src/tests/base/test_orders.py b/src/tests/base/test_orders.py
index ad474182bd..fe1edf0e29 100644
--- a/src/tests/base/test_orders.py
+++ b/src/tests/base/test_orders.py
@@ -1662,7 +1662,7 @@ class OrderChangeManagerTests(TestCase):
mt = self.event.organizer.membership_types.create(name="foo")
customer = self.event.organizer.customers.create()
self.order.customer = customer
- self.o.save()
+ self.order.save()
m = customer.memberships.create(
membership_type=mt,
date_start=now(),
@@ -1674,6 +1674,37 @@ class OrderChangeManagerTests(TestCase):
m.refresh_from_db()
assert m.canceled
+ @classscope(attr='o')
+ def test_create_membership_after_change(self):
+ mt = self.event.organizer.membership_types.create(name="foo")
+ customer = self.event.organizer.customers.create()
+ self.ticket.grant_membership_type = mt
+ self.ticket.save()
+ self.ticket2.grant_membership_type = mt
+ self.ticket2.save()
+ self.order.customer = customer
+ self.order.status = Order.STATUS_PAID
+ self.order.save()
+ assert customer.memberships.count() == 0
+ self.ocm.change_item(self.op1, item=self.ticket2, variation=None)
+ self.ocm.cancel(self.op2)
+ self.ocm.commit()
+
+ self.order.refresh_from_db()
+ self.op1.refresh_from_db()
+ customer.refresh_from_db()
+ assert self.op1.granted_memberships.count() == 1
+ assert customer.memberships.count() == 1
+
+ # But only once
+ self.ocm = OrderChangeManager(self.order, None)
+ self.ocm.change_item(self.op1, item=self.ticket, variation=None)
+ self.ocm.commit()
+
+ customer.refresh_from_db()
+ assert self.op1.granted_memberships.count() == 1
+ assert customer.memberships.count() == 1
+
@classscope(attr='o')
def test_cancel_issued_giftcard_used(self):
gc = self.o.issued_gift_cards.create(currency="EUR", issued_in=self.op1)