98414d729f8f29c6339c77b2c3adfeaac974c815
[mediagoblin.git] / mediagoblin / db / mixin.py
1 # GNU MediaGoblin -- federated, autonomous media hosting
2 # Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
3 #
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License as published by
6 # the Free Software Foundation, either version 3 of the License, or
7 # (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU Affero General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17 """
18 This module contains some Mixin classes for the db objects.
19
20 A bunch of functions on the db objects are really more like
21 "utility functions": They could live outside the classes
22 and be called "by hand" passing the appropiate reference.
23 They usually only use the public API of the object and
24 rarely use database related stuff.
25
26 These functions now live here and get "mixed in" into the
27 real objects.
28 """
29
30 from uuid import uuid4
31
32 from werkzeug.utils import cached_property
33
34 from mediagoblin import mg_globals
35 from mediagoblin.auth import lib as auth_lib
36 from mediagoblin.media_types import get_media_managers, FileTypeNotSupported
37 from mediagoblin.tools import common, licenses
38 from mediagoblin.tools.text import cleaned_markdown_conversion
39 from mediagoblin.tools.url import slugify
40
41
42 class UserMixin(object):
43 def check_login(self, password):
44 """
45 See if a user can login with this password
46 """
47 return auth_lib.bcrypt_check_password(
48 password, self.pw_hash)
49
50 @property
51 def bio_html(self):
52 return cleaned_markdown_conversion(self.bio)
53
54
55 class MediaEntryMixin(object):
56 def generate_slug(self):
57 """
58 Generate a unique slug for this MediaEntry.
59
60 This one does not *force* slugs, but usually it will probably result
61 in a niceish one.
62
63 The end *result* of the algorithm will result in these resolutions for
64 these situations:
65 - If we have a slug, make sure it's clean and sanitized, and if it's
66 unique, we'll use that.
67 - If we have a title, slugify it, and if it's unique, we'll use that.
68 - If we can't get any sort of thing that looks like it'll be a useful
69 slug out of a title or an existing slug, bail, and don't set the
70 slug at all. Don't try to create something just because. Make
71 sure we have a reasonable basis for a slug first.
72 - If we have a reasonable basis for a slug (either based on existing
73 slug or slugified title) but it's not unique, first try appending
74 the entry's id, if that exists
75 - If that doesn't result in something unique, tack on some randomly
76 generated bits until it's unique. That'll be a little bit of junk,
77 but at least it has the basis of a nice slug.
78 """
79 # import this here due to a cyclic import issue
80 # (db.models -> db.mixin -> db.util -> db.models)
81 from mediagoblin.db.util import check_media_slug_used
82
83 #Is already a slug assigned? Check if it is valid
84 if self.slug:
85 self.slug = slugify(self.slug)
86
87 # otherwise, try to use the title.
88 elif self.title:
89 # assign slug based on title
90 self.slug = slugify(self.title)
91
92 # We don't want any empty string slugs
93 if self.slug == u"":
94 self.slug = None
95
96 # Do we have anything at this point?
97 # If not, we're not going to get a slug
98 # so just return... we're not going to force one.
99 if not self.slug:
100 return # giving up!
101
102 # Otherwise, let's see if this is unique.
103 if check_media_slug_used(self.uploader, self.slug, self.id):
104 # It looks like it's being used... lame.
105
106 # Can we just append the object's id to the end?
107 if self.id:
108 slug_with_id = u"%s-%s" % (self.slug, self.id)
109 if not check_media_slug_used(self.uploader,
110 slug_with_id, self.id):
111 self.slug = slug_with_id
112 return # success!
113
114 # okay, still no success;
115 # let's whack junk on there till it's unique.
116 self.slug += '-'
117 while check_media_slug_used(self.uploader, self.slug, self.id):
118 self.slug += uuid4().hex[:4]
119
120 @property
121 def description_html(self):
122 """
123 Rendered version of the description, run through
124 Markdown and cleaned with our cleaning tool.
125 """
126 return cleaned_markdown_conversion(self.description)
127
128 def get_display_media(self, media_map,
129 fetch_order=common.DISPLAY_IMAGE_FETCHING_ORDER):
130 """
131 Find the best media for display.
132
133 Args:
134 - media_map: a dict like
135 {u'image_size': [u'dir1', u'dir2', u'image.jpg']}
136 - fetch_order: the order we should try fetching images in
137
138 Returns:
139 (media_size, media_path)
140 """
141 media_sizes = media_map.keys()
142
143 for media_size in common.DISPLAY_IMAGE_FETCHING_ORDER:
144 if media_size in media_sizes:
145 return media_map[media_size]
146
147 def main_mediafile(self):
148 pass
149
150 @property
151 def slug_or_id(self):
152 return (self.slug or self.id)
153
154
155 def url_for_self(self, urlgen, **extra_args):
156 """
157 Generate an appropriate url for ourselves
158
159 Use a slug if we have one, else use our 'id'.
160 """
161 uploader = self.get_uploader
162
163 return urlgen(
164 'mediagoblin.user_pages.media_home',
165 user=uploader.username,
166 media=self.slug_or_id,
167 **extra_args)
168
169 @property
170 def thumb_url(self):
171 """Return the thumbnail URL (for usage in templates)
172 Will return either the real thumbnail or a default fallback icon."""
173 # TODO: implement generic fallback in case MEDIA_MANAGER does
174 # not specify one?
175 if u'thumb' in self.media_files:
176 thumb_url = mg_globals.app.public_store.file_url(
177 self.media_files[u'thumb'])
178 else:
179 # No thumbnail in media available. Get the media's
180 # MEDIA_MANAGER for the fallback icon and return static URL
181 # Raises FileTypeNotSupported in case no such manager is enabled
182 manager = self.media_manager
183 thumb_url = mg_globals.app.staticdirector(manager[u'default_thumb'])
184 return thumb_url
185
186 @cached_property
187 def media_manager(self):
188 """Returns the MEDIA_MANAGER of the media's media_type
189
190 Raises FileTypeNotSupported in case no such manager is enabled
191 """
192 # TODO, we should be able to make this a simple lookup rather
193 # than iterating through all media managers.
194 for media_type, manager in get_media_managers():
195 if media_type == self.media_type:
196 return manager
197 # Not found? Then raise an error
198 raise FileTypeNotSupported(
199 "MediaManager not in enabled types. Check media_types in config?")
200
201 def get_fail_exception(self):
202 """
203 Get the exception that's appropriate for this error
204 """
205 if self.fail_error:
206 return common.import_component(self.fail_error)
207
208 def get_license_data(self):
209 """Return license dict for requested license"""
210 return licenses.get_license_by_url(self.license or "")
211
212 def exif_display_iter(self):
213 from mediagoblin.tools.exif import USEFUL_TAGS
214
215 if not self.media_data:
216 return
217 exif_all = self.media_data.get("exif_all")
218
219 for key in USEFUL_TAGS:
220 if key in exif_all:
221 yield key, exif_all[key]
222
223
224 class MediaCommentMixin(object):
225 @property
226 def content_html(self):
227 """
228 the actual html-rendered version of the comment displayed.
229 Run through Markdown and the HTML cleaner.
230 """
231 return cleaned_markdown_conversion(self.content)
232
233
234 class CollectionMixin(object):
235 def generate_slug(self):
236 # import this here due to a cyclic import issue
237 # (db.models -> db.mixin -> db.util -> db.models)
238 from mediagoblin.db.util import check_collection_slug_used
239
240 self.slug = slugify(self.title)
241
242 duplicate = check_collection_slug_used(mg_globals.database,
243 self.creator, self.slug, self.id)
244
245 if duplicate:
246 if self.id is not None:
247 self.slug = u"%s-%s" % (self.id, self.slug)
248 else:
249 self.slug = None
250
251 @property
252 def description_html(self):
253 """
254 Rendered version of the description, run through
255 Markdown and cleaned with our cleaning tool.
256 """
257 return cleaned_markdown_conversion(self.description)
258
259 @property
260 def slug_or_id(self):
261 return (self.slug or self.id)
262
263 def url_for_self(self, urlgen, **extra_args):
264 """
265 Generate an appropriate url for ourselves
266
267 Use a slug if we have one, else use our 'id'.
268 """
269 creator = self.get_creator
270
271 return urlgen(
272 'mediagoblin.user_pages.user_collection',
273 user=creator.username,
274 collection=self.slug_or_id,
275 **extra_args)
276
277
278 class CollectionItemMixin(object):
279 @property
280 def note_html(self):
281 """
282 the actual html-rendered version of the note displayed.
283 Run through Markdown and the HTML cleaner.
284 """
285 return cleaned_markdown_conversion(self.note)