mirror of
https://github.com/pretix/pretix.git
synced 2026-05-21 17:54:08 +00:00
Added documentation on logging
This commit is contained in:
@@ -16,3 +16,4 @@ Contents:
|
|||||||
settings
|
settings
|
||||||
background
|
background
|
||||||
email
|
email
|
||||||
|
logging
|
||||||
|
|||||||
102
doc/development/implementation/logging.rst
Normal file
102
doc/development/implementation/logging.rst
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
Logging
|
||||||
|
=======
|
||||||
|
|
||||||
|
As pretix is handling monetary transactions, we are very careful to make it possible to review all changes
|
||||||
|
in the system that lead to the current state.
|
||||||
|
|
||||||
|
Logging changes
|
||||||
|
---------------
|
||||||
|
|
||||||
|
We log data changes to the database in a format that makes it mossible to display those logs to a human, if
|
||||||
|
required. pretix stores all those logs centrally in a model called :py:class:`pretix.base.models.LogEntry`.
|
||||||
|
We recommend all relevant models to inherit from ``LoggedModel`` as it simplifies creating new log entries:
|
||||||
|
|
||||||
|
.. autoclass:: pretix.base.models.LoggedModel
|
||||||
|
:members: log_action, all_logentries
|
||||||
|
|
||||||
|
To actually log an action, you can just call the ``log_action`` method on your object::
|
||||||
|
|
||||||
|
order.log_action('pretix.event.order.cancelled', user=user, data={})
|
||||||
|
|
||||||
|
The positional ``action`` argument should represent the type of action and should be globally unique, we
|
||||||
|
recomment do prefix it with your packagename, e.g. ``paypal.payment.rejected``. The ``user`` argument is
|
||||||
|
optional and may contain the user who performed the action. The optional ``data`` argument can contain
|
||||||
|
additional information about this action.
|
||||||
|
|
||||||
|
Logging form actions
|
||||||
|
""""""""""""""""""""
|
||||||
|
|
||||||
|
A very common use case is to log the changes to a model that have been done in a ``ModelForm``. In this case,
|
||||||
|
we generally use a custom ``form_valid`` method on our ``FormView`` that looks like this::
|
||||||
|
|
||||||
|
@transaction.atomic()
|
||||||
|
def form_valid(self, form):
|
||||||
|
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)
|
||||||
|
|
||||||
|
It gets a little bit more complicated if your form allows file uploads::
|
||||||
|
|
||||||
|
@transaction.atomic()
|
||||||
|
def form_valid(self, form):
|
||||||
|
if form.has_changed():
|
||||||
|
self.request.event.log_action(
|
||||||
|
'pretix.event.changed', user=self.request.user, data={
|
||||||
|
k: (form.cleaned_data.get(k).name
|
||||||
|
if isinstance(form.cleaned_data.get(k), File)
|
||||||
|
else 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)
|
||||||
|
|
||||||
|
|
||||||
|
Displaying logs
|
||||||
|
"""""""""""""""
|
||||||
|
|
||||||
|
If you want to display the logs of a particular object to a user in the backend, you can use the
|
||||||
|
following ready-to-include template::
|
||||||
|
|
||||||
|
{% include "pretixcontrol/includes/logs.html" with obj=order %}
|
||||||
|
|
||||||
|
We now need a way to translate the action codes like ``pretix.event.changed`` into human-readable
|
||||||
|
strings. The :py:attr:`pretix.base.signals.logentry_display` signals allows you to do so. A simple
|
||||||
|
implementation could look like::
|
||||||
|
|
||||||
|
from pretix.base.signals import logentry_display
|
||||||
|
|
||||||
|
@receiver(signal=logentry_display)
|
||||||
|
def pretixcontrol_logentry_display(sender, logentry, **kwargs):
|
||||||
|
plains = {
|
||||||
|
'pretix.event.order.paid': _('The order has been marked as paid.'),
|
||||||
|
'pretix.event.order.refunded': _('The order has been refunded.'),
|
||||||
|
'pretix.event.order.cancelled': _('The order has been cancelled.'),
|
||||||
|
...
|
||||||
|
}
|
||||||
|
if logentry.action_type in plains:
|
||||||
|
return plains[logentry.action_type]
|
||||||
|
|
||||||
|
|
||||||
|
Logging technical information
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
|
If you just want to log technical information to a log file on disk that does not need to be parsed
|
||||||
|
and displayed later, you can just use Python's ``logging`` module::
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
logger.info('Startup complete.')
|
||||||
|
|
||||||
|
This is also very useful to provide debugging information when an exception occurs::
|
||||||
|
|
||||||
|
try:
|
||||||
|
foo()
|
||||||
|
except:
|
||||||
|
logger.exception('Error when calling foo()') # Traceback will automatically be appended
|
||||||
|
messages.error(request, _('An error occured.'))
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
from .auth import User
|
from .auth import User
|
||||||
from .base import CachedFile, cachedfile_name
|
from .base import CachedFile, LoggedModel, cachedfile_name
|
||||||
from .event import Event, EventLock, EventPermission, EventSetting
|
from .event import Event, EventLock, EventPermission, EventSetting
|
||||||
from .invoices import Invoice, InvoiceLine, invoice_filename
|
from .invoices import Invoice, InvoiceLine, invoice_filename
|
||||||
from .items import (
|
from .items import (
|
||||||
|
|||||||
@@ -64,4 +64,9 @@ class LoggedModel(models.Model, LoggingMixin):
|
|||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
def all_logentries(self):
|
def all_logentries(self):
|
||||||
|
"""
|
||||||
|
Returns all log entries that are attached to this object.
|
||||||
|
|
||||||
|
:return: A QuerySet of LogEntry objects
|
||||||
|
"""
|
||||||
return self.logentries.all().select_related('user')
|
return self.logentries.all().select_related('user')
|
||||||
|
|||||||
Reference in New Issue
Block a user