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 from lxml.html import fromstring, tostring
16 from oauth2client.client import Credentials
17 from taggit.managers import TaggableManager
19 from karmaworld.apps.courses.models import Course
22 from secrets.drive import GOOGLE_USER
24 GOOGLE_USER = u'admin@karmanotes.org'
26 fs = FileSystemStorage(location=settings.MEDIA_ROOT)
28 class Note(models.Model):
29 """ A django model representing an uploaded file and associated metadata.
33 ('doc', 'MS Word compatible file (.doc, .docx, .rtf, .odf)'),
34 ('img', 'Scan or picture of notes'),
36 ('ppt', 'Powerpoint'),
37 (UNKNOWN_FILE, 'Unknown file'),
40 course = models.ForeignKey(Course)
42 tags = TaggableManager(blank=True)
44 name = models.CharField(max_length=255, blank=True, null=True)
45 slug = models.SlugField(max_length=255, null=True)
46 year = models.IntegerField(blank=True, null=True,
47 default=datetime.datetime.utcnow().year)
48 desc = models.TextField(max_length=511, blank=True, null=True)
49 uploaded_at = models.DateTimeField(null=True, default=datetime.datetime.utcnow)
51 file_type = models.CharField(max_length=15,
52 choices=FILE_TYPE_CHOICES,
54 blank=True, null=True)
56 # Upload files to MEDIA_ROOT/notes/YEAR/MONTH/DAY, 2012/10/30/filename
57 # FIXME: because we are adding files by hand in tasks.py, upload_to is being ignored for media files
58 note_file = models.FileField(
60 upload_to="notes/%Y/%m/%j/",
61 blank=True, null=True)
62 pdf_file = models.FileField(
64 upload_to="notes/%Y/%m/%j/",
65 blank=True, null=True)
67 ## post gdrive conversion data
68 embed_url = models.URLField(max_length=1024, blank=True, null=True)
69 download_url = models.URLField(max_length=1024, blank=True, null=True)
70 # for word processor documents
71 html = models.TextField(blank=True, null=True)
72 text = models.TextField(blank=True, null=True)
74 # if True, NEVER show this file
75 draft = models.BooleanField(default=False)
79 """ Sort files by most recent first """
80 ordering = ['-uploaded_at']
83 def __unicode__(self):
84 return u"{0}: {1} -- {2}".format(self.file_type, self.name, self.uploaded_at)
86 def save(self, *args, **kwargs):
87 """ override built-in save to ensure contextual self.name """
88 # TODO: If self.name isn't set, generate one based on uploaded_name
89 # if we fail to set the Note.name earlier than this, use the saved filename
91 # only generate a slug if the name has been set, and slug hasn't
92 if not self.slug and self.name:
93 slug = defaultfilters.slugify(self.name)
94 cursor = Note.objects.filter(slug=slug)
95 # If there are no other notes with this slug, then the slug does not need an id
96 if cursor.count() == 0:
99 super(Note, self).save(*args, **kwargs) # generate self.id
100 self.slug = defaultfilters.slugify("%s %s" % (self.name, self.id))
101 super(Note, self).save(*args, **kwargs)
103 # Check if Note.uploaded_at is after Course.updated_at
104 if self.uploaded_at and self.uploaded_at > self.course.updated_at:
105 self.course.updated_at = self.uploaded_at
106 # if it is, update Course.updated_at
109 super(Note, self).save(*args, **kwargs)
111 def get_absolute_url(self):
112 """ Resolve note url, use 'note' route and slug if slug
113 otherwise use note.id
115 if self.slug is not None:
116 # return a url ending in slug
117 return u"/{0}/{1}/{2}".format(self.course.school.slug, self.course.slug, self.slug)
119 # return a url ending in id
120 return u"/{0}/{1}/{2}".format(self.course.school.slug, self.course.slug, self.id)
122 def sanitize_html(self, save=True):
123 """ if self contains html, find all <a> tags and add target=_blank
125 returns True/False on succ/fail and error or count
127 # build a tag sanitizer
128 def add_attribute_target(tag):
129 tag.attrib['target'] = '_blank'
131 # if no html, return false
133 return False, "Note has no html"
135 _html = fromstring(self.html)
136 a_tags = _html.findall('.//a') # recursively find all a tags in document tree
137 # if there are a tags
139 #apply the add attribute function
140 map(add_attribute_target, a_tags)
144 return True, len(a_tags)
148 class DriveAuth(models.Model):
149 """ stored google drive authentication and refresh token
150 used for interacting with google drive """
152 email = models.EmailField(default=GOOGLE_USER)
153 credentials = models.TextField() # JSON of Oauth2Credential object
154 stored_at = models.DateTimeField(auto_now=True)
158 def get(email=GOOGLE_USER):
159 """ Staticmethod for getting the singleton DriveAuth object """
160 # FIXME: this is untested
161 return DriveAuth.objects.filter(email=email).reverse()[0]
164 def store(self, creds):
165 """ Transform an existing credentials object to a db serialized """
166 self.email = creds.id_token['email']
167 self.credentials = creds.to_json()
171 def transform_to_cred(self):
172 """ take stored credentials and produce a Credentials object """
173 return Credentials.new_from_json(self.credentials)
176 def __unicode__(self):
177 return u'Gdrive auth for %s created/updated at %s' % \
178 (self.email, self.stored_at)