""" Model to store a microsite in the database. The object is stored as a json representation of the python dict that would have been used in the settings. """ import collections from django.db import models from django.dispatch import receiver from django.db.models.signals import pre_save, pre_delete from django.db.models.base import ObjectDoesNotExist from django.contrib.sites.models import Site from jsonfield.fields import JSONField from model_utils.models import TimeStampedModel from simple_history.models import HistoricalRecords class Microsite(models.Model): """ This is where the information about the microsite gets stored to the db. To achieve the maximum flexibility, most of the fields are stored inside a json field. Notes: - The key field was required for the dict definition at the settings, and it is used in some of the microsite_configuration methods. - The site field is django site. - The values field must be validated on save to prevent the platform from crashing badly in the case the string is not able to be loaded as json. """ site = models.OneToOneField(Site, related_name='microsite') key = models.CharField(max_length=63, db_index=True, unique=True) values = JSONField(null=False, blank=True, load_kwargs={'object_pairs_hook': collections.OrderedDict}) def __unicode__(self): return self.key def get_organizations(self): """ Helper method to return a list of organizations associated with our particular Microsite """ return MicrositeOrganizationMapping.get_organizations_for_microsite_by_pk(self.id) # pylint: disable=no-member @classmethod def get_microsite_for_domain(cls, domain): """ Returns the microsite associated with this domain. Note that we always convert to lowercase, or None if no match """ # remove any port number from the hostname domain = domain.split(':')[0] microsites = cls.objects.filter(site__domain__iexact=domain) return microsites[0] if microsites else None class MicrositeHistory(TimeStampedModel): """ This is an archive table for Microsites model, so that we can maintain a history of changes. Note that the key field is no longer unique """ site = models.ForeignKey(Site, related_name='microsite_history') key = models.CharField(max_length=63, db_index=True) values = JSONField(null=False, blank=True, load_kwargs={'object_pairs_hook': collections.OrderedDict}) def __unicode__(self): return self.key class Meta(object): """ Meta class for this Django model """ verbose_name_plural = "Microsite histories" def _make_archive_copy(instance): """ Helper method to make a copy of a Microsite into the history table """ archive_object = MicrositeHistory( key=instance.key, site=instance.site, values=instance.values, ) archive_object.save() @receiver(pre_delete, sender=Microsite) def on_microsite_deleted(sender, instance, **kwargs): # pylint: disable=unused-argument """ Archive the exam attempt when the item is about to be deleted Make a clone and populate in the History table """ _make_archive_copy(instance) @receiver(pre_save, sender=Microsite) def on_microsite_updated(sender, instance, **kwargs): # pylint: disable=unused-argument """ Archive the microsite on an update operation """ if instance.id: # on an update case, get the original and archive it original = Microsite.objects.get(id=instance.id) _make_archive_copy(original) class MicrositeOrganizationMapping(models.Model): """ Mapping of Organization to which Microsite it belongs """ organization = models.CharField(max_length=63, db_index=True, unique=True) microsite = models.ForeignKey(Microsite, db_index=True) # for archiving history = HistoricalRecords() def __unicode__(self): """String conversion""" return u'{microsite_key}: {organization}'.format( microsite_key=self.microsite.key, organization=self.organization ) @classmethod def get_organizations_for_microsite_by_pk(cls, microsite_pk): """ Returns a list of organizations associated with the microsite key, returned as a set """ return cls.objects.filter(microsite_id=microsite_pk).values_list('organization', flat=True) @classmethod def get_microsite_for_organization(cls, org): """ Returns the microsite object for a given organization based on the table mapping, None if no mapping exists """ try: item = cls.objects.select_related('microsite').get(organization=org) return item.microsite except ObjectDoesNotExist: return None class MicrositeTemplate(models.Model): """ A HTML template that a microsite can use """ microsite = models.ForeignKey(Microsite, db_index=True) template_uri = models.CharField(max_length=255, db_index=True) template = models.TextField() # for archiving history = HistoricalRecords() def __unicode__(self): """String conversion""" return u'{microsite_key}: {template_uri}'.format( microsite_key=self.microsite.key, template_uri=self.template_uri ) class Meta(object): """ Meta class for this Django model """ unique_together = (('microsite', 'template_uri'),) @classmethod def get_template_for_microsite(cls, domain, template_uri): """ Returns the template object for the microsite, None if not found """ try: return cls.objects.get(microsite__site__domain=domain, template_uri=template_uri) except ObjectDoesNotExist: return None