mirror of
https://github.com/pretix/pretix.git
synced 2025-12-29 18:02:26 +00:00
Compare commits
3 Commits
product-li
...
release/4.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9054665a73 | ||
|
|
3728a3af72 | ||
|
|
2b6f003206 |
@@ -611,8 +611,12 @@ Order position endpoints
|
||||
Tries to redeem an order position, identified by its internal ID, i.e. checks the attendee in. This endpoint
|
||||
accepts a number of optional requests in the body.
|
||||
|
||||
**Tip:** Instead of an ID, you can also use the ``secret`` field as the lookup parameter.
|
||||
**Tip:** Instead of an ID, you can also use the ``secret`` field as the lookup parameter. In this case, you should
|
||||
always set ``untrusted_input=true`` as a query parameter to avoid security issues.
|
||||
|
||||
:query boolean untrusted_input: If set to true, the lookup parameter is **always** interpreted as a ``secret``, never
|
||||
as an ``id``. This should be always set if you are passing through untrusted, scanned
|
||||
data to avoid guessing of ticket IDs.
|
||||
:<json boolean questions_supported: When this parameter is set to ``true``, handling of questions is supported. If
|
||||
you do not implement question handling in your user interface, you **must**
|
||||
set this to ``false``. In that case, questions will just be ignored. Defaults
|
||||
|
||||
@@ -78,7 +78,7 @@ Synchronization setting any
|
||||
----------------------------------------------- ----------------------------------- ----------------------------------------------------------------------- -----------------------------------------------------------------------
|
||||
Ticket secrets any Random Signed Random Signed
|
||||
=============================================== =================================== =================================== =================================== ================================= =====================================
|
||||
Scenario supported on platforms Android, Desktop, iOS Android, Desktop, iOS Android, Desktop Android, Desktop, iOS Android, Desktop, iOS
|
||||
Scenario supported on platforms Android, Desktop, iOS Android, Desktop, iOS Android, Desktop Android, Desktop Android, Desktop
|
||||
Synchronization speed for large data sets slow slow fast fast
|
||||
Tickets can be scanned yes yes yes no yes
|
||||
Ticket is valid after sale immediately next sync (~5 minutes) immediately never immediately
|
||||
|
||||
@@ -19,4 +19,4 @@
|
||||
# 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/>.
|
||||
#
|
||||
__version__ = "4.12.0.dev0"
|
||||
__version__ = "4.11.1"
|
||||
|
||||
@@ -409,6 +409,11 @@ class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
ignore_unpaid = bool(self.request.data.get('ignore_unpaid', False))
|
||||
nonce = self.request.data.get('nonce')
|
||||
|
||||
untrusted_input = (
|
||||
self.request.GET.get('untrusted_input', '') not in ('0', 'false', 'False', '')
|
||||
or (isinstance(self.request.auth, Device) and 'pretixscan' in (self.request.auth.software_brand or '').lower())
|
||||
)
|
||||
|
||||
if 'datetime' in self.request.data:
|
||||
dt = DateTimeField().to_internal_value(self.request.data.get('datetime'))
|
||||
else:
|
||||
@@ -429,7 +434,7 @@ class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
|
||||
try:
|
||||
queryset = self.get_queryset(ignore_status=True, ignore_products=True)
|
||||
if self.kwargs['pk'].isnumeric():
|
||||
if self.kwargs['pk'].isnumeric() and not untrusted_input:
|
||||
op = queryset.get(Q(pk=self.kwargs['pk']) | Q(secret=self.kwargs['pk']))
|
||||
else:
|
||||
# In application/x-www-form-urlencoded, you can encodes space ' ' with '+' instead of '%20'.
|
||||
|
||||
@@ -475,11 +475,8 @@ def base_placeholders(sender, **kwargs):
|
||||
),
|
||||
SimpleFunctionalMailTextPlaceholder(
|
||||
'event_admission_time', ['event_or_subevent'],
|
||||
lambda event_or_subevent:
|
||||
date_format(event_or_subevent.date_admission.astimezone(event_or_subevent.timezone), 'TIME_FORMAT')
|
||||
if event_or_subevent.date_admission
|
||||
else '',
|
||||
lambda event: date_format(event.date_admission.astimezone(event.timezone), 'TIME_FORMAT') if event.date_admission else '',
|
||||
lambda event_or_subevent: date_format(event_or_subevent.date_admission, 'TIME_FORMAT') if event_or_subevent.date_admission else '',
|
||||
lambda event: date_format(event.date_admission, 'TIME_FORMAT') if event.date_admission else '',
|
||||
),
|
||||
SimpleFunctionalMailTextPlaceholder(
|
||||
'subevent', ['waiting_list_entry', 'event'],
|
||||
|
||||
@@ -23,7 +23,6 @@ from .answers import * # noqa
|
||||
from .dekodi import * # noqa
|
||||
from .events import * # noqa
|
||||
from .invoices import * # noqa
|
||||
from .items import * # noqa
|
||||
from .json import * # noqa
|
||||
from .mail import * # noqa
|
||||
from .orderlist import * # noqa
|
||||
|
||||
@@ -1,218 +0,0 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
#
|
||||
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
|
||||
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
|
||||
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
|
||||
# this file, see <https://pretix.eu/about/en/license>.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# 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 Prefetch
|
||||
from django.dispatch import receiver
|
||||
from django.utils.formats import date_format
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from openpyxl.styles import Alignment
|
||||
from openpyxl.utils import get_column_letter
|
||||
|
||||
from ..channels import get_all_sales_channels
|
||||
from ..exporter import ListExporter
|
||||
from ..models import ItemMetaValue
|
||||
from ..signals import register_data_exporters
|
||||
from ...helpers.safe_openpyxl import SafeCell
|
||||
|
||||
|
||||
def _max(a1, a2):
|
||||
if a1 and a2:
|
||||
return max(a1, a2)
|
||||
return a1 or a2
|
||||
|
||||
|
||||
def _min(a1, a2):
|
||||
if a1 and a2:
|
||||
return min(a1, a2)
|
||||
return a1 or a2
|
||||
|
||||
|
||||
class ItemDataExporter(ListExporter):
|
||||
identifier = 'itemdata'
|
||||
verbose_name = _('Product data')
|
||||
|
||||
def iterate_list(self, form_data):
|
||||
locales = self.event.settings.locales
|
||||
scs = get_all_sales_channels()
|
||||
header = [
|
||||
_("Product ID"),
|
||||
_("Variation ID"),
|
||||
_("Product category"),
|
||||
_("Internal name"),
|
||||
]
|
||||
for l in locales:
|
||||
header.append(
|
||||
_("Item name") + f" ({l})"
|
||||
)
|
||||
for l in locales:
|
||||
header.append(
|
||||
_("Variation") + f" ({l})"
|
||||
)
|
||||
header += [
|
||||
_("Active"),
|
||||
_("Sales channels"),
|
||||
_("Default price"),
|
||||
_("Free price input"),
|
||||
_("Sales tax"),
|
||||
_("Is an admission ticket"),
|
||||
_("Generate tickets"),
|
||||
_("Waiting list"),
|
||||
_("Available from"),
|
||||
_("Available until"),
|
||||
_("This product can only be bought using a voucher."),
|
||||
_("This product will only be shown if a voucher matching the product is redeemed."),
|
||||
_("Buying this product requires approval"),
|
||||
_("Only sell this product as part of a bundle"),
|
||||
_("Allow product to be canceled or changed"),
|
||||
_("Minimum amount per order"),
|
||||
_("Maximum amount per order"),
|
||||
_("Requires special attention"),
|
||||
_("Original price"),
|
||||
_("This product is a gift card"),
|
||||
_("Require a valid membership"),
|
||||
_("Hide without a valid membership"),
|
||||
]
|
||||
props = list(self.event.item_meta_properties.all())
|
||||
for p in props:
|
||||
header.append(p.name)
|
||||
|
||||
if form_data["_format"] == "xlsx":
|
||||
row = []
|
||||
for h in header:
|
||||
c = SafeCell(self.__ws, value=h)
|
||||
c.alignment = Alignment(wrap_text=True, vertical='top')
|
||||
row.append(c)
|
||||
else:
|
||||
row = header
|
||||
|
||||
yield row
|
||||
|
||||
for i in self.event.items.prefetch_related(
|
||||
'variations',
|
||||
Prefetch(
|
||||
'meta_values',
|
||||
ItemMetaValue.objects.select_related('property'),
|
||||
to_attr='meta_values_cached'
|
||||
)
|
||||
).select_related('category', 'tax_rule'):
|
||||
m = i.meta_data
|
||||
vars = list(i.variations.all())
|
||||
|
||||
if vars:
|
||||
for v in vars:
|
||||
row = [
|
||||
i.pk,
|
||||
v.pk,
|
||||
str(i.category) if i.category else "",
|
||||
i.internal_name or "",
|
||||
]
|
||||
for l in locales:
|
||||
row.append(i.name.localize(l))
|
||||
for l in locales:
|
||||
row.append(v.value.localize(l))
|
||||
row += [
|
||||
_("Yes") if i.active and v.active else "",
|
||||
", ".join([str(sn.verbose_name) for s, sn in scs.items() if s in i.sales_channels and s in v.sales_channels]),
|
||||
v.default_price or i.default_price,
|
||||
_("Yes") if i.free_price else "",
|
||||
str(i.tax_rule) if i.tax_rule else "",
|
||||
_("Yes") if i.admission else "",
|
||||
_("Yes") if i.generate_tickets else "",
|
||||
_("Yes") if i.allow_waitinglist else "",
|
||||
date_format(_max(i.available_from, v.available_from).astimezone(self.timezone),
|
||||
"SHORT_DATETIME_FORMAT") if i.available_from or v.available_from else "",
|
||||
date_format(_min(i.available_until, v.available_until).astimezone(self.timezone),
|
||||
"SHORT_DATETIME_FORMAT") if i.available_until or v.available_until else "",
|
||||
_("Yes") if i.require_voucher else "",
|
||||
_("Yes") if i.hide_without_voucher or v.hide_without_voucher else "",
|
||||
_("Yes") if i.require_approval or v.require_approval else "",
|
||||
_("Yes") if i.require_bundling else "",
|
||||
_("Yes") if i.allow_cancel else "",
|
||||
i.min_per_order if i.min_per_order is not None else "",
|
||||
i.max_per_order if i.max_per_order is not None else "",
|
||||
_("Yes") if i.checkin_attention else "",
|
||||
v.original_price or i.original_price or "",
|
||||
_("Yes") if i.issue_giftcard else "",
|
||||
_("Yes") if i.require_membership or v.require_membership else "",
|
||||
_("Yes") if i.require_membership_hidden or v.require_membership_hidden else "",
|
||||
]
|
||||
|
||||
else:
|
||||
row = [
|
||||
i.pk,
|
||||
"",
|
||||
str(i.category) if i.category else "",
|
||||
i.internal_name or "",
|
||||
]
|
||||
for l in locales:
|
||||
row.append(i.name.localize(l))
|
||||
for l in locales:
|
||||
row.append("")
|
||||
row += [
|
||||
_("Yes") if i.active else "",
|
||||
", ".join([str(sn.verbose_name) for s, sn in scs.items() if s in i.sales_channels]),
|
||||
i.default_price,
|
||||
_("Yes") if i.free_price else "",
|
||||
str(i.tax_rule) if i.tax_rule else "",
|
||||
_("Yes") if i.admission else "",
|
||||
_("Yes") if i.generate_tickets else "",
|
||||
_("Yes") if i.allow_waitinglist else "",
|
||||
date_format(i.available_from.astimezone(self.timezone),
|
||||
"SHORT_DATETIME_FORMAT") if i.available_from else "",
|
||||
date_format(i.available_until.astimezone(self.timezone),
|
||||
"SHORT_DATETIME_FORMAT") if i.available_until else "",
|
||||
_("Yes") if i.require_voucher else "",
|
||||
_("Yes") if i.hide_without_voucher else "",
|
||||
_("Yes") if i.require_approval else "",
|
||||
_("Yes") if i.require_bundling else "",
|
||||
_("Yes") if i.allow_cancel else "",
|
||||
i.min_per_order if i.min_per_order is not None else "",
|
||||
i.max_per_order if i.max_per_order is not None else "",
|
||||
_("Yes") if i.checkin_attention else "",
|
||||
i.original_price or "",
|
||||
_("Yes") if i.issue_giftcard else "",
|
||||
_("Yes") if i.require_membership else "",
|
||||
_("Yes") if i.require_membership_hidden else "",
|
||||
]
|
||||
|
||||
row += [
|
||||
m.get(p.name, '') for p in props
|
||||
]
|
||||
yield row
|
||||
|
||||
def get_filename(self):
|
||||
return '{}_products'.format(self.events.first().organizer.slug)
|
||||
|
||||
def prepare_xlsx_sheet(self, ws):
|
||||
self.__ws = ws
|
||||
ws.freeze_panes = 'A1'
|
||||
ws.column_dimensions['C'].width = 25
|
||||
ws.column_dimensions['D'].width = 25
|
||||
for i in range(len(self.event.settings.locales)):
|
||||
ws.column_dimensions[get_column_letter(5 + 2 * i + 0)].width = 25
|
||||
ws.column_dimensions[get_column_letter(5 + 2 * i + 1)].width = 25
|
||||
ws.column_dimensions[get_column_letter(5 + 2 * len(self.event.settings.locales) + 1)].width = 25
|
||||
ws.row_dimensions[1].height = 40
|
||||
|
||||
|
||||
@receiver(register_data_exporters, dispatch_uid="exporter_itemdata")
|
||||
def register_itemdata_exporter(sender, **kwargs):
|
||||
return ItemDataExporter
|
||||
@@ -843,7 +843,7 @@ class Order(LockModel, LoggedModel):
|
||||
if terms:
|
||||
term_last = min(terms)
|
||||
else:
|
||||
return None
|
||||
term_last = None
|
||||
else:
|
||||
term_last = term_last.datetime(self.event).date()
|
||||
term_last = make_aware(datetime.combine(
|
||||
@@ -1588,7 +1588,7 @@ class OrderPayment(models.Model):
|
||||
if status_change:
|
||||
self.order.create_transactions()
|
||||
|
||||
def fail(self, info=None, user=None, auth=None, log_data=None):
|
||||
def fail(self, info=None, user=None, auth=None):
|
||||
"""
|
||||
Marks the order as failed and sets info to ``info``, but only if the order is in ``created`` or ``pending``
|
||||
state. This is equivalent to setting ``state`` to ``OrderPayment.PAYMENT_STATE_FAILED`` and logging a failure,
|
||||
@@ -1616,7 +1616,6 @@ class OrderPayment(models.Model):
|
||||
'local_id': self.local_id,
|
||||
'provider': self.provider,
|
||||
'info': info,
|
||||
'data': log_data,
|
||||
}, user=user, auth=auth)
|
||||
|
||||
def confirm(self, count_waitinglist=True, send_mail=True, force=False, user=None, auth=None, mail_text='',
|
||||
|
||||
@@ -143,8 +143,8 @@ class Sig1TicketSecretGenerator(BaseTicketSecretGenerator):
|
||||
The resulting string is REVERSED, to avoid all secrets of same length beginning with the same 10
|
||||
characters, which would make it impossible to search for secrets manually.
|
||||
"""
|
||||
verbose_name = _('pretix signature scheme 1 (for very large events, changes semantics of offline scanning – '
|
||||
'please refer to documentation or support for details)')
|
||||
verbose_name = _('pretix signature scheme 1 (for very large events, does not work with pretixSCAN on iOS and '
|
||||
'changes semantics of offline scanning – please refer to documentation or support for details)')
|
||||
identifier = 'pretix_sig1'
|
||||
use_revocation_list = True
|
||||
|
||||
|
||||
@@ -48,9 +48,12 @@
|
||||
Make sure to always use the latest version of our scanning apps for these options to work.
|
||||
{% endblocktrans %}
|
||||
<br>
|
||||
{% blocktrans trimmed %}
|
||||
If you make use of these advanced options, we recommend using our Android and Desktop apps.
|
||||
{% endblocktrans %}
|
||||
<strong>
|
||||
{% blocktrans trimmed %}
|
||||
If you make use of these advanced options, we recommend using our Android and Desktop apps.
|
||||
Custom check-in rules do not work offline with our iOS scanning app.
|
||||
{% endblocktrans %}
|
||||
</strong>
|
||||
</div>
|
||||
|
||||
{% bootstrap_field form.allow_multiple_entries layout="control" %}
|
||||
|
||||
@@ -199,7 +199,7 @@ def isu_return(request, *args, **kwargs):
|
||||
if not any(k in request.GET for k in getparams) or not any(k in request.session for k in sessionparams):
|
||||
messages.error(request, _('An error occurred returning from PayPal: request parameters missing. Please try again.'))
|
||||
missing_getparams = set(getparams) - set(request.GET)
|
||||
missing_sessionparams = {p for p in sessionparams if p not in request.session}
|
||||
missing_sessionparams = set(sessionparams) - set(request.session)
|
||||
logger.exception('PayPal2 - Missing params in GET {} and/or Session {}'.format(missing_getparams, missing_sessionparams))
|
||||
return redirect(reverse('control:index'))
|
||||
|
||||
|
||||
@@ -128,7 +128,7 @@
|
||||
</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary pull-right" @click="check(checkResult.position.secret, true, false, false)">
|
||||
<button type="button" class="btn btn-primary pull-right" @click="check(checkResult.position.secret, true, false, false, true)">
|
||||
{{ $root.strings['modal.continue'] }}
|
||||
</button>
|
||||
<button type="button" class="btn btn-default" @click="showUnpaidModal = false">
|
||||
@@ -188,7 +188,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary pull-right" @click="check(checkResult.position.secret, true, true)">
|
||||
<button type="button" class="btn btn-primary pull-right" @click="check(checkResult.position.secret, true, true, true)">
|
||||
{{ $root.strings['modal.continue'] }}
|
||||
</button>
|
||||
<button type="button" class="btn btn-default" @click="showQuestionsModal = false">
|
||||
@@ -296,7 +296,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
selectResult(res) {
|
||||
this.check(res.id, false, false, false)
|
||||
this.check(res.id, false, false, false, false)
|
||||
},
|
||||
answerSetM(qid, opid, checked) {
|
||||
let arr = this.answers[qid] ? this.answers[qid].split(',') : [];
|
||||
@@ -320,7 +320,7 @@ export default {
|
||||
this.showQuestionsModal = false
|
||||
this.answers = {}
|
||||
},
|
||||
check(id, ignoreUnpaid, keepAnswers, fallbackToSearch) {
|
||||
check(id, ignoreUnpaid, keepAnswers, fallbackToSearch, untrusted) {
|
||||
if (!keepAnswers) {
|
||||
this.answers = {}
|
||||
} else if (this.showQuestionsModal) {
|
||||
@@ -339,7 +339,11 @@ export default {
|
||||
this.$refs.input.blur()
|
||||
})
|
||||
|
||||
fetch(this.$root.api.lists + this.checkinlist.id + '/positions/' + encodeURIComponent(id) + '/redeem/?expand=item&expand=subevent&expand=variation', {
|
||||
let url = this.$root.api.lists + this.checkinlist.id + '/positions/' + encodeURIComponent(id) + '/redeem/?expand=item&expand=subevent&expand=variation'
|
||||
if (untrusted) {
|
||||
url += '&untrusted_input=true'
|
||||
}
|
||||
fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-CSRFToken': document.querySelector("input[name=csrfmiddlewaretoken]").value,
|
||||
@@ -439,7 +443,7 @@ export default {
|
||||
startSearch(fallbackToScan) {
|
||||
if (this.query.length >= 32 && fallbackToScan) {
|
||||
// likely a secret, not a search result
|
||||
this.check(this.query, false, false, true)
|
||||
this.check(this.query, false, false, true, true)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
2415
src/pretix/static/npm_dir/package-lock.json
generated
2415
src/pretix/static/npm_dir/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -4,13 +4,13 @@
|
||||
"private": true,
|
||||
"scripts": {},
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.18.6",
|
||||
"@babel/preset-env": "^7.18.6",
|
||||
"@babel/core": "^7.18.2",
|
||||
"@babel/preset-env": "^7.18.2",
|
||||
"@rollup/plugin-babel": "^5.3.1",
|
||||
"@rollup/plugin-node-resolve": "^13.3.0",
|
||||
"vue": "^2.7.0",
|
||||
"rollup": "^2.75.7",
|
||||
"vue": "^2.6.14",
|
||||
"rollup": "^2.75.5",
|
||||
"rollup-plugin-vue": "^5.0.1",
|
||||
"vue-template-compiler": "^2.7.0"
|
||||
"vue-template-compiler": "^2.6.14"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,7 +204,7 @@ setup(
|
||||
'packaging',
|
||||
'paypalrestsdk==1.13.*',
|
||||
'paypal-checkout-serversdk==1.0.*',
|
||||
'PyJWT==2.4.*',
|
||||
'PyJWT==2.0.*',
|
||||
'phonenumberslite==8.12.*',
|
||||
'Pillow==9.1.*',
|
||||
'protobuf==3.19.*',
|
||||
|
||||
@@ -1199,7 +1199,6 @@ def test_redeem_unknown_legacy_device_bug(device, device_client, organizer, clis
|
||||
), {
|
||||
'force': True
|
||||
}, format='json')
|
||||
print(resp.data)
|
||||
assert resp.status_code == 400
|
||||
assert resp.data["status"] == "error"
|
||||
assert resp.data["reason"] == "already_redeemed"
|
||||
@@ -1219,3 +1218,43 @@ def test_redeem_unknown_legacy_device_bug(device, device_client, organizer, clis
|
||||
assert resp.data["reason"] == "invalid"
|
||||
with scopes_disabled():
|
||||
assert not Checkin.objects.last()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_redeem_by_id_not_allowed_if_pretixscan(device, device_client, organizer, clist, event, order):
|
||||
with scopes_disabled():
|
||||
p = order.positions.first()
|
||||
device.software_brand = "pretixSCAN"
|
||||
device.software_version = "1.14.2"
|
||||
device.save()
|
||||
resp = device_client.post('/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/{}/redeem/'.format(
|
||||
organizer.slug, event.slug, clist.pk, p.pk
|
||||
), {
|
||||
'force': True
|
||||
}, format='json')
|
||||
print(resp.data)
|
||||
assert resp.status_code == 404
|
||||
resp = device_client.post('/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/{}/redeem/'.format(
|
||||
organizer.slug, event.slug, clist.pk, p.secret
|
||||
), {
|
||||
'force': True
|
||||
}, format='json')
|
||||
assert resp.status_code == 201
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_redeem_by_id_not_allowed_if_untrusted(device, device_client, organizer, clist, event, order):
|
||||
with scopes_disabled():
|
||||
p = order.positions.first()
|
||||
resp = device_client.post('/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/{}/redeem/?untrusted_input=true'.format(
|
||||
organizer.slug, event.slug, clist.pk, p.pk
|
||||
), {
|
||||
'force': True
|
||||
}, format='json')
|
||||
assert resp.status_code == 404
|
||||
resp = device_client.post('/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/{}/redeem/?untrusted_input=true'.format(
|
||||
organizer.slug, event.slug, clist.pk, p.secret
|
||||
), {
|
||||
'force': True
|
||||
}, format='json')
|
||||
assert resp.status_code == 201
|
||||
|
||||
Reference in New Issue
Block a user