Merge branch 'ppt-gdrive' of https://github.com/FinalsClub/karmaworld into ppt-gdrive
[oweals/karmaworld.git] / karmaworld / apps / notes / models.py
1 #!/usr/bin/env python
2 # -*- coding:utf8 -*-
3 # Copyright (C) 2012  FinalsClub Foundation
4
5 """
6     Models for the notes django app.
7     Contains only the minimum for handling files and their representation
8 """
9 import datetime
10
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
18
19 from karmaworld.apps.courses.models import Course
20
21 try:
22     from secrets.drive import GOOGLE_USER
23 except:
24     GOOGLE_USER = u'admin@karmanotes.org'
25
26 fs = FileSystemStorage(location=settings.MEDIA_ROOT)
27
28 class Note(models.Model):
29     """ A django model representing an uploaded file and associated metadata.
30     """
31     UNKNOWN_FILE = '???'
32     FILE_TYPE_CHOICES = (
33         ('doc', 'MS Word compatible file (.doc, .docx, .rtf, .odf)'),
34         ('img', 'Scan or picture of notes'),
35         ('pdf', 'PDF file'),
36         (UNKNOWN_FILE, 'Unknown file'),
37     )
38
39     course          = models.ForeignKey(Course)
40     # Tagging system
41     tags            = TaggableManager(blank=True)
42
43     name            = models.CharField(max_length=255, blank=True, null=True)
44     slug            = models.SlugField(max_length=255, null=True)
45     year            = models.IntegerField(blank=True, null=True, 
46                         default=datetime.datetime.utcnow().year)
47     desc            = models.TextField(max_length=511, blank=True, null=True)
48     uploaded_at     = models.DateTimeField(null=True, default=datetime.datetime.utcnow)
49
50     file_type       = models.CharField(max_length=15,
51                             choices=FILE_TYPE_CHOICES,
52                             default=UNKNOWN_FILE,
53                             blank=True, null=True)
54
55     # Upload files to MEDIA_ROOT/notes/YEAR/MONTH/DAY, 2012/10/30/filename
56     # FIXME: because we are adding files by hand in tasks.py, upload_to is being ignored for media files
57     note_file       = models.FileField(
58                             storage=fs,
59                             upload_to="notes/%Y/%m/%j/",
60                             blank=True, null=True)
61     pdf_file       = models.FileField(
62                             storage=fs,
63                             upload_to="notes/%Y/%m/%j/",
64                             blank=True, null=True)
65
66     ## post gdrive conversion data
67     embed_url       = models.URLField(max_length=1024, blank=True, null=True)
68     download_url    = models.URLField(max_length=1024, blank=True, null=True)
69     # for word processor documents
70     html            = models.TextField(blank=True, null=True)
71     text            = models.TextField(blank=True, null=True)
72
73
74     class Meta:
75         """ Sort files by most recent first """
76         ordering = ['-uploaded_at']
77
78
79     def __unicode__(self):
80         return u"{0}: {1} -- {2}".format(self.file_type, self.name, self.uploaded_at)
81
82     def save(self, *args, **kwargs):
83         """ override built-in save to ensure contextual self.name """
84         # TODO: If self.name isn't set, generate one based on uploaded_name
85         # if we fail to set the Note.name earlier than this, use the saved filename
86
87         # only generate a slug if the name has been set, and slug hasn't
88         if not self.slug and self.name:
89             slug = defaultfilters.slugify(self.name)
90             cursor = Note.objects.filter(slug=slug)
91             # If there are no other notes with this slug, then the slug does not need an id
92             if cursor.count() == 0:
93                 self.slug = slug
94             else:
95                 super(Note, self).save(*args, **kwargs) # generate self.id
96                 self.slug = defaultfilters.slugify("%s %s" % (self.name, self.id))
97             super(Note, self).save(*args, **kwargs)
98
99         # Check if Note.uploaded_at is after Course.updated_at
100         if self.uploaded_at and self.uploaded_at > self.course.updated_at:
101             self.course.updated_at = self.uploaded_at
102             # if it is, update Course.updated_at
103             self.course.save()
104
105         super(Note, self).save(*args, **kwargs)
106
107     def get_absolute_url(self):
108         """ Resolve note url, use 'note' route and slug if slug
109             otherwise use note.id
110         """
111         if self.slug is not None:
112             # return a url ending in slug
113             return u"/{0}/{1}/{2}".format(self.course.school.slug, self.course.slug, self.slug)
114         else:
115             # return a url ending in id
116             return u"/{0}/{1}/{2}".format(self.course.school.slug, self.course.slug, self.id)
117
118     def sanitize_html(self, save=True):
119         """ if self contains html, find all <a> tags and add target=_blank 
120             takes self
121             returns True/False on succ/fail and error or count
122         """
123         # build a tag sanitizer
124         def add_attribute_target(tag):
125             tag.attrib['target'] = '_blank'
126
127         # if no html, return false
128         if not self.html:
129             return False, "Note has no html"
130
131         _html = fromstring(self.html)
132         a_tags = _html.findall('.//a') # recursively find all a tags in document tree
133         # if there are a tags
134         if a_tags > 1:
135             #apply the add attribute function
136             map(add_attribute_target, a_tags)
137             self.html = _html
138             if save:
139                 self.save()
140             return True, len(a_tags)
141
142
143
144 class DriveAuth(models.Model):
145     """ stored google drive authentication and refresh token
146         used for interacting with google drive """
147
148     email = models.EmailField(default=GOOGLE_USER)
149     credentials = models.TextField() # JSON of Oauth2Credential object
150     stored_at = models.DateTimeField(auto_now=True)
151
152
153     @staticmethod
154     def get(email=GOOGLE_USER):
155         """ Staticmethod for getting the singleton DriveAuth object """
156         # FIXME: this is untested
157         return DriveAuth.objects.filter(email=email).reverse()[0]
158
159
160     def store(self, creds):
161         """ Transform an existing credentials object to a db serialized """
162         self.email = creds.id_token['email']
163         self.credentials = creds.to_json()
164         self.save()
165
166
167     def transform_to_cred(self):
168         """ take stored credentials and produce a Credentials object """
169         return Credentials.new_from_json(self.credentials)
170
171
172     def __unicode__(self):
173         return u'Gdrive auth for %s created/updated at %s' % \
174                     (self.email, self.stored_at)