Add new gift card to orderposition relationship (#3291)

This commit is contained in:
Raphael Michel
2023-05-09 09:54:46 +02:00
committed by GitHub
parent 996621c028
commit 6fac1aeb62
26 changed files with 477 additions and 64 deletions

View File

@@ -201,6 +201,7 @@ class PretixPosSecurityProfile(AllowListSecurityProfile):
('DELETE', 'api-v1:cartposition-detail'),
('GET', 'api-v1:giftcard-list'),
('POST', 'api-v1:giftcard-transact'),
('PATCH', 'api-v1:giftcard-detail'),
('GET', 'plugins:pretix_posbackend:posclosing-list'),
('POST', 'plugins:pretix_posbackend:posreceipt-list'),
('POST', 'plugins:pretix_posbackend:posclosing-list'),

View File

@@ -19,3 +19,30 @@
# 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 rest_framework import serializers
class AsymmetricField(serializers.Field):
def __init__(self, read, write, **kwargs):
self.read = read
self.write = write
super().__init__(
required=self.write.required,
default=self.write.default,
initial=self.write.initial,
source=self.write.source if self.write.source != self.write.field_name else None,
label=self.write.label,
allow_null=self.write.allow_null,
error_messages=self.write.error_messages,
validators=self.write.validators,
**kwargs
)
def to_internal_value(self, data):
return self.write.to_internal_value(data)
def to_representation(self, value):
return self.read.to_representation(value)
def run_validation(self, data=serializers.empty):
return self.write.run_validation(data)

View File

@@ -64,7 +64,9 @@ class ReusableMediaSerializer(I18nAwareModelSerializer):
super().__init__(*args, **kwargs)
if 'linked_giftcard' in self.context['request'].query_params.getlist('expand'):
self.fields['linked_giftcard'] = NestedGiftCardSerializer(read_only=True)
self.fields['linked_giftcard'] = NestedGiftCardSerializer(read_only=True, context=self.context)
if 'linked_giftcard.owner_ticket' in self.context['request'].query_params.getlist('expand'):
self.fields['linked_giftcard'].fields['owner_ticket'] = NestedOrderPositionSerializer(read_only=True, context=self.context)
else:
self.fields['linked_giftcard'] = serializers.PrimaryKeyRelatedField(
required=False,

View File

@@ -22,12 +22,14 @@
import logging
from decimal import Decimal
from django.core.exceptions import ObjectDoesNotExist
from django.db.models import Q
from django.utils.crypto import get_random_string
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from pretix.api.serializers import AsymmetricField
from pretix.api.serializers.i18n import I18nAwareModelSerializer
from pretix.api.serializers.order import CompatibleJSONField
from pretix.api.serializers.settings import SettingsSerializer
@@ -35,8 +37,8 @@ from pretix.base.auth import get_auth_backends
from pretix.base.i18n import get_language_without_region
from pretix.base.models import (
Customer, Device, GiftCard, GiftCardTransaction, Membership,
MembershipType, Organizer, SeatingPlan, Team, TeamAPIToken, TeamInvite,
User,
MembershipType, OrderPosition, Organizer, ReusableMedium, SeatingPlan,
Team, TeamAPIToken, TeamInvite, User,
)
from pretix.base.models.seating import SeatingPlanLayoutValidator
from pretix.base.services.mail import SendMailException, mail
@@ -127,8 +129,51 @@ class MembershipSerializer(I18nAwareModelSerializer):
return super().update(instance, validated_data)
class FlexibleTicketRelatedField(serializers.PrimaryKeyRelatedField):
def to_internal_value(self, data):
queryset = self.get_queryset()
if isinstance(data, int):
try:
return queryset.get(pk=data)
except ObjectDoesNotExist:
self.fail('does_not_exist', pk_value=data)
elif isinstance(data, str):
try:
return queryset.get(
Q(secret=data)
| Q(pseudonymization_id=data)
| Q(pk__in=ReusableMedium.objects.filter(
organizer=self.context['organizer'],
type='barcode',
identifier=data
))
)
except ObjectDoesNotExist:
self.fail('does_not_exist', pk_value=data)
self.fail('incorrect_type', data_type=type(data).__name__)
class GiftCardSerializer(I18nAwareModelSerializer):
value = serializers.DecimalField(max_digits=13, decimal_places=2, min_value=Decimal('0.00'))
owner_ticket = FlexibleTicketRelatedField(required=False, allow_null=True, queryset=OrderPosition.all.none())
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['owner_ticket'].queryset = OrderPosition.objects.filter(order__event__organizer=self.context['organizer'])
if 'owner_ticket' in self.context['request'].query_params.getlist('expand'):
from pretix.api.serializers.media import (
NestedOrderPositionSerializer,
)
self.fields['owner_ticket'] = AsymmetricField(
NestedOrderPositionSerializer(read_only=True, context=self.context),
self.fields['owner_ticket'],
)
def validate(self, data):
data = super().validate(data)
@@ -151,7 +196,7 @@ class GiftCardSerializer(I18nAwareModelSerializer):
class Meta:
model = GiftCard
fields = ('id', 'secret', 'issuance', 'value', 'currency', 'testmode', 'expires', 'conditions')
fields = ('id', 'secret', 'issuance', 'value', 'currency', 'testmode', 'expires', 'conditions', 'owner_ticket')
class OrderEventSlugField(serializers.RelatedField):

View File

@@ -179,18 +179,32 @@ class GiftCardViewSet(viewsets.ModelViewSet):
if 'include_accepted' in self.request.GET:
raise PermissionDenied("Accepted gift cards cannot be updated, use transact instead.")
GiftCard.objects.select_for_update(of=OF_SELF).get(pk=self.get_object().pk)
old_value = serializer.instance.value
value = serializer.validated_data.pop('value')
inst = serializer.save(secret=serializer.instance.secret, currency=serializer.instance.currency,
testmode=serializer.instance.testmode)
diff = value - old_value
inst.transactions.create(value=diff)
inst.log_action(
'pretix.giftcards.transaction.manual',
user=self.request.user,
auth=self.request.auth,
data={'value': diff}
)
value = serializer.validated_data.pop('value', None)
if any(k != 'value' for k in self.request.data):
inst = serializer.save(secret=serializer.instance.secret, currency=serializer.instance.currency,
testmode=serializer.instance.testmode)
inst.log_action(
'pretix.giftcards.modified',
user=self.request.user,
auth=self.request.auth,
data=self.request.data,
)
else:
inst = serializer.instance
if 'value' in self.request.data and value is not None:
old_value = serializer.instance.value
diff = value - old_value
inst.transactions.create(value=diff)
inst.log_action(
'pretix.giftcards.transaction.manual',
user=self.request.user,
auth=self.request.auth,
data={'value': diff}
)
return inst
@action(detail=True, methods=["POST"])
@@ -214,7 +228,7 @@ class GiftCardViewSet(viewsets.ModelViewSet):
auth=self.request.auth,
data={'value': value, 'text': text}
)
return Response(GiftCardSerializer(gc).data, status=status.HTTP_200_OK)
return Response(GiftCardSerializer(gc, context=self.get_serializer_context()).data, status=status.HTTP_200_OK)
def perform_destroy(self, instance):
raise MethodNotAllowed("Gift cards cannot be deleted.")

View File

@@ -0,0 +1,19 @@
# Generated by Django 3.2.18 on 2023-05-04 12:19
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('pretixbase', '0237_question_valid_string_length'),
]
operations = [
migrations.AddField(
model_name='giftcard',
name='owner_ticket',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='owned_gift_cards', to='pretixbase.orderposition'),
),
]

View File

@@ -66,6 +66,13 @@ class GiftCard(LoggedModel):
on_delete=models.PROTECT,
null=True, blank=True
)
owner_ticket = models.ForeignKey(
'OrderPosition',
related_name='owned_gift_cards',
on_delete=models.PROTECT,
null=True, blank=True,
verbose_name=_('Owned by ticket holder')
)
issuance = models.DateTimeField(
auto_now_add=True,
)

View File

@@ -1338,6 +1338,7 @@ class GiftCardFilterForm(FilterForm):
Q(secret__icontains=query)
| Q(transactions__text__icontains=query)
| Q(transactions__order__code__icontains=query)
| Q(owner_ticket__order__code__icontains=query)
)
if fdata.get('testmode') == 'yes':
qs = qs.filter(testmode=True)

View File

@@ -186,6 +186,15 @@ class OrganizerUpdateForm(OrganizerForm):
return instance
class SafeOrderPositionChoiceField(forms.ModelChoiceField):
def __init__(self, queryset, **kwargs):
queryset = queryset.model.all.none()
super().__init__(queryset, **kwargs)
def label_from_instance(self, op):
return f'{op.order.code}-{op.positionid} ({str(op.item) + ((" - " + str(op.variation)) if op.variation else "")})'
class EventMetaPropertyForm(forms.ModelForm):
class Meta:
model = EventMetaProperty
@@ -650,23 +659,32 @@ class GiftCardCreateForm(forms.ModelForm):
class GiftCardUpdateForm(forms.ModelForm):
class Meta:
model = GiftCard
fields = ['expires', 'conditions']
fields = ['expires', 'conditions', 'owner_ticket']
field_classes = {
'expires': SplitDateTimeField
'expires': SplitDateTimeField,
'owner_ticket': SafeOrderPositionChoiceField,
}
widgets = {
'expires': SplitDateTimePickerWidget,
'conditions': forms.Textarea(attrs={"rows": 2})
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
organizer = self.instance.issuer
class SafeOrderPositionChoiceField(forms.ModelChoiceField):
def __init__(self, queryset, **kwargs):
queryset = queryset.model.all.none()
super().__init__(queryset, **kwargs)
def label_from_instance(self, op):
return f'{op.order.code}-{op.positionid} ({str(op.item) + ((" - " + str(op.variation)) if op.variation else "")})'
self.fields['owner_ticket'].queryset = OrderPosition.all.filter(order__event__organizer=organizer).all()
self.fields['owner_ticket'].widget = Select2(
attrs={
'data-model-select2': 'generic',
'data-select2-url': reverse('control:organizer.ticket_select2', kwargs={
'organizer': organizer.slug,
}),
'data-placeholder': _('Ticket')
}
)
self.fields['owner_ticket'].widget.choices = self.fields['owner_ticket'].choices
self.fields['owner_ticket'].required = False
class ReusableMediumUpdateForm(forms.ModelForm):

View File

@@ -631,6 +631,12 @@
</div>
<div class="clearfix"></div>
</div>
{% for gc in line.owned_gift_cards.all %}
<div class="product-row-giftcard">
<span class="fa fa-credit-card" aria-hidden="true"></span>
<a href="{% url "control:organizer.giftcard" organizer=gc.issuer.slug giftcard=gc.pk %}">{{ gc.secret }}</a>
</div>
{% endfor %}
{% endfor %}
{% for fee in items.fees %}
<div class="row-fluid product-row {% if fee.canceled %}pos-canceled{% endif %}">

View File

@@ -46,6 +46,14 @@
{{ card.issued_in.order.full_code }}</a>-{{ card.issued_in.positionid }}
</dd>
{% endif %}
{% if card.owner_ticket %}
<dt>{% trans "Owned by ticket holder" %}</dt>
<dd>
<span class="fa fa-ticket fa-fw"></span>
<a href="{% url "control:event.order" event=card.owner_ticket.order.event.slug organizer=request.organizer.slug code=card.owner_ticket.order.code %}">
{{ card.owner_ticket.order.code }}</a>-{{ card.owner_ticket.positionid }}
</dd>
{% endif %}
</dl>
</div>
</div>

View File

@@ -14,6 +14,7 @@
{% csrf_token %}
{% bootstrap_form_errors form %}
{% bootstrap_field form.expires layout="control" %}
{% bootstrap_field form.owner_ticket layout="control" %}
{% bootstrap_field form.conditions layout="control" %}
<div class="form-group submit-group">
<button type="submit" class="btn btn-primary btn-save">

View File

@@ -367,7 +367,7 @@ class OrderDetail(OrderView):
'item', 'variation', 'addon_to', 'tax_rule', 'used_membership', 'used_membership__membership_type',
'discount',
).prefetch_related(
'item__questions', 'issued_gift_cards', 'linked_media',
'item__questions', 'issued_gift_cards', 'owned_gift_cards', 'linked_media',
Prefetch('answers', queryset=QuestionAnswer.objects.prefetch_related('options').select_related('question')),
Prefetch('all_checkins', queryset=Checkin.all.select_related('list').order_by('datetime')),
).order_by('positionid')

View File

@@ -210,7 +210,7 @@ def giftcard_select2(request, **kwargs):
return JsonResponse(doc)
@organizer_permission_required(("can_manage_reusable_media"))
@organizer_permission_required(("can_manage_reusable_media", "can_manage_gift_cards"))
def ticket_select2(request, **kwargs):
query = request.GET.get('query', '')
try:

View File

@@ -369,6 +369,20 @@
{% endif %}
<div class="clearfix"></div>
</div>
{% for gc in line.owned_gift_cards.all %}
<div class="cart-row-giftcard">
<span class="fa fa-credit-card" aria-hidden="true"></span>
<strong>{% trans "Gift card" %}</strong>
<code>{{ gc.secret }}</code>
<strong>{% trans "Current value:" %}</strong>
{{ gc.value|money:gc.currency }}
<a href="{% if position_page and line.addon_to %}{% eventurl event "presale:event.order.position.giftcard" secret=line.addon_to.web_secret order=order.code pk=gc.pk position=line.addon_to.positionid %}{% elif position_page %}{% eventurl event "presale:event.order.position.giftcard" secret=line.web_secret order=order.code pk=gc.pk position=line.positionid %}{% else %}{% eventurl event "presale:event.order.giftcard" secret=order.secret order=order.code pk=gc.pk %}{% endif %}"
class="btn btn-default btn-sm">
<span class="fa fa-eye" aria-hidden="true"></span>
{% trans "Details" %}
</a>
</div>
{% endfor %}
{% endfor %}
{% for fee in cart.fees %}
<div class="row cart-row">

View File

@@ -0,0 +1,29 @@
{% load i18n %}
{% load money %}
<table class="panel-body table">
<thead>
<tr>
<th>{% trans "Date" %}</th>
<th>{% trans "Order" %}</th>
<th class="text-right">{% trans "Value" %}</th>
</tr>
</thead>
<tbody>
{% for t in transactions %}
<tr>
<td>{{ t.datetime|date:"SHORT_DATETIME_FORMAT" }}</td>
<td>
{% if t.order %}
{{ t.order.full_code }}
{% else %}
<em>{% if t.text %}{{ t.text }}{% else %}{% trans "Manual transaction" %}{% endif %}</em>
{% endif %}
</td>
<td class="text-right">
{{ t.value|money:giftcard.currency }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% include "pretixcontrol/pagination.html" %}

View File

@@ -0,0 +1,24 @@
{% extends "pretixpresale/event/base.html" %}
{% load i18n %}
{% load money %}
{% load eventurl %}
{% load l10n %}
{% load rich_text %}
{% block title %}{% trans "Gift card" %}{% endblock %}
{% block content %}
<h2>
{% blocktrans trimmed with code=giftcard.secret %}
Gift card: {{ code }}
{% endblocktrans %}
<a href="{% eventurl request.event "presale:event.order" secret=order.secret order=order.code %}"
class="btn btn-default btn-sm btn-link">
<span class="fa fa-caret-left"></span>
{% trans "Back" %}
</a>
</h2>
<p>
<strong>{% trans "Current value:" %} {{ giftcard.value|money:giftcard.currency }}</strong>
</p>
<h3>{% trans "Transaction history" %}</h3>
{% include "pretixpresale/event/fragment_giftcard_history.html" %}
{% endblock %}

View File

@@ -0,0 +1,24 @@
{% extends "pretixpresale/event/base.html" %}
{% load i18n %}
{% load money %}
{% load eventurl %}
{% load l10n %}
{% load rich_text %}
{% block title %}{% trans "Gift card" %}{% endblock %}
{% block content %}
<h2>
{% blocktrans trimmed with code=giftcard.secret %}
Gift card: {{ code }}
{% endblocktrans %}
<a href="{% eventurl request.event "presale:event.order.position" secret=position.web_secret order=order.code position=position.positionid %}"
class="btn btn-default btn-sm btn-link">
<span class="fa fa-caret-left"></span>
{% trans "Back" %}
</a>
</h2>
<p>
<strong>{% trans "Current value:" %} {{ giftcard.value|money:giftcard.currency }}</strong>
</p>
<h3>{% trans "Transaction history" %}</h3>
{% include "pretixpresale/event/fragment_giftcard_history.html" %}
{% endblock %}

View File

@@ -140,6 +140,9 @@ event_patterns = [
re_path(r'^order/(?P<order>[^/]+)/(?P<secret>[A-Za-z0-9]+)/download/(?P<position>[0-9]+)/(?P<output>[^/]+)$',
pretix.presale.views.order.OrderDownload.as_view(),
name='event.order.download'),
re_path(r'^order/(?P<order>[^/]+)/(?P<secret>[A-Za-z0-9]+)/giftcard/(?P<pk>[0-9]+)/$',
pretix.presale.views.order.OrderGiftCardDetails.as_view(),
name='event.order.giftcard'),
re_path(r'^order/(?P<order>[^/]+)/(?P<secret>[A-Za-z0-9]+)/invoice/(?P<invoice>[0-9]+)$',
pretix.presale.views.order.InvoiceDownload.as_view(),
name='event.invoice.download'),
@@ -150,6 +153,9 @@ event_patterns = [
re_path(r'^ticket/(?P<order>[^/]+)/(?P<position>\d+)/(?P<secret>[A-Za-z0-9]+)/download/(?P<pid>[0-9]+)/(?P<output>[^/]+)$',
pretix.presale.views.order.OrderPositionDownload.as_view(),
name='event.order.position.download'),
re_path(r'^ticket/(?P<order>[^/]+)/(?P<position>\d+)/(?P<secret>[A-Za-z0-9]+)/giftcard/(?P<pk>[0-9]+)/$',
pretix.presale.views.order.OrderPositionGiftCardDetails.as_view(),
name='event.order.position.giftcard'),
re_path(r'^ticket/(?P<order>[^/]+)/(?P<position>\d+)/(?P<secret>[A-Za-z0-9]+)/change$',
pretix.presale.views.order.OrderPositionChange.as_view(),
name='event.order.position.change'),

View File

@@ -57,7 +57,7 @@ from django.utils.functional import cached_property
from django.utils.timezone import now
from django.utils.translation import gettext, gettext_lazy as _
from django.views.decorators.clickjacking import xframe_options_exempt
from django.views.generic import TemplateView, View
from django.views.generic import ListView, TemplateView, View
from pretix.base.models import (
CachedTicket, Checkin, GiftCard, Invoice, Order, OrderPosition, Quota,
@@ -241,7 +241,7 @@ class OrderDetails(EventViewMixin, OrderDetailMixin, CartMixin, TicketPageMixin,
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
qs = self.order.positions.prefetch_related('issued_gift_cards').select_related('tax_rule')
qs = self.order.positions.prefetch_related('issued_gift_cards', 'owned_gift_cards').select_related('tax_rule')
if self.request.event.settings.show_checkin_number_user:
qs = qs.annotate(
checkin_count=Subquery(
@@ -1128,6 +1128,53 @@ class OrderDownload(OrderDownloadMixin, EventViewMixin, OrderDetailMixin, AsyncA
return None
@method_decorator(xframe_options_exempt, 'dispatch')
class OrderGiftCardDetails(EventViewMixin, OrderDetailMixin, ListView):
template_name = 'pretixpresale/event/order_giftcard.html'
context_object_name = 'transactions'
paginate_by = 50
@cached_property
def giftcard(self):
return GiftCard.objects.filter(
owner_ticket__order_id=self.order.pk
).get(pk=self.kwargs['pk'])
def get_queryset(self):
return self.giftcard.transactions.order_by('-datetime', '-pk')
def get_context_data(self, **kwargs):
return super().get_context_data(
order=self.order,
giftcard=self.giftcard,
**kwargs,
)
@method_decorator(xframe_options_exempt, 'dispatch')
class OrderPositionGiftCardDetails(EventViewMixin, OrderPositionDetailMixin, ListView):
template_name = 'pretixpresale/event/position_giftcard.html'
context_object_name = 'transactions'
paginate_by = 50
@cached_property
def giftcard(self):
return GiftCard.objects.filter(
Q(owner_ticket_id=self.position.pk) | Q(owner_ticket__addon_to_id=self.position.pk)
).get(pk=self.kwargs['pk'])
def get_queryset(self):
return self.giftcard.transactions.order_by('-datetime', '-pk')
def get_context_data(self, **kwargs):
return super().get_context_data(
order=self.order,
position=self.position,
giftcard=self.giftcard,
**kwargs,
)
@method_decorator(xframe_options_exempt, 'dispatch')
class OrderPositionDownload(OrderDownloadMixin, EventViewMixin, OrderPositionDetailMixin, AsyncAction, View):
task = generate

View File

@@ -101,6 +101,16 @@ nav.navbar .danger, nav.navbar .danger:hover, nav.navbar .danger:active {
}
}
.product-row-giftcard {
border-radius: $border-radius-base;
padding: 10px 1.4em;
margin: 0 0 5px 1.4em;
background: $panel-footer-bg;
code {
color: $text-color;
}
}
h1 .btn-sm {
margin-top: -5px;
}

View File

@@ -106,6 +106,16 @@
}
}
.cart-row-giftcard {
border-radius: $border-radius-base;
padding: 10px 1.4em;
margin: 0 0 5px 1.4em;
background: $panel-footer-bg;
code {
color: $text-color;
}
}
.cart .firstchild-in-panel .cart-row:first-child {
padding-top: 0;
}

View File

@@ -20,12 +20,14 @@
# <https://www.gnu.org/licenses/>.
#
import copy
from datetime import timedelta
from decimal import Decimal
import pytest
from django.utils.timezone import now
from django_scopes import scopes_disabled
from pretix.base.models import GiftCard, Organizer
from pretix.base.models import GiftCard, Order, Organizer
@pytest.fixture
@@ -50,7 +52,8 @@ TEST_GC_RES = {
"testmode": False,
"expires": None,
"conditions": None,
"currency": "EUR"
"currency": "EUR",
"owner_ticket": None
}
@@ -93,6 +96,63 @@ def test_giftcard_detail(token_client, organizer, event, giftcard):
assert res == resp.data
@pytest.mark.django_db
def test_giftcard_detail_expand(token_client, organizer, event, giftcard):
with scopes_disabled():
o = Order.objects.create(
code='FOO', event=event, email='dummy@dummy.test',
status=Order.STATUS_PENDING, datetime=now(), expires=now() + timedelta(days=10),
total=14, locale='en'
)
ticket = event.items.create(name='Early-bird ticket', category=None, default_price=23, admission=True,
personalized=True)
op = o.positions.create(item=ticket, price=Decimal("14"))
giftcard.owner_ticket = op
giftcard.save()
res = dict(TEST_GC_RES)
res["id"] = giftcard.pk
res["issuance"] = giftcard.issuance.isoformat().replace('+00:00', 'Z')
resp = token_client.get('/api/v1/organizers/{}/giftcards/{}/?expand=owner_ticket'.format(organizer.slug, giftcard.pk))
assert resp.status_code == 200
assert resp.data["owner_ticket"] == {
"id": op.pk,
"order": {"code": "FOO", "event": "dummy"},
"positionid": op.positionid,
"item": ticket.pk,
"variation": None,
"price": "14.00",
"attendee_name": None,
"attendee_name_parts": {},
"company": None,
"street": None,
"zipcode": None,
"city": None,
"country": None,
"state": None,
"discount": None,
"attendee_email": None,
"voucher": None,
"tax_rate": "0.00",
"tax_value": "0.00",
"secret": op.secret,
"addon_to": None,
"subevent": None,
"checkins": [],
"downloads": [],
"answers": [],
"tax_rule": None,
"pseudonymization_id": op.pseudonymization_id,
"pdf_data": {},
"seat": None,
"canceled": False,
"valid_from": None,
"valid_until": None,
"blocked": None
}
TEST_GIFTCARD_CREATE_PAYLOAD = {
"secret": "DEFABC",
"value": "12.00",
@@ -129,34 +189,51 @@ def test_giftcard_duplicate_secert(token_client, organizer, event, giftcard):
@pytest.mark.django_db
def test_giftcard_patch(token_client, organizer, event, giftcard):
resp = token_client.patch(
'/api/v1/organizers/{}/giftcards/{}/'.format(organizer.slug, giftcard.pk),
{
'secret': 'foo',
'value': '10.00',
'testmode': True,
'currency': 'USD'
},
format='json'
)
assert resp.status_code == 200
giftcard.refresh_from_db()
assert giftcard.value == Decimal('10.00')
assert giftcard.secret == "ABCDEF"
assert giftcard.currency == "EUR"
assert not giftcard.testmode
def test_giftcard_patch_owner_by_id(token_client, organizer, event, giftcard):
with scopes_disabled():
o = Order.objects.create(
code='FOO', event=event, email='dummy@dummy.test',
status=Order.STATUS_PENDING, datetime=now(), expires=now() + timedelta(days=10),
total=14, locale='en'
)
ticket = event.items.create(name='Early-bird ticket', category=None, default_price=23, admission=True,
personalized=True)
op = o.positions.create(item=ticket, price=Decimal("14"))
resp = token_client.patch(
'/api/v1/organizers/{}/giftcards/{}/'.format(organizer.slug, giftcard.pk),
{
'value': '9.00',
'owner_ticket': op.pk,
},
format='json'
)
assert resp.status_code == 200
giftcard.refresh_from_db()
assert giftcard.value == Decimal('9.00')
assert giftcard.owner_ticket == op
@pytest.mark.django_db
def test_giftcard_patch_owner_by_secret(token_client, organizer, event, giftcard):
with scopes_disabled():
o = Order.objects.create(
code='FOO', event=event, email='dummy@dummy.test',
status=Order.STATUS_PENDING, datetime=now(), expires=now() + timedelta(days=10),
total=14, locale='en'
)
ticket = event.items.create(name='Early-bird ticket', category=None, default_price=23, admission=True,
personalized=True)
op = o.positions.create(item=ticket, price=Decimal("14"))
resp = token_client.patch(
'/api/v1/organizers/{}/giftcards/{}/'.format(organizer.slug, giftcard.pk),
{
'owner_ticket': op.secret,
},
format='json'
)
assert resp.status_code == 200
giftcard.refresh_from_db()
assert giftcard.owner_ticket == op
@pytest.mark.django_db

View File

@@ -121,9 +121,14 @@ def test_medium_detail(token_client, organizer, event, medium, giftcard, custome
medium.linked_giftcard = giftcard
medium.customer = customer
medium.save()
giftcard.owner_ticket = op
giftcard.save()
resp = token_client.get(
'/api/v1/organizers/{}/reusablemedia/{}/?expand=linked_giftcard&expand=linked_orderposition&expand=customer'.format(
organizer.slug, medium.pk))
'/api/v1/organizers/{}/reusablemedia/{}/?expand=linked_giftcard&expand='
'linked_giftcard.owner_ticket&expand=linked_orderposition&expand=customer'.format(
organizer.slug, medium.pk
)
)
assert resp.status_code == 200
assert resp.data["customer"] == {
@@ -183,7 +188,8 @@ def test_medium_detail(token_client, organizer, event, medium, giftcard, custome
"currency": "EUR",
"testmode": False,
"expires": None,
"conditions": None
"conditions": None,
"owner_ticket": resp.data["linked_orderposition"],
}