forked from CGM_Public/pretix_original
Added logging for all basic operations
This commit is contained in:
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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."),
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.'))
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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.'))
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user