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 def _choose_upload_to(instance, filename):
30 # /school/course/year/month/day
31 return u"{school}/{course}/{year}/{month}/{day}".format(
32 school=instance.course.school.slug,
33 course=instance.course.slug,
34 year=instance.uploaded_at.year,
35 month=instance.uploaded_at.month,
36 day=instance.uploaded_at.day)
38 class Document(models.Model):
39 """ An Abstract Base Class representing a document
40 intended to be subclassed
43 course = models.ForeignKey(Course)
44 tags = TaggableManager(blank=True)
45 name = models.CharField(max_length=255, blank=True, null=True)
46 slug = models.SlugField(max_length=255, null=True)
48 # metadata relevant to the Upload process
49 ip = models.IPAddressField(blank=True, null=True,
50 help_text=u"IP address of the uploader")
51 uploaded_at = models.DateTimeField(null=True, default=datetime.datetime.utcnow)
54 # if True, NEVER show this file
55 # WARNING: This may throw an error on migration
56 is_hidden = models.BooleanField(default=False)
58 fp_file = django_filepicker.models.FPFileField(
59 upload_to=_choose_upload_to,
60 null=True, blank=True,
61 help_text=u"An uploaded file reference from Filepicker.io")
65 ordering = ['-uploaded_at']
67 def __unicode__(self):
68 return u"Document: {1} -- {2}".format(self.name, self.uploaded_at)
70 def _generate_unique_slug(self):
71 """ generate a unique slug based on name and uploaded_at """
72 _slug = defaultfilters.slugify(self.name)
73 klass = self.__class__
74 collision = klass.objects.filter(slug=self.slug)
76 _slug = u"{0}-{1}-{2}-{3}".format(
77 _slug, self.uploaded_at.month,
78 self.uploaded_at.day, self.uploaded_at.microsecond)
81 def save(self, *args, **kwargs):
82 if self.name and not self.slug:
83 self._generate_unique_slug()
84 super(Document, self).save(*args, **kwargs)
87 """ A django model representing an uploaded file and associated metadata.
89 # FIXME: refactor file choices after FP.io integration
92 ('doc', 'MS Word compatible file (.doc, .docx, .rtf, .odf)'),
93 ('img', 'Scan or picture of notes'),
95 ('ppt', 'Powerpoint'),
96 (UNKNOWN_FILE, 'Unknown file'),
99 file_type = models.CharField(max_length=15, \
100 choices=FILE_TYPE_CHOICES, \
101 default=UNKNOWN_FILE, \
102 blank=True, null=True)
104 # Upload files to MEDIA_ROOT/notes/YEAR/MONTH/DAY, 2012/10/30/filename
105 # FIXME: because we are adding files by hand in tasks.py, upload_to is being ignored for media files
106 pdf_file = models.FileField( \
108 upload_to="notes/%Y/%m/%j/",\
109 blank=True, null=True)
110 # No longer keeping a local copy backed by django
111 note_file = models.FileField( \
113 upload_to="notes/%Y/%m/%j/",\
114 blank=True, null=True)
117 embed_url = models.URLField(max_length=1024, blank=True, null=True)
118 download_url = models.URLField(max_length=1024, blank=True, null=True)
120 # Generated by Google Drive by saved locally
121 html = models.TextField(blank=True, null=True)
122 text = models.TextField(blank=True, null=True)
125 # not using, but keeping old data
126 year = models.IntegerField(blank=True, null=True,\
127 default=datetime.datetime.utcnow().year)
128 desc = models.TextField(max_length=511, blank=True, null=True)
130 is_flagged = models.BooleanField(default=False)
131 is_moderated = models.BooleanField(default=False)
134 def __unicode__(self):
135 return u"Note: {0} {1} -- {2}".format(self.file_type, self.name, self.uploaded_at)
138 def get_absolute_url(self):
139 """ Resolve note url, use 'note' route and slug if slug
140 otherwise use note.id
142 if self.slug is not None:
143 # return a url ending in slug
144 return u"/{0}/{1}/{2}".format(self.course.school.slug, self.course.slug, self.slug)
146 # return a url ending in id
147 return u"/{0}/{1}/{2}".format(self.course.school.slug, self.course.slug, self.id)
149 def sanitize_html(self, save=True):
150 """ if self contains html, find all <a> tags and add target=_blank
152 returns True/False on succ/fail and error or count
154 # build a tag sanitizer
155 def add_attribute_target(tag):
156 tag.attrib['target'] = '_blank'
158 # if no html, return false
160 return False, "Note has no html"
162 _html = fromstring(self.html)
163 a_tags = _html.findall('.//a') # recursively find all a tags in document tree
164 # if there are a tags
166 #apply the add attribute function
167 map(add_attribute_target, a_tags)
171 return True, len(a_tags)
173 def _update_parent_updated_at(self):
174 """ update the parent Course.updated_at model
175 with the latest uploaded_at """
176 self.course.updated_at = self.uploaded_at
179 def save(self, *args, **kwargs):
180 if self.uploaded_at and self.uploaded_at > self.course.updated_at:
181 self._update_parent_updated_at()
182 super(Note, self).save(*args, **kwargs)
185 class DriveAuth(models.Model):
186 """ stored google drive authentication and refresh token
187 used for interacting with google drive """
189 email = models.EmailField(default=GOOGLE_USER)
190 credentials = models.TextField() # JSON of Oauth2Credential object
191 stored_at = models.DateTimeField(auto_now=True)
195 def get(email=GOOGLE_USER):
196 """ Staticmethod for getting the singleton DriveAuth object """
197 # FIXME: this is untested
198 return DriveAuth.objects.filter(email=email).reverse()[0]
201 def store(self, creds):
202 """ Transform an existing credentials object to a db serialized """
203 self.email = creds.id_token['email']
204 self.credentials = creds.to_json()
208 def transform_to_cred(self):
209 """ take stored credentials and produce a Credentials object """
210 return Credentials.new_from_json(self.credentials)
213 def __unicode__(self):
214 return u'Gdrive auth for %s created/updated at %s' % \
215 (self.email, self.stored_at)