MIGRATION add Note.draft and set it to True until user submits form, closes #142
[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         ('ppt', 'Powerpoint'),
37         (UNKNOWN_FILE, 'Unknown file'),
38     )
39
40     course          = models.ForeignKey(Course)
41     # Tagging system
42     tags            = TaggableManager(blank=True)
43
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)
50
51     file_type       = models.CharField(max_length=15,
52                             choices=FILE_TYPE_CHOICES,
53                             default=UNKNOWN_FILE,
54                             blank=True, null=True)
55
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(
59                             storage=fs,
60                             upload_to="notes/%Y/%m/%j/",
61                             blank=True, null=True)
62     pdf_file       = models.FileField(
63                             storage=fs,
64                             upload_to="notes/%Y/%m/%j/",
65                             blank=True, null=True)
66
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)
73
74     # if True, NEVER show this file
75     draft           = models.BooleanField(default=False)
76
77
78     class Meta:
79         """ Sort files by most recent first """
80         ordering = ['-uploaded_at']
81
82
83     def __unicode__(self):
84         return u"{0}: {1} -- {2}".format(self.file_type, self.name, self.uploaded_at)
85
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
90
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:
97                 self.slug = slug
98             else:
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)
102
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
107             self.course.save()
108
109         super(Note, self).save(*args, **kwargs)
110
111     def get_absolute_url(self):
112         """ Resolve note url, use 'note' route and slug if slug
113             otherwise use note.id
114         """
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)
118         else:
119             # return a url ending in id
120             return u"/{0}/{1}/{2}".format(self.course.school.slug, self.course.slug, self.id)
121
122     def sanitize_html(self, save=True):
123         """ if self contains html, find all <a> tags and add target=_blank 
124             takes self
125             returns True/False on succ/fail and error or count
126         """
127         # build a tag sanitizer
128         def add_attribute_target(tag):
129             tag.attrib['target'] = '_blank'
130
131         # if no html, return false
132         if not self.html:
133             return False, "Note has no html"
134
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
138         if a_tags > 1:
139             #apply the add attribute function
140             map(add_attribute_target, a_tags)
141             self.html = _html
142             if save:
143                 self.save()
144             return True, len(a_tags)
145
146
147
148 class DriveAuth(models.Model):
149     """ stored google drive authentication and refresh token
150         used for interacting with google drive """
151
152     email = models.EmailField(default=GOOGLE_USER)
153     credentials = models.TextField() # JSON of Oauth2Credential object
154     stored_at = models.DateTimeField(auto_now=True)
155
156
157     @staticmethod
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]
162
163
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()
168         self.save()
169
170
171     def transform_to_cred(self):
172         """ take stored credentials and produce a Credentials object """
173         return Credentials.new_from_json(self.credentials)
174
175
176     def __unicode__(self):
177         return u'Gdrive auth for %s created/updated at %s' % \
178                     (self.email, self.stored_at)