Refactor Note > Document, an Abstract Base Class
authorSeth Woodworth <seth@sethish.com>
Tue, 27 Aug 2013 17:51:53 +0000 (13:51 -0400)
committerSeth Woodworth <seth@sethish.com>
Tue, 27 Aug 2013 17:51:53 +0000 (13:51 -0400)
Here I have refactored all of the shared content of the Note model
to the Document model so I can base Note and RawDocument on it
I've also cleanedup .save() validation and generation

karmaworld/apps/notes/models.py

index dc574946a5cee11960ac1401f43e2301be003b47..689b2a3802733a047a894c22009c1d79530c1ab3 100644 (file)
@@ -12,6 +12,7 @@ from django.conf import settings
 from django.core.files.storage import FileSystemStorage
 from django.db import models
 from django.template import defaultfilters
+import django_filepicker
 from lxml.html import fromstring, tostring
 from oauth2client.client import Credentials
 from taggit.managers import TaggableManager
@@ -25,9 +26,59 @@ except:
 
 fs = FileSystemStorage(location=settings.MEDIA_ROOT)
 
-class Note(models.Model):
+class Document(models.Model):
+    """ An Abstract Base Class representing a document
+        intended to be subclassed
+
+    """
+    course          = models.ForeignKey(Course)
+    tags            = TaggableManager(blank=True)
+    name            = models.CharField(max_length=255, blank=True, null=True)
+    slug            = models.SlugField(max_length=255, null=True)
+
+    # metadata relevant to the Upload process
+    ip      = models.IPAddressField(blank=True, null=True,
+                help_text=u"IP address of the uploader")
+    uploaded_at     = models.DateTimeField(null=True, default=datetime.datetime.utcnow)
+
+
+    # if True, NEVER show this file
+    # WARNING: This may throw an error on migration
+    is_hidden       = models.BooleanField(default=False)
+
+    fp_note = django_filepicker.models.FPFileField(
+            upload_to='queue/%Y/%m/%j/',
+            null=True, blank=True,
+            help_text=u"An uploaded file reference from Filepicker.io")
+
+    class Meta:
+        abstract = True
+        ordering = ['-uploaded_at']
+
+
+    def __unicode__(self):
+        return u"Document: {1} -- {2}".format(self.name, self.uploaded_at)
+
+    def _generate_unique_slug(self):
+        """ generate a unique slug based on name and uploaded_at  """
+        _slug = defaultfilters.slugify(self.name)
+        klass = self.__class__
+        collision = klass.objects.filter(slug=self.slug)
+        if collision:
+            _slug = u"{0}-{1}-{2}-{3}".format(
+                    _slug, self.uploaded_at.month,
+                    self.uploaded_at.day, self.uploaded_at.microsecond)
+        self.slug = _slug
+
+    def save(self, *args, **kwargs):
+        if self.name and not self.slug:
+            self._generate_unique_slug()
+        super(Document, self).save(*args, **kwargs)
+
+class Note(Document):
     """ A django model representing an uploaded file and associated metadata.
     """
+    # FIXME: refactor file choices after FP.io integration
     UNKNOWN_FILE = '???'
     FILE_TYPE_CHOICES = (
         ('doc', 'MS Word compatible file (.doc, .docx, .rtf, .odf)'),
@@ -37,76 +88,44 @@ class Note(models.Model):
         (UNKNOWN_FILE, 'Unknown file'),
     )
 
-    course          = models.ForeignKey(Course)
-    # Tagging system
-    tags            = TaggableManager(blank=True)
-
-    name            = models.CharField(max_length=255, blank=True, null=True)
-    slug            = models.SlugField(max_length=255, null=True)
-    year            = models.IntegerField(blank=True, null=True,
-                        default=datetime.datetime.utcnow().year)
-    desc            = models.TextField(max_length=511, blank=True, null=True)
-    uploaded_at     = models.DateTimeField(null=True, default=datetime.datetime.utcnow)
-
-    file_type       = models.CharField(max_length=15,
-                            choices=FILE_TYPE_CHOICES,
-                            default=UNKNOWN_FILE,
+    file_type       = models.CharField(max_length=15,  \
+                            choices=FILE_TYPE_CHOICES, \
+                            default=UNKNOWN_FILE,      \
                             blank=True, null=True)
 
     # Upload files to MEDIA_ROOT/notes/YEAR/MONTH/DAY, 2012/10/30/filename
     # FIXME: because we are adding files by hand in tasks.py, upload_to is being ignored for media files
-    note_file       = models.FileField(
-                            storage=fs,
-                            upload_to="notes/%Y/%m/%j/",
+    pdf_file       = models.FileField(                  \
+                            storage=fs,                 \
+                            upload_to="notes/%Y/%m/%j/",\
                             blank=True, null=True)
-    pdf_file       = models.FileField(
-                            storage=fs,
-                            upload_to="notes/%Y/%m/%j/",
+    # No longer keeping a local copy backed by django
+    note_file       = models.FileField(                 \
+                            storage=fs,                 \
+                            upload_to="notes/%Y/%m/%j/",\
                             blank=True, null=True)
 
-    ## post gdrive conversion data
+    # Google Drive URLs
     embed_url       = models.URLField(max_length=1024, blank=True, null=True)
     download_url    = models.URLField(max_length=1024, blank=True, null=True)
-    # for word processor documents
+
+    # Generated by Google Drive by saved locally
     html            = models.TextField(blank=True, null=True)
     text            = models.TextField(blank=True, null=True)
 
-    # if True, NEVER show this file
-    draft           = models.BooleanField(default=False)
 
+    # not using, but keeping old data
+    year            = models.IntegerField(blank=True, null=True,\
+                        default=datetime.datetime.utcnow().year)
+    desc            = models.TextField(max_length=511, blank=True, null=True)
 
-    class Meta:
-        """ Sort files by most recent first """
-        ordering = ['-uploaded_at']
+    is_flagged      = models.BooleanField(default=False)
+    is_moderated    = models.BooleanField(default=False)
 
 
     def __unicode__(self):
-        return u"{0}: {1} -- {2}".format(self.file_type, self.name, self.uploaded_at)
+        return u"Note: {0} {1} -- {2}".format(self.file_type, self.name, self.uploaded_at)
 
-    def save(self, *args, **kwargs):
-        """ override built-in save to ensure contextual self.name """
-        # TODO: If self.name isn't set, generate one based on uploaded_name
-        # if we fail to set the Note.name earlier than this, use the saved filename
-
-        # only generate a slug if the name has been set, and slug hasn't
-        if not self.slug and self.name:
-            slug = defaultfilters.slugify(self.name)
-            cursor = Note.objects.filter(slug=slug)
-            # If there are no other notes with this slug, then the slug does not need an id
-            if cursor.count() == 0:
-                self.slug = slug
-            else:
-                super(Note, self).save(*args, **kwargs) # generate self.id
-                self.slug = defaultfilters.slugify("%s %s" % (self.name, self.id))
-            super(Note, self).save(*args, **kwargs)
-
-        # Check if Note.uploaded_at is after Course.updated_at
-        if self.uploaded_at and self.uploaded_at > self.course.updated_at:
-            self.course.updated_at = self.uploaded_at
-            # if it is, update Course.updated_at
-            self.course.save()
-
-        super(Note, self).save(*args, **kwargs)
 
     def get_absolute_url(self):
         """ Resolve note url, use 'note' route and slug if slug
@@ -143,6 +162,16 @@ class Note(models.Model):
                 self.save()
             return True, len(a_tags)
 
+    def _update_parent_updated_at(self):
+        """ update the parent Course.updated_at model
+            with the latest uploaded_at """
+        self.course.updated_at = self.uploaded_at
+        self.course.save()
+
+    def save(self, *args, **kwargs):
+        if self.uploaded_at and self.uploaded_at > self.course.updated_at:
+            self._update_parent_updated_at()
+        super(Note, self).save(*args, **kwargs)
 
 
 class DriveAuth(models.Model):