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
 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
 
 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
 
 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)
        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)
 
 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
        )
 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)
        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)
 
 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
                # 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)
 
 from django.db import models
-from pgweb.util.bases import PgModel
 
 from pgweb.core.models import Organisation
 
        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")
 
 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 <a href=\"/account/orglist/\">organisation list</a> and contact the organisation manager or webmaster@postgresql.org if none are listed.")
 
 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'},
 }
 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)
 
                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)
 
 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)
 
        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)
 
 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 <a href=\"/account/orglist/\">organisation list</a> 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)
 
 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,
 
 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
        """
 
 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)
 
 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)
 
 
 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)
        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)
        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)
 
 from django.db import models
 
-from pgweb.util.bases import PgModel
-
 from datetime import datetime
 
 # internal text/value 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)
                # 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)
 
 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:
 
+++ /dev/null
-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 "<not available yet>"
-                       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)
 
--- /dev/null
+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 "<not available yet>"
+               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)