Skip to content

Commit

Permalink
Add a simple framework for event- and organizer-related settings
Browse files Browse the repository at this point in the history
  • Loading branch information
raphaelm committed Feb 14, 2015
1 parent 38e313c commit 73aa3fb
Show file tree
Hide file tree
Showing 3 changed files with 241 additions and 2 deletions.
49 changes: 49 additions & 0 deletions src/pretix/base/migrations/0009_eventsetting_organizersetting.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models, migrations
import versions.models


class Migration(migrations.Migration):

dependencies = [
('pretixbase', '0008_quota_locked'),
]

operations = [
migrations.CreateModel(
name='EventSetting',
fields=[
('id', models.CharField(primary_key=True, serialize=False, max_length=36)),
('identity', models.CharField(max_length=36)),
('version_start_date', models.DateTimeField()),
('version_end_date', models.DateTimeField(blank=True, default=None, null=True)),
('version_birth_date', models.DateTimeField()),
('key', models.CharField(max_length=255)),
('value', models.TextField()),
('event', versions.models.VersionedForeignKey(related_name='setting_objects', to='pretixbase.Event')),
],
options={
'abstract': False,
},
bases=(models.Model,),
),
migrations.CreateModel(
name='OrganizerSetting',
fields=[
('id', models.CharField(primary_key=True, serialize=False, max_length=36)),
('identity', models.CharField(max_length=36)),
('version_start_date', models.DateTimeField()),
('version_end_date', models.DateTimeField(blank=True, default=None, null=True)),
('version_birth_date', models.DateTimeField()),
('key', models.CharField(max_length=255)),
('value', models.TextField()),
('organizer', versions.models.VersionedForeignKey(related_name='setting_objects', to='pretixbase.Organizer')),
],
options={
'abstract': False,
},
bases=(models.Model,),
),
]
126 changes: 126 additions & 0 deletions src/pretix/base/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from django.conf import settings
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin
from django.db.models import Q, Count
from django.utils.functional import cached_property
from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _
from django.template.defaultfilters import date as _date
Expand Down Expand Up @@ -227,6 +228,58 @@ class Meta:
def __str__(self):
return self.name

class OrganizerSettingsProxy:
"""
This objects allows convenient access to settings stored in the
OrganizerSettings database model. It exposes all settings as properties
and it will do all the nasty defaults stuff for
you. It will return None for non-existing properties.
"""

def __init__(self, organizer):
self._organizer = organizer
self._cached_obj = None

def _cache(self):
if self._cached_obj is None:
self._cached_obj = {}
for setting in self._organizer.setting_objects.current.all():
self._cached_obj[setting.key] = setting
return self._cached_obj

def __getattr__(self, key):
if key in self._cache():
return self._cache()[key].value
if key in OrganizerSetting.DEFAULTS:
return OrganizerSetting.DEFAULTS[key]
return None

def __delattr__(self, key):
if key.startswith('_'):
return super().__delattr__(key)
if key in self._cache():
self._cache()[key].delete()
del self._cache()[key]

def __setattr__(self, key, value):
if key.startswith('_'):
return super().__setattr__(key, value)
if key in self._cache():
s = self._cache()[key]
s = s.clone()
else:
s = OrganizerSetting(organizer=self._organizer, key=key)
s.value = value
s.save()
self._cache()[key] = s

@cached_property
def settings(self):
"""
Returns an object representing this organizer's settings
"""
return Organizer.OrganizerSettingsProxy(self)


class OrganizerPermission(Versionable):
"""
Expand Down Expand Up @@ -386,6 +439,56 @@ def get_cache(self) -> "pretix.base.cache.EventRelatedCache":
from pretix.base.cache import EventRelatedCache
return EventRelatedCache(self)

class EventSettingsProxy:
"""
This objects allows convenient access to settings stored in the
EventSettings database model. It exposes all settings as properties
and it will do all the nasty inheritance and defaults stuff for
you. It will return None for non-existing properties.
"""

def __init__(self, event):
self._event = event
self._cached_obj = None

def _cache(self):
if self._cached_obj is None:
self._cached_obj = {}
for setting in self._event.setting_objects.current.all():
self._cached_obj[setting.key] = setting
return self._cached_obj

def __getattr__(self, key):
if key in self._cache():
return self._cache()[key].value
return getattr(self._event.organizer.settings, key)

def __setattr__(self, key, value):
if key.startswith('_'):
return super().__setattr__(key, value)
if key in self._cache():
s = self._cache()[key]
s = s.clone()
else:
s = EventSetting(event=self._event, key=key)
s.value = value
s.save()
self._cache()[key] = s

def __delattr__(self, key):
if key.startswith('_'):
return super().__delattr__(key)
if key in self._cache():
self._cache()[key].delete()
del self._cache()[key]

@cached_property
def settings(self):
"""
Returns an object representing this event's settings
"""
return Event.EventSettingsProxy(self)


class EventPermission(Versionable):
"""
Expand Down Expand Up @@ -1320,3 +1423,26 @@ class CartPosition(Versionable):
class Meta:
verbose_name = _("Cart position")
verbose_name_plural = _("Cart positions")


class EventSetting(Versionable):
"""
An event settings is a key-value setting which can be set for a
specific event
"""
event = VersionedForeignKey(Event, related_name='setting_objects')
key = models.CharField(max_length=255)
value = models.TextField()


class OrganizerSetting(Versionable):
"""
An event option is a key-value setting which can be set for an
organizer. It will be inherited by the events of this organizer
"""
DEFAULTS = {

}
organizer = VersionedForeignKey(Organizer, related_name='setting_objects')
key = models.CharField(max_length=255)
value = models.TextField()
68 changes: 66 additions & 2 deletions src/pretix/base/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
from pretix.base.models import (
Event, Organizer, Item, ItemVariation,
Property, PropertyValue, User, Quota,
Order, OrderPosition, CartPosition
)
Order, OrderPosition, CartPosition,
OrganizerSetting)
from pretix.base.types import VariationDict


Expand Down Expand Up @@ -292,3 +292,67 @@ def test_multiple(self):
quota2.size = 0
quota2.save()
self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_GONE, 0))


class SettingsTestCase(TestCase):

def setUp(self):
OrganizerSetting.DEFAULTS['test_default'] = 'def'
self.organizer = Organizer.objects.create(name='Dummy', slug='dummy')
self.event = Event.objects.create(
organizer=self.organizer, name='Dummy', slug='dummy',
date_from=now(),
)

def test_event_set_explicit(self):
self.event.settings.test = 'foo'
self.assertEqual(self.event.settings.test, 'foo')

# Reload object
self.event = Event.objects.get(identity=self.event.identity)
self.assertEqual(self.event.settings.test, 'foo')

def test_event_set_on_organizer(self):
self.organizer.settings.test = 'foo'
self.assertEqual(self.organizer.settings.test, 'foo')
self.assertEqual(self.event.settings.test, 'foo')

# Reload object
self.organizer = Organizer.objects.get(identity=self.organizer.identity)
self.event = Event.objects.get(identity=self.event.identity)
self.assertEqual(self.organizer.settings.test, 'foo')
self.assertEqual(self.event.settings.test, 'foo')

def test_override_organizer(self):
self.organizer.settings.test = 'foo'
self.event.settings.test = 'bar'
self.assertEqual(self.organizer.settings.test, 'foo')
self.assertEqual(self.event.settings.test, 'bar')

# Reload object
self.organizer = Organizer.objects.get(identity=self.organizer.identity)
self.event = Event.objects.get(identity=self.event.identity)
self.assertEqual(self.organizer.settings.test, 'foo')
self.assertEqual(self.event.settings.test, 'bar')

def test_default(self):
self.assertEqual(self.organizer.settings.test_default, 'def')
self.assertEqual(self.event.settings.test_default, 'def')

def test_delete(self):
self.organizer.settings.test = 'foo'
self.event.settings.test = 'bar'
self.assertEqual(self.organizer.settings.test, 'foo')
self.assertEqual(self.event.settings.test, 'bar')

del self.event.settings.test
self.assertEqual(self.event.settings.test, 'foo')

self.event = Event.objects.get(identity=self.event.identity)
self.assertEqual(self.event.settings.test, 'foo')

del self.organizer.settings.test
self.assertIsNone(self.organizer.settings.test)

self.organizer = Organizer.objects.get(identity=self.organizer.identity)
self.assertIsNone(self.organizer.settings.test)

0 comments on commit 73aa3fb

Please sign in to comment.