3 # Copyright (C) 2012 FinalsClub Foundation
6 Models for the notes django app.
7 Contains only the minimum for handling files and their representation
11 from django.conf import settings
12 from django.core.files.storage import FileSystemStorage
13 from django.db import models
14 from django.template import defaultfilters
15 import django_filepicker
16 from lxml.html import fromstring, tostring
17 from oauth2client.client import Credentials
18 from taggit.managers import TaggableManager
20 from karmaworld.apps.courses.models import Course
23 from secrets.drive import GOOGLE_USER
25 GOOGLE_USER = u'admin@karmanotes.org'
27 fs = FileSystemStorage(location=settings.MEDIA_ROOT)
29 class Document(models.Model):
30 """ An Abstract Base Class representing a document
31 intended to be subclassed
34 course = models.ForeignKey(Course)
35 tags = TaggableManager(blank=True)
36 name = models.CharField(max_length=255, blank=True, null=True)
37 slug = models.SlugField(max_length=255, null=True)
39 # metadata relevant to the Upload process
40 ip = models.IPAddressField(blank=True, null=True,
41 help_text=u"IP address of the uploader")
42 uploaded_at = models.DateTimeField(null=True, default=datetime.datetime.utcnow)
45 # if True, NEVER show this file
46 # WARNING: This may throw an error on migration
47 is_hidden = models.BooleanField(default=False)
49 fp_note = django_filepicker.models.FPFileField(
50 upload_to='queue/%Y/%m/%j/',
51 null=True, blank=True,
52 help_text=u"An uploaded file reference from Filepicker.io")
56 ordering = ['-uploaded_at']
59 def __unicode__(self):
60 return u"Document: {1} -- {2}".format(self.name, self.uploaded_at)
62 def _generate_unique_slug(self):
63 """ generate a unique slug based on name and uploaded_at """
64 _slug = defaultfilters.slugify(self.name)
65 klass = self.__class__
66 collision = klass.objects.filter(slug=self.slug)
68 _slug = u"{0}-{1}-{2}-{3}".format(
69 _slug, self.uploaded_at.month,
70 self.uploaded_at.day, self.uploaded_at.microsecond)
73 def save(self, *args, **kwargs):
74 if self.name and not self.slug:
75 self._generate_unique_slug()
76 super(Document, self).save(*args, **kwargs)
79 """ A django model representing an uploaded file and associated metadata.
81 # FIXME: refactor file choices after FP.io integration
84 ('doc', 'MS Word compatible file (.doc, .docx, .rtf, .odf)'),
85 ('img', 'Scan or picture of notes'),
87 ('ppt', 'Powerpoint'),
88 (UNKNOWN_FILE, 'Unknown file'),
91 file_type = models.CharField(max_length=15, \
92 choices=FILE_TYPE_CHOICES, \
93 default=UNKNOWN_FILE, \
94 blank=True, null=True)
96 # Upload files to MEDIA_ROOT/notes/YEAR/MONTH/DAY, 2012/10/30/filename
97 # FIXME: because we are adding files by hand in tasks.py, upload_to is being ignored for media files
98 pdf_file = models.FileField( \
100 upload_to="notes/%Y/%m/%j/",\
101 blank=True, null=True)
102 # No longer keeping a local copy backed by django
103 note_file = models.FileField( \
105 upload_to="notes/%Y/%m/%j/",\
106 blank=True, null=True)
109 embed_url = models.URLField(max_length=1024, blank=True, null=True)
110 download_url = models.URLField(max_length=1024, blank=True, null=True)
112 # Generated by Google Drive by saved locally
113 html = models.TextField(blank=True, null=True)
114 text = models.TextField(blank=True, null=True)
117 # not using, but keeping old data
118 year = models.IntegerField(blank=True, null=True,\
119 default=datetime.datetime.utcnow().year)
120 desc = models.TextField(max_length=511, blank=True, null=True)
122 is_flagged = models.BooleanField(default=False)
123 is_moderated = models.BooleanField(default=False)
126 def __unicode__(self):
127 return u"Note: {0} {1} -- {2}".format(self.file_type, self.name, self.uploaded_at)
130 def get_absolute_url(self):
131 """ Resolve note url, use 'note' route and slug if slug
132 otherwise use note.id
134 if self.slug is not None:
135 # return a url ending in slug
136 return u"/{0}/{1}/{2}".format(self.course.school.slug, self.course.slug, self.slug)
138 # return a url ending in id
139 return u"/{0}/{1}/{2}".format(self.course.school.slug, self.course.slug, self.id)
141 def sanitize_html(self, save=True):
142 """ if self contains html, find all <a> tags and add target=_blank
144 returns True/False on succ/fail and error or count
146 # build a tag sanitizer
147 def add_attribute_target(tag):
148 tag.attrib['target'] = '_blank'
150 # if no html, return false
152 return False, "Note has no html"
154 _html = fromstring(self.html)
155 a_tags = _html.findall('.//a') # recursively find all a tags in document tree
156 # if there are a tags
158 #apply the add attribute function
159 map(add_attribute_target, a_tags)
163 return True, len(a_tags)
165 def _update_parent_updated_at(self):
166 """ update the parent Course.updated_at model
167 with the latest uploaded_at """
168 self.course.updated_at = self.uploaded_at
171 def save(self, *args, **kwargs):
172 if self.uploaded_at and self.uploaded_at > self.course.updated_at:
173 self._update_parent_updated_at()
174 super(Note, self).save(*args, **kwargs)
177 class DriveAuth(models.Model):
178 """ stored google drive authentication and refresh token
179 used for interacting with google drive """
181 email = models.EmailField(default=GOOGLE_USER)
182 credentials = models.TextField() # JSON of Oauth2Credential object
183 stored_at = models.DateTimeField(auto_now=True)
187 def get(email=GOOGLE_USER):
188 """ Staticmethod for getting the singleton DriveAuth object """
189 # FIXME: this is untested
190 return DriveAuth.objects.filter(email=email).reverse()[0]
193 def store(self, creds):
194 """ Transform an existing credentials object to a db serialized """
195 self.email = creds.id_token['email']
196 self.credentials = creds.to_json()
200 def transform_to_cred(self):
201 """ take stored credentials and produce a Credentials object """
202 return Credentials.new_from_json(self.credentials)
205 def __unicode__(self):
206 return u'Gdrive auth for %s created/updated at %s' % \
207 (self.email, self.stored_at)