Added logging for all basic operations

This commit is contained in:
Raphael Michel
2015-12-12 22:35:25 +01:00
parent 83b5fa2fa6
commit 58b85819bc
18 changed files with 316 additions and 61 deletions

View File

@@ -50,7 +50,7 @@ class SettingsForm(forms.Form):
def __init__(self, *args, **kwargs):
self.obj = kwargs.pop('obj')
kwargs['initial'] = self.obj.settings
kwargs['initial'] = self.obj.settings.freeze()
super().__init__(*args, **kwargs)
def save(self):

View File

@@ -3,7 +3,8 @@ import json
from django import forms
from django.conf import settings
from django.db.models import SubfieldBase, TextField
from django.core.serializers.json import DjangoJSONEncoder
from django.db.models import Model, QuerySet, SubfieldBase, TextField
from django.utils import translation
from django.utils.safestring import mark_safe
from typing import Dict, List
@@ -233,3 +234,15 @@ class I18nTextField(I18nFieldMixin, TextField, metaclass=SubfieldBase):
Like I18nCharField, but for TextFields.
"""
widget = I18nTextarea
class I18nJSONEncoder(DjangoJSONEncoder):
def default(self, obj):
if isinstance(obj, LazyI18nString):
return obj.data
elif isinstance(obj, QuerySet):
return list(obj)
elif isinstance(obj, Model):
return {'type': obj.__class__.__name__, 'id': obj.id}
else:
return super().default(obj)

View File

@@ -5,6 +5,8 @@ from django.contrib.auth.models import (
from django.db import models
from django.utils.translation import ugettext_lazy as _
from .base import LoggingMixin
class UserManager(BaseUserManager):
"""
@@ -30,7 +32,7 @@ class UserManager(BaseUserManager):
return user
class User(AbstractBaseUser, PermissionsMixin):
class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
"""
This is the user model used by pretix for authentication.

View File

@@ -1,3 +1,4 @@
import json
import uuid
from django.contrib.contenttypes.fields import GenericRelation
@@ -5,6 +6,8 @@ from django.db import models
from django.db.models.signals import post_delete
from django.dispatch import receiver
from pretix.base.i18n import I18nJSONEncoder
def cachedfile_name(instance, filename: str) -> str:
return 'cachedfiles/%012d.%s' % (instance.id, filename.split('.')[-1])
@@ -29,10 +32,18 @@ def cached_file_delete(sender, instance, **kwargs):
instance.file.delete(False)
class LoggedModel(models.Model):
class LoggingMixin:
logentries = GenericRelation('LogEntry')
def log_action(self, user, action, data):
def log_action(self, action, data=None, user=None):
"""
Create a LogEntry object that is related to this object.
See the LogEntry documentation for details.
:param action: The namespaced action code
:param data: Any JSON-serializable object
:param user: The user performing the action (optional)
"""
from .log import LogEntry
from .event import Event
@@ -41,7 +52,13 @@ class LoggedModel(models.Model):
event = self
elif hasattr(self, 'event'):
event = self.event
LogEntry.objects.create(content_object=self, user=user, action=action, data=data, event=event)
l = LogEntry(content_object=self, user=user, action_type=action, event=event)
if data:
l.data = json.dumps(data, cls=I18nJSONEncoder)
l.save()
class LoggedModel(models.Model, LoggingMixin):
class Meta:
abstract = True

View File

@@ -2,7 +2,7 @@ import random
import string
from datetime import datetime
from django.db import models
from django.db import models, transaction
from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _
from typing import List, Union
@@ -177,14 +177,6 @@ class Order(LoggedModel):
return True
return False # nothing there to modify
def mark_refunded(self):
"""
Mark this order as refunded. This sets the payment status and returns the order object.
"""
self.status = Order.STATUS_REFUNDED
self.save()
return self
def _can_be_paid(self) -> Union[bool, str]:
error_messages = {
'late': _("The payment is too late to be accepted."),

View File

@@ -359,6 +359,7 @@ class BasePaymentProvider:
:param order: The order object
"""
self.order.log_action('pretix.base.order.refunded')
return '<div class="alert alert-warning">%s</div>' % _('The money can not be automatically refunded, '
'please transfer the money back manually.')
@@ -379,7 +380,9 @@ class BasePaymentProvider:
:param request: The HTTP request
:param order: The order object
"""
order.mark_refunded()
from pretix.base.services.orders import mark_order_refunded
mark_order_refunded(order, user=request.user)
messages.success(request, _('The order has been marked as refunded. Please transfer the money '
'back to the buyer manually.'))
@@ -440,7 +443,9 @@ class FreeOrderProvider(BasePaymentProvider):
:param request: The HTTP request
:param order: The order object
"""
order.mark_refunded()
from pretix.base.services.orders import mark_order_refunded
mark_order_refunded(order, user=request.user)
messages.success(request, _('The order has been marked as refunded.'))
def is_allowed(self, request: HttpRequest) -> bool:

View File

@@ -7,7 +7,7 @@ from django.utils.translation import ugettext_lazy as _
from typing import List
from pretix.base.models import (
CartPosition, Event, EventLock, Order, OrderPosition, Quota,
CartPosition, Event, EventLock, Order, OrderPosition, Quota, User,
)
from pretix.base.payment import BasePaymentProvider
from pretix.base.services.mail import mail
@@ -31,7 +31,7 @@ error_messages = {
def mark_order_paid(order: Order, provider: str=None, info: str=None, date: datetime=None, manual: bool=None,
force: bool=False) -> Order:
force: bool=False, user: User=None) -> Order:
"""
Marks an order as paid. This sets the payment provider, info and date and returns
the order object.
@@ -46,6 +46,7 @@ def mark_order_paid(order: Order, provider: str=None, info: str=None, date: date
:param force: Whether this payment should be marked as paid even if no remaining
quota is available (default: ``False``).
:type force: boolean
:param user: The user that performed the change
:raises Quota.QuotaExceededException: if the quota is exceeded and ``force`` is ``False``
"""
with order.event.lock():
@@ -59,6 +60,13 @@ def mark_order_paid(order: Order, provider: str=None, info: str=None, date: date
order.payment_manual = manual
order.status = Order.STATUS_PAID
order.save()
order.log_action('pretix.event.order.paid', {
'provider': provider,
'info': info,
'date': date,
'manual': manual,
'force': force
}, user=user)
order_paid.send(order.event, order=order)
mail(
@@ -78,6 +86,32 @@ def mark_order_paid(order: Order, provider: str=None, info: str=None, date: date
return order
@transaction.atomic
def mark_order_refunded(order: Order, user: User=None):
"""
Mark this order as refunded. This sets the payment status and returns the order object.
:param order: The order to change
:param user: The user that performed the change
"""
order.status = Order.STATUS_REFUNDED
order.save()
order.log_action('pretix.event.order.refunded', user=user)
return order
@transaction.atomic
def cancel_order(order: Order, user: User=None):
"""
Mark this order as canceled
:param order: The order to change
:param user: The user that performed the change
"""
order.status = Order.STATUS_CANCELLED
order.save()
order.log_action('pretix.event.order.cancelled', user=user)
return order
class OrderError(Exception):
pass
@@ -154,6 +188,7 @@ def _create_order(event: Event, email: str, positions: List[CartPosition], dt: d
payment_provider=payment_provider.identifier
)
OrderPosition.transform_cart_positions(positions, order)
order.log_action('pretix.event.order.placed')
order_placed.send(event, order=order)
return order

View File

@@ -121,7 +121,20 @@ class SettingsProxy:
def _flush(self) -> None:
self._cached_obj = None
def freeze(self):
settings = {}
for key, v in DEFAULTS.items():
settings[key] = self._unserialize(v['default'], v['type'])
if self._parent:
settings.update(self._parent.settings.freeze())
for key, value in self._cache().items():
settings[key] = self.get(key)
return settings
def _unserialize(self, value: str, as_type: type) -> Any:
if as_type is None and value is not None and value.startswith('file://'):
as_type = File
if as_type is not None and isinstance(value, as_type):
return value
elif value is None:
@@ -186,15 +199,14 @@ class SettingsProxy:
if value is None and default is not None:
value = default
if as_type is None and value is not None and value.startswith('file://'):
as_type = File
return self._unserialize(value, as_type)
def __getitem__(self, key: str) -> Any:
return self.get(key)
def __getattr__(self, key: str) -> Any:
if key.startswith('_'):
return super().__getattr__(key)
return self.get(key)
def __setattr__(self, key: str, value: Any) -> None:

View File

@@ -62,6 +62,7 @@ def register(request):
timezone=request.timezone if hasattr(request, 'timezone') else settings.TIME_ZONE
)
user = authenticate(email=user.email, password=form.cleaned_data['password'])
user.log_action('pretix.control.auth.user.created', user=user)
auth_login(request, user)
return redirect('control:index')
else:
@@ -90,6 +91,7 @@ class Forgot(TemplateView):
},
None, locale=user.locale
)
user.log_action('pretix.control.auth.user.forgot_password.mail_sent')
messages.success(request, _('We sent you an e-mail containing further instructions.'))
return redirect('control:auth.forgot')
else:
@@ -141,6 +143,7 @@ class Recover(TemplateView):
user.set_password(self.form.cleaned_data['password'])
user.save()
messages.success(request, _('You can now login using your new password.'))
user.log_action('pretix.control.auth.user.forgot_password.recovered')
return redirect('control:auth.login')
else:
return self.get(request, *args, **kwargs)

View File

@@ -3,6 +3,7 @@ from collections import OrderedDict
from django import forms
from django.contrib import messages
from django.core.urlresolvers import reverse
from django.db import transaction
from django.db.models import Sum
from django.forms import modelformset_factory
from django.shortcuts import redirect, render
@@ -53,8 +54,17 @@ class EventUpdate(EventPermissionRequiredMixin, UpdateView):
context['sform'] = self.sform
return context
@transaction.atomic()
def form_valid(self, form):
self.sform.save()
if self.sform.has_changed():
self.request.event.log_action('pretix.event.settings', user=self.request.user, data={
k: self.request.event.settings.get(k) for k in self.sform.changed_data
})
if form.has_changed():
self.request.event.log_action('pretix.event.changed', user=self.request.user, data={
k: getattr(self.request.event, k) for k in form.changed_data
})
messages.success(self.request, _('Your changes have been saved.'))
return super().form_valid(form)
@@ -97,15 +107,20 @@ class EventPlugins(EventPermissionRequiredMixin, TemplateView, SingleObjectMixin
def post(self, request, *args, **kwargs):
self.object = self.get_object()
plugins_active = self.object.get_plugins()
for key, value in request.POST.items():
if key.startswith("plugin:"):
module = key.split(":")[1]
if value == "enable":
plugins_active.append(module)
else:
plugins_active.remove(module)
self.object.plugins = ",".join(plugins_active)
self.object.save()
with transaction.atomic():
for key, value in request.POST.items():
if key.startswith("plugin:"):
module = key.split(":")[1]
if value == "enable":
self.request.event.log_action('pretix.event.plugins.enabled', user=self.request.user,
data={'plugin': module})
plugins_active.append(module)
else:
self.request.event.log_action('pretix.event.plugins.disabled', user=self.request.user,
data={'plugin': module})
plugins_active.remove(module)
self.object.plugins = ",".join(plugins_active)
self.object.save()
messages.success(self.request, _('Your changes have been saved.'))
return redirect(self.get_success_url())
@@ -159,11 +174,18 @@ class PaymentSettings(EventPermissionRequiredMixin, TemplateView, SingleObjectMi
context['providers'] = self.provider_forms
return self.render_to_response(context)
@transaction.atomic()
def post(self, request, *args, **kwargs):
self.object = self.get_object()
success = True
for provider in self.provider_forms:
if provider.form.is_valid():
if provider.form.has_changed():
self.request.event.log_action(
'pretix.event.payment.provider.' + provider.identifier, user=self.request.user, data={
k: provider.form.cleaned_data.get(k) for k in provider.form.changed_data
}
)
provider.form.save()
else:
success = False
@@ -207,16 +229,29 @@ class TicketSettings(EventPermissionRequiredMixin, FormView):
form.prepare_fields()
return form
@transaction.atomic()
def post(self, request, *args, **kwargs):
success = True
for provider in self.provider_forms:
if provider.form.is_valid():
provider.form.save()
if provider.form.has_changed():
self.request.event.log_action(
'pretix.event.tickets.provider.' + provider.identifier, user=self.request.user, data={
k: provider.form.cleaned_data.get(k) for k in provider.form.changed_data
}
)
else:
success = False
form = self.get_form(self.get_form_class())
if success and form.is_valid():
form.save()
if form.has_changed():
self.request.event.log_action(
'pretix.event.tickets.settings', user=self.request.user, data={
k: form.cleaned_data.get(k) for k in form.changed_data
}
)
messages.success(self.request, _('Your changes have been saved.'))
return redirect(self.get_success_url())
else:
@@ -310,6 +345,7 @@ class EventPermissions(EventPermissionRequiredMixin, TemplateView):
ctx['add_form'] = self.add_form
return ctx
@transaction.atomic()
def post(self, *args, **kwargs):
if self.formset.is_valid() and self.add_form.is_valid():
if self.add_form.has_changed():
@@ -327,7 +363,22 @@ class EventPermissions(EventPermissionRequiredMixin, TemplateView):
messages.error(self.request, _('This user already has permissions for this event.'))
return self.get(*args, **kwargs)
self.add_form.save()
logdata = {
k: v for k, v in self.add_form.cleaned_data.items()
}
logdata['user'] = self.add_form.instance.user_id
self.request.event.log_action(
'pretix.event.permissions.added', user=self.request.user, data=logdata
)
for form in self.formset.forms:
if form.has_changed():
changedata = {
k: form.cleaned_data.get(k) for k in form.changed_data
}
changedata['user'] = form.instance.user_id
self.request.event.log_action(
'pretix.event.permissions.changed', user=self.request.user, data=changedata
)
if form.instance.user_id == self.request.user.pk:
if not form.cleaned_data['can_change_permissions'] or form in self.formset.deleted_forms:
messages.error(self.request, _('You cannot remove your own permission to view this page.'))

View File

@@ -103,12 +103,14 @@ class CategoryDelete(EventPermissionRequiredMixin, DeleteView):
except ItemCategory.DoesNotExist:
raise Http404(_("The requested product category does not exist."))
@transaction.atomic()
def delete(self, request, *args, **kwargs):
self.object = self.get_object()
for item in self.object.items.all():
item.category = None
item.save()
success_url = self.get_success_url()
self.object.log_action('pretix.event.category.deleted', user=self.request.user)
self.object.delete()
messages.success(request, _('The selected category has been deleted.'))
return HttpResponseRedirect(success_url)
@@ -136,8 +138,15 @@ class CategoryUpdate(EventPermissionRequiredMixin, UpdateView):
except ItemCategory.DoesNotExist:
raise Http404(_("The requested product category does not exist."))
@transaction.atomic()
def form_valid(self, form):
messages.success(self.request, _('Your changes have been saved.'))
if form.has_changed():
self.object.log_action(
'pretix.event.category.changed', user=self.request.user, data={
k: form.cleaned_data.get(k) for k in form.changed_data
}
)
return super().form_valid(form)
def get_success_url(self) -> str:
@@ -160,10 +169,13 @@ class CategoryCreate(EventPermissionRequiredMixin, CreateView):
'event': self.request.event.slug,
})
@transaction.atomic()
def form_valid(self, form):
form.instance.event = self.request.event
messages.success(self.request, _('The new category has been created.'))
return super().form_valid(form)
ret = super().form_valid(form)
form.instance.log_action('pretix.event.category.added', data=dict(form.cleaned_data), user=self.request.user)
return ret
class CategoryList(ListView):
@@ -248,9 +260,11 @@ class QuestionDelete(EventPermissionRequiredMixin, DeleteView):
context['dependent'] = list(self.get_object().items.all())
return context
@transaction.atomic()
def delete(self, request, *args, **kwargs):
self.object = self.get_object()
success_url = self.get_success_url()
self.object.log_action(action='pretix.event.question.deleted', user=request.user)
self.object.delete()
messages.success(request, _('The selected question has been deleted.'))
return HttpResponseRedirect(success_url)
@@ -277,7 +291,14 @@ class QuestionUpdate(EventPermissionRequiredMixin, UpdateView):
except Question.DoesNotExist:
raise Http404(_("The requested question does not exist."))
@transaction.atomic()
def form_valid(self, form):
if form.has_changed():
self.object.log_action(
'pretix.event.question.changed', user=self.request.user, data={
k: form.cleaned_data.get(k) for k in form.changed_data
}
)
messages.success(self.request, _('Your changes have been saved.'))
return super().form_valid(form)
@@ -306,9 +327,12 @@ class QuestionCreate(EventPermissionRequiredMixin, CreateView):
'event': self.request.event.slug,
})
@transaction.atomic()
def form_valid(self, form):
messages.success(self.request, _('The new question has been created.'))
return super().form_valid(form)
ret = super().form_valid(form)
form.instance.log_action('pretix.event.question.added', user=self.request.user, data=dict(form.cleaned_data))
return ret
class QuotaList(ListView):
@@ -378,10 +402,13 @@ class QuotaCreate(EventPermissionRequiredMixin, QuotaEditorMixin, CreateView):
'event': self.request.event.slug,
})
@transaction.atomic()
def form_valid(self, form):
form.instance.event = self.request.event
messages.success(self.request, _('The new quota has been created.'))
return super().form_valid(form)
ret = super().form_valid(form)
form.instance.log_action('pretix.event.quota.added', user=self.request.user, data=dict(form.cleaned_data))
return ret
class QuotaUpdate(EventPermissionRequiredMixin, QuotaEditorMixin, UpdateView):
@@ -399,8 +426,15 @@ class QuotaUpdate(EventPermissionRequiredMixin, QuotaEditorMixin, UpdateView):
except Quota.DoesNotExist:
raise Http404(_("The requested quota does not exist."))
@transaction.atomic()
def form_valid(self, form):
messages.success(self.request, _('Your changes have been saved.'))
if form.has_changed():
self.object.log_action(
'pretix.event.quota.changed', user=self.request.user, data={
k: form.cleaned_data.get(k) for k in form.changed_data
}
)
return super().form_valid(form)
def get_success_url(self) -> str:
@@ -429,9 +463,11 @@ class QuotaDelete(EventPermissionRequiredMixin, DeleteView):
context['dependent'] = list(self.get_object().items.all())
return context
@transaction.atomic()
def delete(self, request, *args, **kwargs):
self.object = self.get_object()
success_url = self.get_success_url()
self.object.log_action(action='pretix.event.quota.deleted', user=request.user)
self.object.delete()
messages.success(self.request, _('The selected quota has been deleted.'))
return HttpResponseRedirect(success_url)
@@ -471,9 +507,12 @@ class ItemCreate(EventPermissionRequiredMixin, CreateView):
'item': self.object.id,
})
@transaction.atomic()
def form_valid(self, form):
messages.success(self.request, _('Your changes have been saved.'))
return super().form_valid(form)
ret = super().form_valid(form)
form.instance.log_action('pretix.event.item.added', user=self.request.user, data=dict(form.cleaned_data))
return ret
def get_form_kwargs(self):
"""
@@ -497,8 +536,15 @@ class ItemUpdateGeneral(ItemDetailMixin, EventPermissionRequiredMixin, UpdateVie
'item': self.get_object().id,
})
@transaction.atomic()
def form_valid(self, form):
messages.success(self.request, _('Your changes have been saved.'))
if form.has_changed():
self.object.log_action(
'pretix.event.item.changed', user=self.request.user, data={
k: form.cleaned_data.get(k) for k in form.changed_data
}
)
return super().form_valid(form)
@@ -543,13 +589,31 @@ class ItemProperties(ItemDetailMixin, EventPermissionRequiredMixin, TemplateView
for f in formset:
f.instance.event = self.request.event
f.instance.item = self.get_object()
is_created = not f.instance.pk
f.instance.save()
print(f.instance)
if f.has_changed() and not is_created:
change_data = {
k: f.cleaned_data.get(k) for k in f.changed_data
}
change_data['id'] = f.instance.pk
f.instance.item.log_action(
'pretix.event.item.property.changed', user=self.request.user, data=change_data
)
elif is_created:
change_data = dict(f.cleaned_data)
change_data['id'] = f.instance.pk
f.instance.item.log_action(
'pretix.event.item.property.added', user=self.request.user, data=change_data
)
for n in f.nested:
print(n.deleted_forms, n.ordered_forms, n.extra_forms)
for fn in n.deleted_forms:
f.instance.item.log_action(
'pretix.event.item.property.value.deleted', user=self.request.user, data={
'id': fn.instance.pk
}
)
fn.instance.delete()
fn.instance.pk = None
@@ -557,8 +621,24 @@ class ItemProperties(ItemDetailMixin, EventPermissionRequiredMixin, TemplateView
fn.instance.position = i
fn.instance.prop = f.instance
fn.save()
if f.has_changed():
change_data = {k: f.cleaned_data.get(k) for k in f.changed_data}
change_data['id'] = f.instance.pk
f.instance.item.log_action(
'pretix.event.item.property.value.changed', user=self.request.user, data=change_data
)
n.save_new_objects()
for form in n.extra_forms:
if not form.has_changed():
continue
if n.can_delete and n._should_delete_form(form):
continue
change_data = dict(f.cleaned_data)
n.save_new(form)
change_data['id'] = form.instance.pk
f.instance.item.log_action(
'pretix.event.item.property.value.added', user=self.request.user, data=change_data
)
messages.success(self.request, _('Your changes have been saved.'))
return redirect(self.get_success_url())
@@ -700,6 +780,13 @@ class ItemVariations(ItemDetailMixin, EventPermissionRequiredMixin, TemplateView
for form in self.forms_flat:
if form.is_valid() and form.has_changed():
form.save()
change_data = {
k: form.cleaned_data.get(k) for k in form.changed_data
}
change_data['id'] = form.instance.pk
self.object.log_action(
'pretix.event.item.variation.changed', user=self.request.user, data=change_data
)
if hasattr(form.instance, 'creation') and form.instance.creation:
# We need this special 'creation' field set to true in get_form
# for newly created items as cleanerversion does already set the
@@ -758,9 +845,11 @@ class ItemDelete(EventPermissionRequiredMixin, DeleteView):
raise Http404(_("The requested product does not exist."))
return self.object
@transaction.atomic
def delete(self, request, *args, **kwargs):
success_url = self.get_success_url()
if self.is_allowed():
self.get_object().log_action('pretix.event.item.deleted', user=self.request.user)
self.get_object().delete()
messages.success(request, _('The selected product has been deleted.'))
return HttpResponseRedirect(success_url)
@@ -768,6 +857,9 @@ class ItemDelete(EventPermissionRequiredMixin, DeleteView):
o = self.get_object()
o.active = False
o.save()
o.log_action('pretix.event.item.changed', user=self.request.user, data={
'active': False
})
messages.success(request, _('The selected product has been deactivated.'))
return HttpResponseRedirect(success_url)

View File

@@ -17,7 +17,7 @@ from pretix.base.models import (
)
from pretix.base.services import tickets
from pretix.base.services.export import export
from pretix.base.services.orders import mark_order_paid
from pretix.base.services.orders import cancel_order, mark_order_paid
from pretix.base.services.stats import order_overview
from pretix.base.signals import (
register_data_exporters, register_payment_providers,
@@ -136,8 +136,8 @@ class OrderDetail(OrderView):
def keyfunc(pos):
if (pos.item.admission and self.request.event.settings.attendee_names_asked) \
or pos.item.questions.all():
return pos.id, "", "", ""
return "", pos.item_id, pos.variation_id, pos.price
return pos.id, 0, 0, 0
return 0, pos.item_id, pos.variation_id, pos.price
positions = []
for k, g in groupby(sorted(list(cartpos), key=keyfunc), key=keyfunc):
@@ -164,19 +164,19 @@ class OrderTransition(OrderView):
to = self.request.POST.get('status', '')
if self.order.status == 'n' and to == 'p':
try:
mark_order_paid(self.order, manual=True)
mark_order_paid(self.order, manual=True, user=self.request.user)
except Quota.QuotaExceededException as e:
messages.error(self.request, str(e))
else:
messages.success(self.request, _('The order has been marked as paid.'))
elif self.order.status == 'n' and to == 'c':
self.order.status = Order.STATUS_CANCELLED
self.order.save()
cancel_order(self.order, user=self.request.user)
messages.success(self.request, _('The order has been cancelled.'))
elif self.order.status == 'p' and to == 'n':
self.order.status = Order.STATUS_PENDING
self.order.payment_manual = True
self.order.save()
self.order.log_action('pretix.base.order.unpaid', user=self.request.user)
messages.success(self.request, _('The order has been marked as not paid.'))
elif self.order.status == 'p' and to == 'r':
ret = self.payment_provider.order_control_refund_perform(self.request, self.order)
@@ -247,6 +247,9 @@ class OrderExtend(OrderView):
if self.form.is_valid():
if oldvalue > now():
messages.success(self.request, _('The payment term has been changed.'))
self.order.log_action('pretix.order.changed', user=self.request.user, data={
'expires': self.order.expires
})
self.form.save()
else:
try:
@@ -254,6 +257,9 @@ class OrderExtend(OrderView):
is_available = self.order._is_still_available()
if is_available is True:
self.form.save()
self.order.log_action('pretix.order.changed', user=self.request.user, data={
'expires': self.order.expires
})
messages.success(self.request, _('The payment term has been changed.'))
else:
messages.error(self.request, is_available)

View File

@@ -10,7 +10,7 @@ from django.utils.translation import ugettext as __, ugettext_lazy as _
from pretix.base.models import Quota
from pretix.base.payment import BasePaymentProvider
from pretix.base.services.orders import mark_order_paid
from pretix.base.services.orders import mark_order_paid, mark_order_refunded
from pretix.multidomain.urlreverse import build_absolute_uri
logger = logging.getLogger('pretix.plugins.paypal')
@@ -218,7 +218,7 @@ class Paypal(BasePaymentProvider):
payment_info = None
if not payment_info:
order.mark_refunded()
mark_order_refunded(order)
messages.warning(request, _('We were unable to transfer the money back automatically. '
'Please get in touch with the customer and transfer it back manually.'))
return
@@ -231,11 +231,11 @@ class Paypal(BasePaymentProvider):
refund = sale.refund({})
if not refund.success():
order.mark_refunded()
mark_order_refunded(order, user=request.user)
messages.warning(request, _('We were unable to transfer the money back automatically. '
'Please get in touch with the customer and transfer it back manually.'))
else:
sale = paypalrestsdk.Payment.find(payment_info['id'])
order = order.mark_refunded()
order = mark_order_refunded(order)
order.payment_info = json.dumps(sale.to_dict())
order.save()

View File

@@ -27,12 +27,15 @@ class SenderView(EventPermissionRequiredMixin, FormView):
def form_valid(self, form):
orders = Order.objects.filter(
event=self.request.event, status__in=form.cleaned_data['sendto']
).select_related("user")
users = set([o.user for o in orders])
)
mails = set([o.email for o in orders])
for u in users:
mail(u.email, form.cleaned_data['subject'], form.cleaned_data['message'],
None, self.request.event, locale=u.locale)
self.request.event.log_action('pretix.plugins.sendmail.sent', user=self.request.user, data=dict(
form.cleaned_data))
for m in mails:
mail(m, form.cleaned_data['subject'], form.cleaned_data['message'],
None, self.request.event, locale=m.locale)
messages.success(self.request, _('Your message will be sent to the selected users.'))

View File

@@ -10,7 +10,7 @@ from django.utils.translation import ugettext_lazy as _
from pretix.base.models import Quota
from pretix.base.payment import BasePaymentProvider
from pretix.base.services.orders import mark_order_paid
from pretix.base.services.orders import mark_order_paid, mark_order_refunded
from pretix.helpers.urls import build_absolute_uri
logger = logging.getLogger('pretix.plugins.stripe')
@@ -157,7 +157,7 @@ class Stripe(BasePaymentProvider):
payment_info = None
if not payment_info:
order.mark_refunded()
mark_order_refunded(order, user=request.user)
messages.warning(request, _('We were unable to transfer the money back automatically. '
'Please get in touch with the customer and transfer it back manually.'))
return
@@ -173,10 +173,10 @@ class Stripe(BasePaymentProvider):
'support if the problem persists.'))
logger.error('Stripe error: %s' % str(err))
except stripe.error.StripeError:
order.mark_refunded()
mark_order_refunded(order)
messages.warning(request, _('We were unable to transfer the money back automatically. '
'Please get in touch with the customer and transfer it back manually.'))
else:
order = order.mark_refunded()
order = mark_order_refunded(order)
order.payment_info = str(ch)
order.save()

View File

@@ -7,6 +7,7 @@ from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST
from pretix.base.models import Event, Order
from pretix.base.services.orders import mark_order_refunded
from pretix.plugins.stripe.payment import Stripe
logger = logging.getLogger('pretix.plugins.stripe')
@@ -42,6 +43,8 @@ def webhook(request):
prov = Stripe(event)
prov._init_api()
order.log_action('pretix.plugins.stripe.event', data=event_json)
try:
charge = stripe.Charge.retrieve(charge['id'])
except stripe.error.StripeError as err:
@@ -49,6 +52,6 @@ def webhook(request):
return HttpResponse('StripeError', status=500)
if charge['refunds']['total_count'] > 0 and order.status == Order.STATUS_PAID:
order.mark_refunded()
mark_order_refunded(order)
return HttpResponse(status=200)

View File

@@ -10,6 +10,7 @@ from django.utils.translation import ugettext_lazy as _
from django.views.generic import TemplateView, View
from pretix.base.models import CachedFile, CachedTicket, Order, OrderPosition
from pretix.base.services.orders import cancel_order
from pretix.base.services.tickets import generate
from pretix.base.signals import (
register_payment_providers, register_ticket_outputs,
@@ -215,6 +216,7 @@ class OrderModify(EventViewMixin, OrderDetailMixin, QuestionsViewMixin, Template
messages.error(self.request,
_("We had difficulties processing your input. Please review the errors below."))
return self.get(request, *args, **kwargs)
self.order.log_action('pretix.event.order.modified')
return redirect(self.get_order_url())
def get(self, request, *args, **kwargs):
@@ -251,8 +253,7 @@ class OrderCancel(EventViewMixin, OrderDetailMixin, TemplateView):
return super().dispatch(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
self.order.status = Order.STATUS_CANCELLED
self.order.save()
cancel_order(self.order)
return redirect(self.get_order_url())
def get(self, request, *args, **kwargs):

View File

@@ -12,7 +12,6 @@ from pretix.base.settings import SettingsSandbox
class SettingsTestCase(TestCase):
def setUp(self):
settings.DEFAULTS['test_default'] = {
'default': 'def',
@@ -149,6 +148,7 @@ class SettingsTestCase(TestCase):
def test_serialize_unknown(self):
class Type:
pass
try:
self._test_serialization(Type(), Type)
self.assertTrue(False, 'No exception thrown!')
@@ -195,3 +195,23 @@ class SettingsTestCase(TestCase):
self.assertIsNone(sandbox.bar)
self.assertIsNone(sandbox['baz'])
def test_freeze(self):
olddef = settings.DEFAULTS
settings.DEFAULTS = {
'test_default': {
'default': 'def',
'type': str
}
}
self.event.organizer.settings.set('bar', 'baz')
self.event.organizer.settings.set('foo', 'baz')
self.event.settings.set('foo', 'bar')
try:
self.assertEqual(self.event.settings.freeze(), {
'test_default': 'def',
'bar': 'baz',
'foo': 'bar'
})
finally:
settings.DEFAULTS = olddef