Commit d7f781db by Saleem Latif

Add SiteConfiguration configuration model to LMS

parent 1dff9d45
......@@ -823,6 +823,9 @@ INSTALLED_APPS = (
# Theming
'openedx.core.djangoapps.theming',
# Site configuration for theming and behavioral modification
'openedx.core.djangoapps.site_configuration',
# comment common
'django_comment_common',
......
......@@ -1819,6 +1819,9 @@ INSTALLED_APPS = (
# Theming
'openedx.core.djangoapps.theming',
# Site configuration for theming and behavioral modification
'openedx.core.djangoapps.site_configuration',
# Our courseware
'courseware',
'student',
......
"""
This app is used for creating/updating site's configuration. This app encapsulate configuration related logic for sites
and provides a way for sites to override default/system behavioural or presentation logic.
Models:
SiteConfiguration (models.Model):
This model contains configuration for a site and can be used to override OpenEdx configurations.
Fields:
site (OneToOneField): one to one field relating each configuration to a single site
values (JSONField): json field to store configurations for a site
Usage:
configuration of each site would be available as `configuration` attribute to django site.
If you want to access current site's configuration simply access it as `request.site.configuration`.
SiteConfigurationHistory (TimeStampedModel):
This model keeps a track of all the changes made to SiteConfiguration with time stamps.
Fields:
site (ForeignKey): foreign-key to django Site
values (JSONField): json field to store configurations for a site
Usage:
configuration history of each site would be available as `configuration_histories` attribute to django site.
If you want to access a list of current site's configuration history simply access it as
`request.site.configuration_histories.all()`.
"""
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
import jsonfield.fields
import django_extensions.db.fields
class Migration(migrations.Migration):
dependencies = [
('sites', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='SiteConfiguration',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('values', jsonfield.fields.JSONField(blank=True)),
('site', models.OneToOneField(related_name='configuration', to='sites.Site')),
],
),
migrations.CreateModel(
name='SiteConfigurationHistory',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('created', django_extensions.db.fields.CreationDateTimeField(auto_now_add=True, verbose_name='created')),
('modified', django_extensions.db.fields.ModificationDateTimeField(auto_now=True, verbose_name='modified')),
('values', jsonfield.fields.JSONField(blank=True)),
('site', models.ForeignKey(related_name='configuration_histories', to='sites.Site')),
],
options={
'ordering': ('-modified', '-created'),
'abstract': False,
'get_latest_by': 'modified',
},
),
]
"""
Django models for site configurations.
"""
import collections
from django.db import models
from django.contrib.sites.models import Site
from django.db.models.signals import post_save
from django.dispatch import receiver
from django_extensions.db.models import TimeStampedModel
from jsonfield.fields import JSONField
class SiteConfiguration(models.Model):
"""
Model for storing site configuration. These configuration override OpenEdx configurations and settings.
e.g. You can override site name, logo image, favicon etc. using site configuration.
Fields:
site (OneToOneField): one to one field relating each configuration to a single site
values (JSONField): json field to store configurations for a site
"""
site = models.OneToOneField(Site, related_name='configuration')
values = JSONField(
null=False,
blank=True,
load_kwargs={'object_pairs_hook': collections.OrderedDict}
)
def __unicode__(self):
return u"<SiteConfiguration: {site} >".format(site=self.site)
def __repr__(self):
return self.__unicode__()
class SiteConfigurationHistory(TimeStampedModel):
"""
This is an archive table for SiteConfiguration, so that we can maintain a history of
changes. Note that the site field is not unique in this model, compared to SiteConfiguration.
Fields:
site (ForeignKey): foreign-key to django Site
values (JSONField): json field to store configurations for a site
"""
site = models.ForeignKey(Site, related_name='configuration_histories')
values = JSONField(
null=False,
blank=True,
load_kwargs={'object_pairs_hook': collections.OrderedDict}
)
def __unicode__(self):
return u"<SiteConfigurationHistory: {site}, Last Modified: {modified} >".format(
modified=self.modified,
site=self.site,
)
def __repr__(self):
return self.__unicode__()
@receiver(post_save, sender=SiteConfiguration)
def update_site_configuration_history(sender, instance, **kwargs): # pylint: disable=unused-argument
"""
Add site configuration changes to site configuration history.
Args:
sender: sender of the signal i.e. SiteConfiguration model
instance: SiteConfiguration instance associated with the current signal
**kwargs: extra key word arguments
"""
SiteConfigurationHistory.objects.create(
site=instance.site,
values=instance.values,
)
"""
Model factories for unit testing views or models.
"""
from factory.django import DjangoModelFactory
from openedx.core.djangoapps.site_configuration.models import SiteConfiguration
class SiteConfigurationFactory(DjangoModelFactory):
"""
Factory class for SiteConfiguration model
"""
class Meta(object):
model = SiteConfiguration
values = {}
"""
Tests for site configuration's django models.
"""
from django.test import TestCase
from django.db import IntegrityError, transaction
from django.contrib.sites.models import Site
from openedx.core.djangoapps.site_configuration.models import SiteConfigurationHistory
from openedx.core.djangoapps.site_configuration.tests.factories import SiteConfigurationFactory
class SiteConfigurationTests(TestCase):
"""
Tests for SiteConfiguration and its signals/receivers.
"""
domain = 'site_configuration_post_save_receiver_example.com'
name = 'site_configuration_post_save_receiver_example'
@classmethod
def setUpClass(cls):
super(SiteConfigurationTests, cls).setUpClass()
cls.site, _ = Site.objects.get_or_create(domain=cls.domain, name=cls.domain)
def test_site_configuration_post_save_receiver(self):
"""
Test that and entry is added to SiteConfigurationHistory model each time a new
SiteConfiguration is added.
"""
# add SiteConfiguration to database
site_configuration = SiteConfigurationFactory.create(
site=self.site,
)
# Verify an entry to SiteConfigurationHistory was added.
site_configuration_history = SiteConfigurationHistory.objects.filter(
site=site_configuration.site,
).all()
# Make sure an entry (and only one entry) is saved for SiteConfiguration
self.assertEqual(len(site_configuration_history), 1)
def test_site_configuration_post_update_receiver(self):
"""
Test that and entry is added to SiteConfigurationHistory each time a
SiteConfiguration is updated.
"""
# add SiteConfiguration to database
site_configuration = SiteConfigurationFactory.create(
site=self.site,
)
site_configuration.values = {'test': 'test'}
site_configuration.save()
# Verify an entry to SiteConfigurationHistory was added.
site_configuration_history = SiteConfigurationHistory.objects.filter(
site=site_configuration.site,
).all()
# Make sure two entries (one for save and one for update) are saved for SiteConfiguration
self.assertEqual(len(site_configuration_history), 2)
def test_no_entry_is_saved_for_errors(self):
"""
Test that and entry is not added to SiteConfigurationHistory if there is an error while
saving SiteConfiguration.
"""
# add SiteConfiguration to database
site_configuration = SiteConfigurationFactory.create(
site=self.site,
)
# Verify an entry to SiteConfigurationHistory was added.
site_configuration_history = SiteConfigurationHistory.objects.filter(
site=site_configuration.site,
).all()
# Make sure entry is saved if there is no error
self.assertEqual(len(site_configuration_history), 1)
with transaction.atomic():
with self.assertRaises(IntegrityError):
# try to add a duplicate entry
site_configuration = SiteConfigurationFactory.create(
site=self.site,
)
site_configuration_history = SiteConfigurationHistory.objects.filter(
site=site_configuration.site,
).all()
# Make sure no entry is saved if there an error
self.assertEqual(len(site_configuration_history), 1)
"""
Comprehensive Theme related models.
Django models supporting the Comprehensive Theming subsystem
"""
from django.db import models
from django.contrib.sites.models import Site
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment