From 8058accee036bd353128a5ae0b89c8ea2da641f9 Mon Sep 17 00:00:00 2001 From: Magnus Hagander Date: Mon, 7 Mar 2016 21:41:45 +0100 Subject: [PATCH] Get rid of PgModel, replacing it with simple signals We were already using signals for everything except delete, and even in our old version of django the delete signal exists (it didn't exist when this code was first written). Django doesn't really like models to be OOP like this, so keeping PgModel would cause issues with upcoming changes in django 1.8. Using simple functions is easier, and the actual functionality is replicated straight off. --- docs/django.rst | 16 +--- docs/frontend.rst | 2 +- pgweb/contributors/models.py | 6 +- pgweb/core/models.py | 5 +- pgweb/docs/models.py | 3 +- pgweb/downloads/models.py | 3 +- pgweb/events/models.py | 3 +- pgweb/featurematrix/models.py | 6 +- pgweb/lists/models.py | 6 +- pgweb/news/models.py | 3 +- pgweb/profserv/models.py | 3 +- pgweb/pugs/models.py | 3 +- pgweb/pwn/models.py | 4 +- pgweb/quotes/models.py | 3 +- pgweb/sponsors/models.py | 8 +- pgweb/survey/models.py | 6 +- pgweb/urls.py | 2 +- pgweb/util/bases.py | 176 ---------------------------------- pgweb/util/signals.py | 145 ++++++++++++++++++++++++++++ 19 files changed, 172 insertions(+), 231 deletions(-) delete mode 100644 pgweb/util/bases.py create mode 100644 pgweb/util/signals.py diff --git a/docs/django.rst b/docs/django.rst index 7d4066ec..2d893edf 100644 --- a/docs/django.rst +++ b/docs/django.rst @@ -38,13 +38,11 @@ not accidentally committed to the main repository, or cause merge conflicts. Forms ----- -There are some special things to consider when dealing with forms. For +here are some special things to consider when dealing with forms. For any objects that are going to be moderated, the Model that is used -should inherit from the PgModel model, instead of just the regular -django.db.models.Model. When this is done, the send_notification -attribute should be set to True. This will cause the system to -automatically send out notifications to the slaves list whenever a new -object is created or an existing one is modified. +should set the send_notification attribute to True. This will cause +the system to automatically send out notifications to the slaves list +whenever a new object is created or an existing one is modified. If the form contains any text fields that accept markdown, the attribute markdown_fields should be set to a tuple containing a list @@ -75,12 +73,6 @@ auth.py This module implements the community login provider for logging into both the website itself and the admin interface. -bases.py -++++++++ -This module implements base classes to inherit from. Specifically, it -implements the PgModel base class that is used to automatically -generate notifications. - contexts.py +++++++++++ This module implements custom contexts, which is used to implement the diff --git a/docs/frontend.rst b/docs/frontend.rst index 7e42737c..813d399f 100644 --- a/docs/frontend.rst +++ b/docs/frontend.rst @@ -45,7 +45,7 @@ done by using the @cache() decorator on the view method. Caching should be kept lower for pages that have frequently updating data, such as the front page or the survey results page. -Any model inheriting from PgModel can define a tuple or a function +Any model can define a tuple or a function called *purge_urls* (if it's a function, it will be called and should return a tuple or a generator). Each entry is a regular expression, and this data will be automatically removed from the diff --git a/pgweb/contributors/models.py b/pgweb/contributors/models.py index 9cbc9322..fbd72b84 100644 --- a/pgweb/contributors/models.py +++ b/pgweb/contributors/models.py @@ -1,9 +1,7 @@ from django.db import models from django.contrib.auth.models import User -from pgweb.util.bases import PgModel - -class ContributorType(PgModel, models.Model): +class ContributorType(models.Model): typename = models.CharField(max_length=32, null=False, blank=False) sortorder = models.IntegerField(null=False, default=100) extrainfo = models.TextField(null=True, blank=True) @@ -17,7 +15,7 @@ class ContributorType(PgModel, models.Model): class Meta: ordering = ('sortorder',) -class Contributor(PgModel, models.Model): +class Contributor(models.Model): ctype = models.ForeignKey(ContributorType) lastname = models.CharField(max_length=100, null=False, blank=False) firstname = models.CharField(max_length=100, null=False, blank=False) diff --git a/pgweb/core/models.py b/pgweb/core/models.py index 050fb468..07209eec 100644 --- a/pgweb/core/models.py +++ b/pgweb/core/models.py @@ -1,6 +1,5 @@ from django.db import models from django.contrib.auth.models import User -from pgweb.util.bases import PgModel from pgweb.util.misc import varnish_purge from datetime import datetime @@ -13,7 +12,7 @@ TESTING_CHOICES = ( ) TESTING_SHORTSTRING = ('', 'rc', 'beta', 'alpha') -class Version(PgModel, models.Model): +class Version(models.Model): tree = models.DecimalField(max_digits=3, decimal_places=1, null=False, blank=False, unique=True) latestminor = models.IntegerField(null=False, blank=False, default=0, help_text="For testing versions, latestminor means latest beta/rc number. For other releases, it's the latest minor release number in the tree.") reldate = models.DateField(null=False, blank=False) @@ -113,7 +112,7 @@ class OrganisationType(models.Model): def __unicode__(self): return self.typename -class Organisation(PgModel, models.Model): +class Organisation(models.Model): name = models.CharField(max_length=100, null=False, blank=False, unique=True) approved = models.BooleanField(null=False, default=False) address = models.TextField(null=False, blank=True) diff --git a/pgweb/docs/models.py b/pgweb/docs/models.py index d882951e..8c8132be 100644 --- a/pgweb/docs/models.py +++ b/pgweb/docs/models.py @@ -1,6 +1,5 @@ from django.db import models from django.contrib.auth.models import User -from pgweb.util.bases import PgModel from pgweb.core.models import Version from datetime import datetime @@ -24,7 +23,7 @@ class DocPage(models.Model): # Index file first, because we want to list versions by file unique_together = [('file', 'version')] -class DocComment(PgModel, models.Model): +class DocComment(models.Model): version = models.DecimalField(max_digits=3, decimal_places=1, null=False) file = models.CharField(max_length=64, null=False, blank=False) comment = models.TextField(null=False, blank=False) diff --git a/pgweb/downloads/models.py b/pgweb/downloads/models.py index 854534f1..961e142e 100644 --- a/pgweb/downloads/models.py +++ b/pgweb/downloads/models.py @@ -1,5 +1,4 @@ from django.db import models -from pgweb.util.bases import PgModel from pgweb.core.models import Organisation @@ -74,7 +73,7 @@ class LicenceType(models.Model): class Meta: ordering = ('typename',) -class Product(PgModel, models.Model): +class Product(models.Model): name = models.CharField(max_length=100, null=False, blank=False, unique=True) approved = models.BooleanField(null=False, default=False) org = models.ForeignKey(Organisation, db_column="publisher_id", null=False, verbose_name="Organisation") diff --git a/pgweb/events/models.py b/pgweb/events/models.py index 088c2771..59cc1de4 100644 --- a/pgweb/events/models.py +++ b/pgweb/events/models.py @@ -1,9 +1,8 @@ from django.db import models -from pgweb.util.bases import PgModel from core.models import Country, Language, Organisation -class Event(PgModel, models.Model): +class Event(models.Model): approved = models.BooleanField(null=False, blank=False, default=False) org = models.ForeignKey(Organisation, null=False, blank=False, verbose_name="Organisation", help_text="If no organisations are listed, please check the organisation list and contact the organisation manager or webmaster@postgresql.org if none are listed.") diff --git a/pgweb/featurematrix/models.py b/pgweb/featurematrix/models.py index 20fe9bed..72439cf6 100644 --- a/pgweb/featurematrix/models.py +++ b/pgweb/featurematrix/models.py @@ -1,7 +1,5 @@ from django.db import models -from pgweb.util.bases import PgModel - choices_map = { 0: {'str': 'No', 'class': 'no', 'bgcolor': '#ffdddd'}, 1: {'str': 'Yes', 'class': 'yes', 'bgcolor': '#ddffdd'}, @@ -10,7 +8,7 @@ choices_map = { } choices = [(k, v['str']) for k,v in choices_map.items()] -class FeatureGroup(PgModel, models.Model): +class FeatureGroup(models.Model): groupname = models.CharField(max_length=100, null=False, blank=False) groupsort = models.IntegerField(null=False, blank=False) @@ -29,7 +27,7 @@ class FeatureMatrixField(models.IntegerField): super(FeatureMatrixField, self).__init__(null=False, blank=False, default=0, verbose_name=verbose_name, choices=choices) self.visible_default = visible_default -class Feature(PgModel, models.Model): +class Feature(models.Model): group = models.ForeignKey(FeatureGroup, null=False, blank=False) featurename = models.CharField(max_length=100, null=False, blank=False) featuredescription = models.TextField(null=False, blank=True) diff --git a/pgweb/lists/models.py b/pgweb/lists/models.py index b3ea339c..220d8255 100644 --- a/pgweb/lists/models.py +++ b/pgweb/lists/models.py @@ -1,8 +1,6 @@ from django.db import models -from pgweb.util.bases import PgModel - -class MailingListGroup(PgModel, models.Model): +class MailingListGroup(models.Model): groupname = models.CharField(max_length=64, null=False, blank=False) sortkey = models.IntegerField(null=False, default=10) @@ -18,7 +16,7 @@ class MailingListGroup(PgModel, models.Model): class Meta: ordering = ('sortkey', ) -class MailingList(PgModel, models.Model): +class MailingList(models.Model): group = models.ForeignKey(MailingListGroup, null=False) listname = models.CharField(max_length=64, null=False, blank=False) active = models.BooleanField(null=False, default=False) diff --git a/pgweb/news/models.py b/pgweb/news/models.py index 8fffe1d7..330f2032 100644 --- a/pgweb/news/models.py +++ b/pgweb/news/models.py @@ -1,9 +1,8 @@ from django.db import models from datetime import date from pgweb.core.models import Organisation -from pgweb.util.bases import PgModel -class NewsArticle(PgModel, models.Model): +class NewsArticle(models.Model): org = models.ForeignKey(Organisation, null=False, blank=False, verbose_name="Organisation", help_text="If no organisations are listed, please check the organisation list and contact the organisation manager or webmaster@postgresql.org if none are listed.") approved = models.BooleanField(null=False, blank=False, default=False) date = models.DateField(null=False, blank=False, default=date.today) diff --git a/pgweb/profserv/models.py b/pgweb/profserv/models.py index ee4202e8..64b036af 100644 --- a/pgweb/profserv/models.py +++ b/pgweb/profserv/models.py @@ -1,9 +1,8 @@ from django.db import models from pgweb.core.models import Organisation -from pgweb.util.bases import PgModel -class ProfessionalService(PgModel, models.Model): +class ProfessionalService(models.Model): approved = models.BooleanField(null=False, blank=False, default=False) org = models.ForeignKey(Organisation, null=False, blank=False, unique=True, diff --git a/pgweb/pugs/models.py b/pgweb/pugs/models.py index d71414a1..8d73f0c3 100644 --- a/pgweb/pugs/models.py +++ b/pgweb/pugs/models.py @@ -1,7 +1,6 @@ from django.db import models -from pgweb.util.bases import PgModel -class PUG(PgModel, models.Model): +class PUG(models.Model): """ contains information about a local PostgreSQL user group """ diff --git a/pgweb/pwn/models.py b/pgweb/pwn/models.py index 3263d631..5ca4f87e 100644 --- a/pgweb/pwn/models.py +++ b/pgweb/pwn/models.py @@ -1,10 +1,8 @@ from django.db import models -from pgweb.util.bases import PgModel - from datetime import date -class PwnPost(PgModel, models.Model): +class PwnPost(models.Model): date = models.DateField(null=False, blank=False, default=date.today, unique=True) intro = models.TextField(null=False, blank=False) content = models.TextField(null=False, blank=False) diff --git a/pgweb/quotes/models.py b/pgweb/quotes/models.py index 5258eb54..4d4ee914 100644 --- a/pgweb/quotes/models.py +++ b/pgweb/quotes/models.py @@ -1,7 +1,6 @@ from django.db import models -from pgweb.util.bases import PgModel -class Quote(PgModel, models.Model): +class Quote(models.Model): approved = models.BooleanField(null=False, default=False) quote = models.TextField(null=False, blank=False) who = models.CharField(max_length=100, null=False, blank=False) diff --git a/pgweb/sponsors/models.py b/pgweb/sponsors/models.py index 5ab44859..150233f3 100644 --- a/pgweb/sponsors/models.py +++ b/pgweb/sponsors/models.py @@ -2,9 +2,7 @@ from django.db import models from core.models import Country -from pgweb.util.bases import PgModel - -class SponsorType(PgModel, models.Model): +class SponsorType(models.Model): typename = models.CharField(max_length=32, null=False, blank=False) description = models.TextField(null=False, blank=False) sortkey = models.IntegerField(null=False, default=10) @@ -18,7 +16,7 @@ class SponsorType(PgModel, models.Model): class Meta: ordering = ('sortkey', ) -class Sponsor(PgModel, models.Model): +class Sponsor(models.Model): sponsortype = models.ForeignKey(SponsorType, null=False) name = models.CharField(max_length=128, null=False, blank=False) url = models.URLField(null=False, blank=False) @@ -33,7 +31,7 @@ class Sponsor(PgModel, models.Model): class Meta: ordering = ('name', ) -class Server(PgModel, models.Model): +class Server(models.Model): name = models.CharField(max_length=32, null=False, blank=False) sponsors = models.ManyToManyField(Sponsor) dedicated = models.BooleanField(null=False, default=True) diff --git a/pgweb/survey/models.py b/pgweb/survey/models.py index 26346428..ac531da1 100644 --- a/pgweb/survey/models.py +++ b/pgweb/survey/models.py @@ -1,7 +1,5 @@ from django.db import models -from pgweb.util.bases import PgModel - from datetime import datetime # internal text/value object @@ -15,7 +13,7 @@ class SurveyAnswerValues(object): self.votes = votes self.votespercent = votespercent -class Survey(PgModel, models.Model): +class Survey(models.Model): question = models.CharField(max_length=500, null=False, blank=False) opt1 = models.CharField(max_length=500, null=False, blank=False) opt2 = models.CharField(max_length=500, null=False, blank=False) @@ -81,7 +79,7 @@ class Survey(PgModel, models.Model): # free to save this one. super(Survey, self).save() -class SurveyAnswer(PgModel, models.Model): +class SurveyAnswer(models.Model): survey = models.ForeignKey(Survey, null=False, blank=False, primary_key=True) tot1 = models.IntegerField(null=False, default=0) tot2 = models.IntegerField(null=False, default=0) diff --git a/pgweb/urls.py b/pgweb/urls.py index a4d226a7..9875ca05 100644 --- a/pgweb/urls.py +++ b/pgweb/urls.py @@ -2,7 +2,7 @@ from django.conf.urls.defaults import * from django.views.generic.simple import redirect_to # Register our save signal handlers -from pgweb.util.bases import register_basic_signal_handlers +from pgweb.util.signals import register_basic_signal_handlers register_basic_signal_handlers() # Uncomment the next two lines to enable the admin: diff --git a/pgweb/util/bases.py b/pgweb/util/bases.py deleted file mode 100644 index b19e96dc..00000000 --- a/pgweb/util/bases.py +++ /dev/null @@ -1,176 +0,0 @@ -from django.db.models.signals import pre_save, post_save -from django.db import models -from django.conf import settings - -from util.middleware import get_current_user -from util.misc import varnish_purge -from mailqueue.util import send_simple_mail - -class PgModel(object): - send_notification = False - purge_urls = () - notify_fields = None - modifying_user = None - - def PostSaveHandler(self): - """ - If a set of URLs are available as purge_urls, then send commands - to the cache to purge those urls. - """ - if callable(self.purge_urls): - purgelist = self.purge_urls() - else: - if not self.purge_urls: return - purgelist = self.purge_urls - map(varnish_purge, purgelist) - - - def PreSaveHandler(self): - """If send_notification is set to True, send a default formatted notification mail""" - - if not self.send_notification: - return - - (subj, cont) = self._get_changes_texts() - - if not cont: - # If any of these come back as None, it means that nothing actually changed, - # or that we don't care to send out notifications about it. - return - - cont = self._build_url() + "\n\n" + cont - - - # Build the mail text - send_simple_mail(settings.NOTIFICATION_FROM, - settings.NOTIFICATION_EMAIL, - "%s by %s" % (subj, get_current_user()), - cont) - - def delete(self): - # We can't compare the object, but we should be able to construct something anyway - if self.send_notification: - subject = "%s id %s has been deleted by %s" % ( - self._meta.verbose_name, - self.id, - get_current_user()) - - send_simple_mail(settings.NOTIFICATION_FROM, - settings.NOTIFICATION_EMAIL, - subject, - self.full_text_representation()) - - # Now call our super to actually delete the object - super(PgModel, self).delete() - - def _get_changes_texts(self): - try: - oldobj = self.__class__.objects.get(pk=self.pk) - except self.DoesNotExist: - return ('A new %s has been added' % self._meta.verbose_name, self.full_text_representation()) - if hasattr(self,'approved'): - # This object has the capability to do approving. Apply the following logic: - # 1. If object was unapproved, and is still unapproved, don't send notification - # 2. If object was unapproved, and is now approved, send "object approved" notification - # 3. If object was approved, and is no longer approved, send "object unapproved" notification - # 4. (FIXME: configurable?) If object was approved and is still approved, send changes notification - if not self.approved: - if not oldobj.approved: - # Still unapproved, just accept the changes - return (None, None) - # Went from approved to unapproved - return ('%s id %s has been unapproved' % (self._meta.verbose_name, self.id), self.full_text_representation()) - else: - if not oldobj.approved: - # Object went from unapproved to approved - return ('%s id %s has been approved' % (self._meta.verbose_name, self.id), - self.full_text_representation()) - # Object contents have changed. Generate a diff! - diff = self.full_text_diff(oldobj) - if not diff: - return (None, None) - return ('%s id %s has been modified' % (self._meta.verbose_name, self.id), - "The following fields have been modified:\n\n%s" % diff) - else: - # If there is no approved field, but send_notifications was set - # to True, we notify on all changes. - diff = self.full_text_diff(oldobj) - if not diff: - return (None, None) - return ('%s id %s has been modified' % (self._meta.verbose_name, self.id), - "The following fields have been modified:\n\n%s" % diff) - - def _get_all_notification_fields(self): - if self.notify_fields: - return self.notify_fields - else: - # Include all field names except specified ones, that are "direct" (by get_field_by_name()[2]) - return [n for n in self._meta.get_all_field_names() if not n in ('approved', 'submitter', 'id', ) and self._meta.get_field_by_name(n)[2]] - - def full_text_representation(self): - fieldlist = self._get_all_notification_fields() - if not fieldlist: - return "This object does not know how to express itself." - - return "\n".join([u'%s: %s' % (n, self._get_attr_value(n)) for n in fieldlist]) - - def _get_attr_value(self, fieldname): - try: - # see if this is a Many-to-many field, if yes, we want to print out a pretty list - value = getattr(self, fieldname) - if isinstance(self._meta.get_field_by_name(fieldname)[0], models.ManyToManyField): - return ", ".join(map(lambda x: unicode(x), value.all())) - return value - except ValueError, v: - # NOTE! If the object is brand new, and it has a many-to-many relationship, we can't - # access this data yet. So just return that it's not available yet. - # XXX: This is an ugly way to find it out, and is dependent on - # the version of django used. But I've found no better way... - if v.message.find('" needs to have a value for field "') and v.message.find('" before this many-to-many relationship can be used.') > -1: - return "" - else: - raise v - - def _build_url(self): - if self.id: - return "%s/admin/%s/%s/%s/" % ( - settings.SITE_ROOT, - self._meta.app_label, - self._meta.module_name, - self.id, - ) - else: - return "%s/admin/%s/%s/" % ( - settings.SITE_ROOT, - self._meta.app_label, - self._meta.module_name, - ) - - def full_text_diff(self, oldobj): - fieldlist = self._get_all_notification_fields() - if not fieldlist: - return "This object does not know how to express ifself." - - s = "\n\n".join(["%s from: %s\n%s to: %s" % ( - n, - oldobj._get_attr_value(n), - n, - self._get_attr_value(n), - ) for n in fieldlist if oldobj._get_attr_value(n) != self._get_attr_value(n)]) - if not s: return None - return s - - -def my_pre_save_handler(sender, **kwargs): - instance = kwargs['instance'] - if isinstance(instance, PgModel): - instance.PreSaveHandler() - -def my_post_save_handler(sender, **kwargs): - instance = kwargs['instance'] - if isinstance(instance, PgModel): - instance.PostSaveHandler() - -def register_basic_signal_handlers(): - pre_save.connect(my_pre_save_handler) - post_save.connect(my_post_save_handler) diff --git a/pgweb/util/signals.py b/pgweb/util/signals.py new file mode 100644 index 00000000..667640b1 --- /dev/null +++ b/pgweb/util/signals.py @@ -0,0 +1,145 @@ +from django.db.models.signals import pre_save, post_save, pre_delete +from django.db import models +from django.conf import settings + +from util.middleware import get_current_user +from util.misc import varnish_purge +from mailqueue.util import send_simple_mail + +def _build_url(obj): + if obj.id: + return "%s/admin/%s/%s/%s/" % ( + settings.SITE_ROOT, + obj._meta.app_label, + obj._meta.module_name, + obj.id, + ) + else: + return "%s/admin/%s/%s/" % ( + settings.SITE_ROOT, + obj._meta.app_label, + obj._meta.module_name, + ) + +def _get_full_text_diff(obj, oldobj): + fieldlist = _get_all_notification_fields(obj) + if not fieldlist: + return "This object does not know how to express ifself." + + s = "\n\n".join(["%s from: %s\n%s to: %s" % ( + n, + _get_attr_value(oldobj, n), + n, + _get_attr_value(obj, n), + ) for n in fieldlist if _get_attr_value(oldobj, n) != _get_attr_value(obj, n)]) + if not s: return None + return s + +def _get_all_notification_fields(obj): + if hasattr(obj, 'notify_fields'): + return obj.notify_fields + else: + # Include all field names except specified ones, + # that are "direct" (by get_field_by_name()[2]) + return [n for n in obj._meta.get_all_field_names() if not n in ('approved', 'submitter', 'id', ) and obj._meta.get_field_by_name(n)[2]] + +def _get_attr_value(obj, fieldname): + try: + # see if this is a Many-to-many field. If yes, we want to print + # it out as a pretty list + value = getattr(obj, fieldname) + if isinstance(obj._meta.get_field_by_name(fieldname)[0], models.ManyToManyField): + return ", ".join(map(lambda x: unicode(x), value.all())) + return value + except ValueError, v: + # NOTE! If the object is brand new, and it has a many-to-many relationship, we can't + # access this data yet. So just return that it's not available yet. + # XXX: This is an ugly way to find it out, and is dependent on + # the version of django used. But I've found no better way... + if v.message.find('" needs to have a value for field "') and v.message.find('" before this many-to-many relationship can be used.') > -1: + return "" + else: + raise v + +def _get_full_text_representation(obj): + fieldlist = _get_all_notification_fields(obj) + if not fieldlist: + return "This object does not know how to express itself." + + return "\n".join([u'%s: %s' % (n, _get_attr_value(obj, n)) for n in fieldlist]) + +def _get_notification_text(obj): + try: + oldobj = obj.__class__.objects.get(pk=obj.pk) + except obj.DoesNotExist: + return ('A new {0} as been added'.format(obj._meta.verbose_name), + _get_full_text_representation(obj)) + + if hasattr(obj, 'approved'): + # This object has the capability to do approving. Apply the following logic: + # 1. If object was unapproved, and is still unapproved, don't send notification + # 2. If object was unapproved, and is now approved, send "object approved" notification + # 3. If object was approved, and is no longer approved, send "object unapproved" notification + # 4. (FIXME: configurable?) If object was approved and is still approved, send changes notification + if not obj.approved: + if not oldobj.approved: + # Was approved, still approved -> no notification + return (None, None) + # From approved to unapproved + return ('{0} id {1} has been unapproved'.format(obj._meta.verbose_name, obj.id), + _get_full_text_representation(obj)) + else: + if not oldobj.approved: + # Object went from unapproved to approved + return ('{0} id {1} has been approved'.format(obj._meta.verbose_name, obj.id), + _get_full_text_representation(obj)) + # Object contents have changed. Generate a diff! + diff = _get_full_text_diff(obj, oldobj) + if not diff: + return (None, None) + return ('{0} id {1} has been modified'.format(obj._meta.verbose_name, obj.id), + 'The following fields have been modified:\n\n%s' % diff) + else: + # If there is no approved field, but send_notifications was set + # to True, we notify on all changes. + diff = _get_full_text_diff(obj, oldobj) + if not diff: + return (None, None) + return ('{0} id {1} has been modified'.format(obj._meta.verbose_name, obj.id), + 'The following fields have been modified:\n\n%s' % diff) + +def my_pre_save_handler(sender, **kwargs): + instance = kwargs['instance'] + if getattr(instance, 'send_notification', False): + (subj, cont) = _get_notification_text(instance) + if cont: + cont = _build_url(instance) + "\n\n" + cont + send_simple_mail(settings.NOTIFICATION_FROM, + settings.NOTIFICATION_EMAIL, + "%s by %s" % (subj, get_current_user()), + cont) + +def my_pre_delete_handler(sender, **kwargs): + instance = kwargs['instance'] + if getattr(instance, 'send_notification', False): + send_simple_mail(settings.NOTIFICATION_FROM, + settings.NOTIFICATION_EMAIL, + "%s id %s has been deleted by %s" % ( + instance._meta.verbose_name, + instance.id, + get_current_user()), + _get_full_text_representation(instance)) + +def my_post_save_handler(sender, **kwargs): + instance = kwargs['instance'] + if hasattr(instance, 'purge_urls'): + if callable(instance.purge_urls): + purgelist = instance.purge_urls() + else: + purgelist = instance.purge_urls + map(varnish_purge, purgelist) + +def register_basic_signal_handlers(): + pre_save.connect(my_pre_save_handler) + pre_delete.connect(my_pre_delete_handler) + post_save.connect(my_post_save_handler)