Add last_modified property to orders (#907)

This commit is contained in:
Raphael Michel
2018-05-14 11:09:26 +02:00
committed by GitHub
parent 01a702c529
commit 1c2acbb57f
8 changed files with 99 additions and 19 deletions
+12 -1
View File
@@ -49,7 +49,7 @@ invoice_address object Invoice address
└ vat_id_validated string ``True``, if the VAT ID has been validated against the
EU VAT service and validation was successful. This only
happens in rare cases.
position list of objects List of order positions (see below)
positions list of objects List of order positions (see below)
fees list of objects List of fees included in the order total (i.e.
payment fees)
├ fee_type string Type of fee (currently ``payment``, ``passbook``,
@@ -68,6 +68,7 @@ downloads list of objects List of ticket
download options.
├ output string Ticket output provider (e.g. ``pdf``, ``passbook``)
└ url string Download URL
last_modified datetime Last modification of this object
===================================== ========================== =======================================================
@@ -96,6 +97,10 @@ downloads list of objects List of ticket
The attributes ``order.payment_fee``, ``order.payment_fee_tax_rate``, ``order.payment_fee_tax_value`` and
``order.payment_fee_tax_rule`` have finally been removed.
.. versionchanged:: 1.16
The attributes ``order.last_modified`` as well as the corresponding filters to the resource have been added.
.. _order-position-resource:
Order position resource
@@ -174,6 +179,7 @@ Order endpoints
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
X-Page-Generated: 2017-12-01T10:00:00Z
{
"count": 1,
@@ -188,6 +194,7 @@ Order endpoints
"locale": "en",
"datetime": "2017-12-01T10:00:00Z",
"expires": "2017-12-10T10:00:00Z",
"last_modified": "2017-12-01T10:00:00Z",
"payment_date": "2017-12-05",
"payment_provider": "banktransfer",
"fees": [],
@@ -264,8 +271,11 @@ Order endpoints
:query string status: Only return orders in the given order status (see above)
:query string email: Only return orders created with the given email address
:query string locale: Only return orders with the given customer locale
:query datetime modified_since: Only return orders that have changed since the given date
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the event to fetch
:resheader X-Page-Generated: The server time at the beginning of the operation. If you're using this API to fetch
differences, this is the value you want to use as ``modified_since`` in your next call.
:statuscode 200: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource.
@@ -298,6 +308,7 @@ Order endpoints
"locale": "en",
"datetime": "2017-12-01T10:00:00Z",
"expires": "2017-12-10T10:00:00Z",
"last_modified": "2017-12-01T10:00:00Z",
"payment_date": "2017-12-05",
"payment_provider": "banktransfer",
"fees": [],
+1 -15
View File
@@ -1,5 +1,3 @@
from decimal import Decimal
from rest_framework import serializers
from rest_framework.reverse import reverse
@@ -123,18 +121,6 @@ class OrderFeeSerializer(I18nAwareModelSerializer):
fields = ('fee_type', 'value', 'description', 'internal_type', 'tax_rate', 'tax_value', 'tax_rule')
class PaymentFeeLegacyField(serializers.Field):
def __init__(self, *args, **kwargs):
self.attr = kwargs.pop('attribute')
super().__init__(*args, **kwargs)
def to_representation(self, instance: Order):
return str(
sum([getattr(f, self.attr) for f in instance.fees.all() if f.fee_type == OrderFee.FEE_TYPE_PAYMENT],
Decimal('0.00'))
)
class OrderSerializer(I18nAwareModelSerializer):
invoice_address = InvoiceAddressSerializer()
positions = OrderPositionSerializer(many=True)
@@ -145,7 +131,7 @@ class OrderSerializer(I18nAwareModelSerializer):
model = Order
fields = ('code', 'status', 'secret', 'email', 'locale', 'datetime', 'expires', 'payment_date',
'payment_provider', 'fees', 'total', 'comment', 'invoice_address', 'positions', 'downloads',
'checkin_attention')
'checkin_attention', 'last_modified')
class InlineInvoiceLineSerializer(I18nAwareModelSerializer):
+14 -1
View File
@@ -5,7 +5,7 @@ import pytz
from django.db.models import Q
from django.db.models.functions import Concat
from django.http import FileResponse
from django.utils.timezone import make_aware
from django.utils.timezone import make_aware, now
from django_filters.rest_framework import DjangoFilterBackend, FilterSet
from rest_framework import serializers, status, viewsets
from rest_framework.decorators import detail_route
@@ -38,6 +38,7 @@ class OrderFilter(FilterSet):
email = django_filters.CharFilter(name='email', lookup_expr='iexact')
code = django_filters.CharFilter(name='code', lookup_expr='iexact')
status = django_filters.CharFilter(name='status', lookup_expr='iexact')
modified_since = django_filters.IsoDateTimeFilter(name='last_modified', lookup_expr='gte')
class Meta:
model = Order
@@ -71,6 +72,18 @@ class OrderViewSet(viewsets.ReadOnlyModelViewSet):
return prov
raise NotFound('Unknown output provider.')
def list(self, request, **kwargs):
date = serializers.DateTimeField().to_representation(now())
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data, headers={'X-Page-Generated': date})
@detail_route(url_name='download', url_path='download/(?P<output>[^/]+)')
def download(self, request, output, **kwargs):
provider = self._get_output_provider(output)
@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.11 on 2018-05-13 16:41
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('pretixbase', '0090_auto_20180509_0917'),
]
operations = [
migrations.AddField(
model_name='order',
name='last_modified',
field=models.DateTimeField(auto_now=True, db_index=True),
),
]
+8
View File
@@ -168,3 +168,11 @@ class Checkin(models.Model):
return "<Checkin: pos {} on list '{}' at {}>".format(
self.position, self.list, self.datetime
)
def save(self, **kwargs):
self.position.order.touch()
super().save(**kwargs)
def delete(self, **kwargs):
self.position.order.touch()
super().delete(**kwargs)
+28 -2
View File
@@ -180,6 +180,9 @@ class Order(LoggedModel):
verbose_name=_("Meta information"),
null=True, blank=True
)
last_modified = models.DateTimeField(
auto_now=True, db_index=True
)
class Meta:
verbose_name = _("Order")
@@ -208,12 +211,17 @@ class Order(LoggedModel):
def changable(self):
return self.status in (Order.STATUS_PAID, Order.STATUS_PENDING)
def save(self, *args, **kwargs):
def save(self, **kwargs):
if 'update_fields' in kwargs and 'last_modified' not in kwargs['update_fields']:
kwargs['update_fields'] = list(kwargs['update_fields']) + ['last_modified']
if not self.code:
self.assign_code()
if not self.datetime:
self.datetime = now()
super().save(*args, **kwargs)
super().save(**kwargs)
def touch(self):
self.save(update_fields=['last_modified'])
@cached_property
def tax_total(self):
@@ -547,8 +555,15 @@ class QuestionAnswer(models.Model):
def save(self, *args, **kwargs):
if self.orderposition and self.cartposition:
raise ValueError('QuestionAnswer cannot be linked to an order and a cart position at the same time.')
if self.orderposition:
self.orderposition.order.touch()
super().save(*args, **kwargs)
def delete(self, **kwargs):
if self.orderposition:
self.orderposition.order.touch()
super().delete(**kwargs)
class AbstractPosition(models.Model):
"""
@@ -751,8 +766,13 @@ class OrderFee(models.Model):
def save(self, *args, **kwargs):
if self.tax_rate is None:
self._calculate_tax()
self.order.touch()
return super().save(*args, **kwargs)
def delete(self, **kwargs):
self.order.touch()
super().delete(**kwargs)
class OrderPosition(AbstractPosition):
"""
@@ -861,6 +881,7 @@ class OrderPosition(AbstractPosition):
def save(self, *args, **kwargs):
if self.tax_rate is None:
self._calculate_tax()
self.order.touch()
if self.pk is None:
while OrderPosition.objects.filter(secret=self.secret).exists():
self.secret = generate_position_secret()
@@ -945,6 +966,11 @@ class InvoiceAddress(models.Model):
blank=True
)
def save(self, **kwargs):
if self.order:
self.order.touch()
super().save(**kwargs)
def cachedticket_name(instance, filename: str) -> str:
secret = get_random_string(length=16, allowed_chars=string.ascii_letters + string.digits)
+1
View File
@@ -1143,6 +1143,7 @@ class OrderChangeManager:
self._recalculate_total_and_payment_fee()
self._reissue_invoice()
self._clear_tickets_cache()
self.order.touch()
self._check_paid_to_free()
if self.notify:
+15
View File
@@ -144,6 +144,7 @@ def test_order_list(token_client, organizer, event, order, item, taxrule, questi
res["positions"][0]["id"] = order.positions.first().pk
res["positions"][0]["item"] = item.pk
res["positions"][0]["answers"][0]["question"] = question.pk
res["last_modified"] = order.last_modified.isoformat().replace('+00:00', 'Z')
res["fees"][0]["tax_rule"] = taxrule.pk
resp = token_client.get('/api/v1/organizers/{}/events/{}/orders/'.format(organizer.slug, event.slug))
@@ -172,6 +173,19 @@ def test_order_list(token_client, organizer, event, order, item, taxrule, questi
resp = token_client.get('/api/v1/organizers/{}/events/{}/orders/?locale=de'.format(organizer.slug, event.slug))
assert [] == resp.data['results']
resp = token_client.get('/api/v1/organizers/{}/events/{}/orders/?modified_since={}'.format(
organizer.slug, event.slug, (order.last_modified - datetime.timedelta(hours=1)).isoformat().replace('+00:00', 'Z')
))
assert [res] == resp.data['results']
resp = token_client.get('/api/v1/organizers/{}/events/{}/orders/?modified_since={}'.format(
organizer.slug, event.slug, order.last_modified.isoformat().replace('+00:00', 'Z')
))
assert [res] == resp.data['results']
resp = token_client.get('/api/v1/organizers/{}/events/{}/orders/?modified_since={}'.format(
organizer.slug, event.slug, (order.last_modified + datetime.timedelta(hours=1)).isoformat().replace('+00:00', 'Z')
))
assert [] == resp.data['results']
@pytest.mark.django_db
def test_order_detail(token_client, organizer, event, order, item, taxrule, question):
@@ -180,6 +194,7 @@ def test_order_detail(token_client, organizer, event, order, item, taxrule, ques
res["positions"][0]["item"] = item.pk
res["fees"][0]["tax_rule"] = taxrule.pk
res["positions"][0]["answers"][0]["question"] = question.pk
res["last_modified"] = order.last_modified.isoformat().replace('+00:00', 'Z')
resp = token_client.get('/api/v1/organizers/{}/events/{}/orders/{}/'.format(organizer.slug, event.slug,
order.code))
assert resp.status_code == 200