process note html for a target=_blank on file upload and adding managment command...
[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     note_file       = models.FileField(
57                             storage=fs,
58                             upload_to="notes/%Y/%m/%j/",
59                             blank=True, null=True)
60
61     ## post gdrive conversion data
62     embed_url       = models.URLField(max_length=1024, blank=True, null=True)
63     download_url    = models.URLField(max_length=1024, blank=True, null=True)
64     # for word processor documents
65     html            = models.TextField(blank=True, null=True)
66     text            = models.TextField(blank=True, null=True)
67
68
69     class Meta:
70         """ Sort files by most recent first """
71         ordering = ['-uploaded_at']
72
73
74     def __unicode__(self):
75         return u"{0}: {1} -- {2}".format(self.file_type, self.name, self.uploaded_at)
76
77     def save(self, *args, **kwargs):
78         """ override built-in save to ensure contextual self.name """
79         # TODO: If self.name isn't set, generate one based on uploaded_name
80         # if we fail to set the Note.name earlier than this, use the saved filename
81
82         # only generate a slug if the name has been set, and slug hasn't
83         if not self.slug and self.name:
84             slug = defaultfilters.slugify(self.name)
85             cursor = Note.objects.filter(slug=slug)
86             # If there are no other notes with this slug, then the slug does not need an id
87             if cursor.count() == 0:
88                 self.slug = slug
89             else:
90                 super(Note, self).save(*args, **kwargs) # generate self.id
91                 self.slug = defaultfilters.slugify("%s %s" % (self.name, self.id))
92             super(Note, self).save(*args, **kwargs)
93
94         # Check if Note.uploaded_at is after Course.updated_at
95         if self.uploaded_at and self.uploaded_at > self.course.updated_at:
96             self.course.updated_at = self.uploaded_at
97             # if it is, update Course.updated_at
98             self.course.save()
99
100         super(Note, self).save(*args, **kwargs)
101
102     def get_absolute_url(self):
103         """ Resolve note url, use 'note' route and slug if slug
104             otherwise use note.id
105         """
106         if self.slug is not None:
107             # return a url ending in slug
108             return u"/{0}/{1}/{2}".format(self.course.school.slug, self.course.slug, self.slug)
109         else:
110             # return a url ending in id
111             return u"/{0}/{1}/{2}".format(self.course.school.slug, self.course.slug, self.id)
112
113     def sanitize_html(self):
114         """ if self contains html, find all <a> tags and add target=_blank 
115             takes self
116             returns True/False on succ/fail and error or count
117         """
118         # build a tag sanitizer
119         def add_attribute_target(tag):
120             tag.attrib['target'] = '_blank'
121
122         # if no html, return false
123         if not self.html:
124             return False, "Note has no html"
125
126         _html = fromstring(self.html)
127         a_tags = _html.findall('.//a') # recursively find all a tags in document tree
128         # if there are a tags
129         if a_tags > 1:
130             #apply the add attribute function
131             map(add_attribute_target, a_tags)
132             self.html = _html
133             self.save()
134             return True, len(a_tags)
135
136
137
138 class DriveAuth(models.Model):
139     """ stored google drive authentication and refresh token
140         used for interacting with google drive """
141
142     email = models.EmailField(default=GOOGLE_USER)
143     credentials = models.TextField() # JSON of Oauth2Credential object
144     stored_at = models.DateTimeField(auto_now=True)
145
146
147     @staticmethod
148     def get(email=GOOGLE_USER):
149         """ Staticmethod for getting the singleton DriveAuth object """
150         # FIXME: this is untested
151         return DriveAuth.objects.filter(email=email).reverse()[0]
152
153
154     def store(self, creds):
155         """ Transform an existing credentials object to a db serialized """
156         self.email = creds.id_token['email']
157         self.credentials = creds.to_json()
158         self.save()
159
160
161     def transform_to_cred(self):
162         """ take stored credentials and produce a Credentials object """
163         return Credentials.new_from_json(self.credentials)
164
165
166     def __unicode__(self):
167         return u'Gdrive auth for %s created/updated at %s' % \
168                     (self.email, self.stored_at)