From 83b5fa2fa6aed328214b3ef75884df13d8f26465 Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Sat, 12 Dec 2015 15:41:48 +0100 Subject: [PATCH] Added a model for logging actions --- doc/development/models.rst | 6 +++- src/pretix/base/migrations/0003_logentry.py | 31 +++++++++++++++++++++ src/pretix/base/models/__init__.py | 3 +- src/pretix/base/models/base.py | 19 +++++++++++++ src/pretix/base/models/event.py | 3 +- src/pretix/base/models/items.py | 9 +++--- src/pretix/base/models/log.py | 30 ++++++++++++++++++++ src/pretix/base/models/orders.py | 4 +-- src/pretix/base/models/organizer.py | 3 +- 9 files changed, 98 insertions(+), 10 deletions(-) create mode 100644 src/pretix/base/migrations/0003_logentry.py create mode 100644 src/pretix/base/models/log.py diff --git a/doc/development/models.rst b/doc/development/models.rst index 4105490d89..d581bf5da8 100644 --- a/doc/development/models.rst +++ b/doc/development/models.rst @@ -70,4 +70,8 @@ Carts and Orders .. autoclass:: pretix.base.models.QuestionAnswer :members: -.. _cleanerversion: https://github.com/swisscom/cleanerversion \ No newline at end of file +Logging +------- + +.. autoclass:: pretix.base.models.LogEntry + :members: diff --git a/src/pretix/base/migrations/0003_logentry.py b/src/pretix/base/migrations/0003_logentry.py new file mode 100644 index 0000000000..2bbd029d29 --- /dev/null +++ b/src/pretix/base/migrations/0003_logentry.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9 on 2015-12-12 13:32 +from __future__ import unicode_literals + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('contenttypes', '0002_remove_content_type_name'), + ('pretixbase', '0002_auto_20151212_1123'), + ] + + operations = [ + migrations.CreateModel( + name='LogEntry', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('object_id', models.PositiveIntegerField()), + ('datetime', models.DateTimeField(auto_now_add=True)), + ('action_type', models.CharField(max_length=255)), + ('data', models.TextField(default='{}')), + ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')), + ('event', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='pretixbase.Event')), + ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/src/pretix/base/models/__init__.py b/src/pretix/base/models/__init__.py index de32244020..dd203ea60d 100644 --- a/src/pretix/base/models/__init__.py +++ b/src/pretix/base/models/__init__.py @@ -5,6 +5,7 @@ from .items import ( Item, ItemCategory, ItemVariation, Property, PropertyValue, Question, Quota, VariationsField, itempicture_upload_to, ) +from .log import LogEntry from .orders import ( CachedTicket, CartPosition, ObjectWithAnswers, Order, OrderPosition, QuestionAnswer, generate_secret, @@ -16,5 +17,5 @@ __all__ = [ 'ItemCategory', 'Item', 'Property', 'PropertyValue', 'ItemVariation', 'VariationsField', 'Question', 'Quota', 'Order', 'CachedTicket', 'QuestionAnswer', 'ObjectWithAnswers', 'OrderPosition', 'CartPosition', 'EventSetting', 'OrganizerSetting', 'EventLock', 'cachedfile_name', 'itempicture_upload_to', - 'generate_secret' + 'generate_secret', 'LogEntry' ] diff --git a/src/pretix/base/models/base.py b/src/pretix/base/models/base.py index 3d822a2f91..cea57f989a 100644 --- a/src/pretix/base/models/base.py +++ b/src/pretix/base/models/base.py @@ -1,5 +1,6 @@ import uuid +from django.contrib.contenttypes.fields import GenericRelation from django.db import models from django.db.models.signals import post_delete from django.dispatch import receiver @@ -26,3 +27,21 @@ def cached_file_delete(sender, instance, **kwargs): if instance.file: # Pass false so FileField doesn't save the model. instance.file.delete(False) + + +class LoggedModel(models.Model): + logentries = GenericRelation('LogEntry') + + def log_action(self, user, action, data): + from .log import LogEntry + from .event import Event + + event = None + if isinstance(self, Event): + event = self + elif hasattr(self, 'event'): + event = self.event + LogEntry.objects.create(content_object=self, user=user, action=action, data=data, event=event) + + class Meta: + abstract = True diff --git a/src/pretix/base/models/event.py b/src/pretix/base/models/event.py index 248ae8ed0b..5e374ecb01 100644 --- a/src/pretix/base/models/event.py +++ b/src/pretix/base/models/event.py @@ -11,13 +11,14 @@ from django.utils.timezone import now from django.utils.translation import ugettext_lazy as _ from pretix.base.i18n import I18nCharField +from pretix.base.models.base import LoggedModel from pretix.base.settings import SettingsProxy from .auth import User from .organizer import Organizer -class Event(models.Model): +class Event(LoggedModel): """ This model represents an event. An event is anything you can buy tickets for. diff --git a/src/pretix/base/models/items.py b/src/pretix/base/models/items.py index 9a9b4835a0..6f6975f8e6 100644 --- a/src/pretix/base/models/items.py +++ b/src/pretix/base/models/items.py @@ -10,12 +10,13 @@ from django.utils.translation import ugettext_lazy as _ from typing import List, Tuple from pretix.base.i18n import I18nCharField, I18nTextField +from pretix.base.models.base import LoggedModel from ..types import VariationDict from .event import Event -class ItemCategory(models.Model): +class ItemCategory(LoggedModel): """ Items can be sorted into these categories. @@ -72,7 +73,7 @@ def itempicture_upload_to(instance, filename: str) -> str: ) -class Item(models.Model): +class Item(LoggedModel): """ An item is a thing which can be sold. It belongs to an event and may or may not belong to a category. Items are often also called 'products' but are named 'items' internally due to historic reasons. @@ -539,7 +540,7 @@ class VariationsField(models.ManyToManyField): return super(RelatedField, self).formfield(**defaults) -class Question(models.Model): +class Question(LoggedModel): """ A question is an input field that can be used to extend a ticket by custom information, e.g. "Attendee age". A question can allow one o several @@ -613,7 +614,7 @@ class Question(models.Model): self.event.get_cache().clear() -class Quota(models.Model): +class Quota(LoggedModel): """ A quota is a "pool of tickets". It is there to limit the number of items of a certain type to be sold. For example, you could have a quota of 500 diff --git a/src/pretix/base/models/log.py b/src/pretix/base/models/log.py new file mode 100644 index 0000000000..4ec9183f61 --- /dev/null +++ b/src/pretix/base/models/log.py @@ -0,0 +1,30 @@ +from django.contrib.contenttypes.fields import GenericForeignKey +from django.contrib.contenttypes.models import ContentType +from django.db import models + + +class LogEntry(models.Model): + """ + Represents a change or action that has been performed on another object + in the database. This uses django.contrib.contenttypes to allow a + relation to an arbitrary database object. + + :param user: The user that performed the action + :type user: User + :param action_type: The type of action that has been performed. This is + used to look up the renderer used to describe the action in a human- + readable way. This should be some namespaced value using dotted + notationto avaoid duplicates, e.g. + ``"pretix.plugins.banktransfer.incoming_transfer"``. + :type action_type: str + :param data: Arbitrary data that can be used by the log action renderer + :type data: str + """ + content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) + object_id = models.PositiveIntegerField() + content_object = GenericForeignKey('content_type', 'object_id') + datetime = models.DateTimeField(auto_now_add=True) + user = models.ForeignKey('User', null=True, blank=True, on_delete=models.PROTECT) + event = models.ForeignKey('Event', null=True, blank=True, on_delete=models.CASCADE) + action_type = models.CharField(max_length=255) + data = models.TextField(default='{}') diff --git a/src/pretix/base/models/orders.py b/src/pretix/base/models/orders.py index bb017e57b1..18f58bc62b 100644 --- a/src/pretix/base/models/orders.py +++ b/src/pretix/base/models/orders.py @@ -7,7 +7,7 @@ from django.utils.timezone import now from django.utils.translation import ugettext_lazy as _ from typing import List, Union -from .base import CachedFile +from .base import CachedFile, LoggedModel from .event import Event from .items import Item, ItemVariation, Question, Quota @@ -16,7 +16,7 @@ def generate_secret(): return ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(16)) -class Order(models.Model): +class Order(LoggedModel): """ An order is created when a user clicks 'buy' on his cart. It holds several OrderPositions and is connected to an user. It has an diff --git a/src/pretix/base/models/organizer.py b/src/pretix/base/models/organizer.py index 3cb7433cbe..17a67828c9 100644 --- a/src/pretix/base/models/organizer.py +++ b/src/pretix/base/models/organizer.py @@ -3,12 +3,13 @@ from django.db import models from django.utils.functional import cached_property from django.utils.translation import ugettext_lazy as _ +from pretix.base.models.base import LoggedModel from pretix.base.settings import SettingsProxy from .auth import User -class Organizer(models.Model): +class Organizer(LoggedModel): """ This model represents an entity organizing events, e.g. a company, institution, charity, person, …