Refactor generate_slug into a mixin.
[mediagoblin.git] / mediagoblin / db / mixin.py
CommitLineData
f42e49c3 1# GNU MediaGoblin -- federated, autonomous media hosting
7f4ebeed 2# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
f42e49c3
E
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"""
18This module contains some Mixin classes for the db objects.
19
20A bunch of functions on the db objects are really more like
21"utility functions": They could live outside the classes
22and be called "by hand" passing the appropiate reference.
23They usually only use the public API of the object and
24rarely use database related stuff.
25
26These functions now live here and get "mixed in" into the
27real objects.
28"""
29
a81082fc 30import uuid
72bb46c7 31
5f8b4ae8
SS
32from werkzeug.utils import cached_property
33
814334f6 34from mediagoblin import mg_globals
f42e49c3 35from mediagoblin.auth import lib as auth_lib
5f8b4ae8 36from mediagoblin.media_types import get_media_managers, FileTypeNotSupported
17c23e15 37from mediagoblin.tools import common, licenses
e61ab099 38from mediagoblin.tools.text import cleaned_markdown_conversion
814334f6 39from mediagoblin.tools.url import slugify
f42e49c3
E
40
41
42class 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
e61ab099
E
50 @property
51 def bio_html(self):
52 return cleaned_markdown_conversion(self.bio)
53
f42e49c3 54
29c65044 55class GenerateSlugMixin(object):
814334f6 56 def generate_slug(self):
88de830f 57 """
98587109
CAW
58 Generate a unique slug for this MediaEntry.
59
88de830f
CAW
60 This one does not *force* slugs, but usually it will probably result
61 in a niceish one.
62
98587109
CAW
63 The end *result* of the algorithm will result in these resolutions for
64 these situations:
88de830f
CAW
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 """
66d9f1b2
SS
79 #Is already a slug assigned? Check if it is valid
80 if self.slug:
81 self.slug = slugify(self.slug)
b1126f71 82
88de830f 83 # otherwise, try to use the title.
66d9f1b2 84 elif self.title:
88de830f 85 # assign slug based on title
66d9f1b2 86 self.slug = slugify(self.title)
b1126f71 87
b0118957
CAW
88 # We don't want any empty string slugs
89 if self.slug == u"":
90 self.slug = None
91
88de830f
CAW
92 # Do we have anything at this point?
93 # If not, we're not going to get a slug
94 # so just return... we're not going to force one.
95 if not self.slug:
96 return # giving up!
b1126f71 97
88de830f 98 # Otherwise, let's see if this is unique.
29c65044 99 if self.check_slug_used(self.slug):
88de830f 100 # It looks like it's being used... lame.
b1126f71 101
88de830f
CAW
102 # Can we just append the object's id to the end?
103 if self.id:
98587109 104 slug_with_id = u"%s-%s" % (self.slug, self.id)
29c65044 105 if not self.check_slug_used(slug_with_id):
88de830f
CAW
106 self.slug = slug_with_id
107 return # success!
b1126f71 108
88de830f
CAW
109 # okay, still no success;
110 # let's whack junk on there till it's unique.
a81082fc
CAW
111 self.slug += '-' + uuid.uuid4().hex[:4]
112 # keep going if necessary!
29c65044 113 while self.check_slug_used(self.slug):
a81082fc 114 self.slug += uuid.uuid4().hex[:4]
814334f6 115
29c65044
E
116
117class MediaEntryMixin(GenerateSlugMixin):
118 def check_slug_used(self, slug):
119 # import this here due to a cyclic import issue
120 # (db.models -> db.mixin -> db.util -> db.models)
121 from mediagoblin.db.util import check_media_slug_used
122
123 return check_media_slug_used(self.uploader, slug, self.id)
124
1e72e075
E
125 @property
126 def description_html(self):
127 """
128 Rendered version of the description, run through
129 Markdown and cleaned with our cleaning tool.
130 """
131 return cleaned_markdown_conversion(self.description)
132
e77df64f
CAW
133 def get_display_media(self):
134 """Find the best media for display.
f42e49c3 135
53024776 136 We try checking self.media_manager.fetching_order if it exists to
e77df64f 137 pull down the order.
f42e49c3
E
138
139 Returns:
ddbf6af1
CAW
140 (media_size, media_path)
141 or, if not found, None.
e77df64f 142
f42e49c3 143 """
ddbf6af1 144 fetch_order = self.media_manager.get("media_fetch_order")
f42e49c3 145
ddbf6af1
CAW
146 # No fetching order found? well, give up!
147 if not fetch_order:
148 return None
149
150 media_sizes = self.media_files.keys()
151
152 for media_size in fetch_order:
f42e49c3 153 if media_size in media_sizes:
ddbf6af1 154 return media_size, self.media_files[media_size]
f42e49c3
E
155
156 def main_mediafile(self):
157 pass
158
3e907d55
E
159 @property
160 def slug_or_id(self):
7de20e52
CAW
161 if self.slug:
162 return self.slug
163 else:
164 return u'id:%s' % self.id
5f8b4ae8 165
cb7ae1e4 166 def url_for_self(self, urlgen, **extra_args):
f42e49c3
E
167 """
168 Generate an appropriate url for ourselves
169
5c2b8486 170 Use a slug if we have one, else use our 'id'.
f42e49c3
E
171 """
172 uploader = self.get_uploader
173
3e907d55
E
174 return urlgen(
175 'mediagoblin.user_pages.media_home',
176 user=uploader.username,
177 media=self.slug_or_id,
178 **extra_args)
f42e49c3 179
2e4ad359
SS
180 @property
181 def thumb_url(self):
182 """Return the thumbnail URL (for usage in templates)
183 Will return either the real thumbnail or a default fallback icon."""
184 # TODO: implement generic fallback in case MEDIA_MANAGER does
185 # not specify one?
186 if u'thumb' in self.media_files:
187 thumb_url = mg_globals.app.public_store.file_url(
188 self.media_files[u'thumb'])
189 else:
df1c4976 190 # No thumbnail in media available. Get the media's
2e4ad359 191 # MEDIA_MANAGER for the fallback icon and return static URL
5f8b4ae8
SS
192 # Raises FileTypeNotSupported in case no such manager is enabled
193 manager = self.media_manager
df1c4976 194 thumb_url = mg_globals.app.staticdirector(manager[u'default_thumb'])
2e4ad359
SS
195 return thumb_url
196
5f8b4ae8
SS
197 @cached_property
198 def media_manager(self):
199 """Returns the MEDIA_MANAGER of the media's media_type
200
201 Raises FileTypeNotSupported in case no such manager is enabled
202 """
203 # TODO, we should be able to make this a simple lookup rather
204 # than iterating through all media managers.
205 for media_type, manager in get_media_managers():
206 if media_type == self.media_type:
207 return manager
208 # Not found? Then raise an error
209 raise FileTypeNotSupported(
210 "MediaManager not in enabled types. Check media_types in config?")
211
f42e49c3
E
212 def get_fail_exception(self):
213 """
214 Get the exception that's appropriate for this error
215 """
51eb0267
JW
216 if self.fail_error:
217 return common.import_component(self.fail_error)
17c23e15
AW
218
219 def get_license_data(self):
220 """Return license dict for requested license"""
138a18fd 221 return licenses.get_license_by_url(self.license or "")
feba5c52 222
5bad26bc 223 def exif_display_iter(self):
7b82f56b
E
224 from mediagoblin.tools.exif import USEFUL_TAGS
225
5bad26bc
E
226 if not self.media_data:
227 return
228 exif_all = self.media_data.get("exif_all")
229
230 for key in USEFUL_TAGS:
231 if key in exif_all:
232 yield key, exif_all[key]
233
feba5c52
E
234
235class MediaCommentMixin(object):
236 @property
237 def content_html(self):
238 """
239 the actual html-rendered version of the comment displayed.
240 Run through Markdown and the HTML cleaner.
241 """
242 return cleaned_markdown_conversion(self.content)
be5be115
AW
243
244
245class CollectionMixin(object):
246 def generate_slug(self):
247 # import this here due to a cyclic import issue
248 # (db.models -> db.mixin -> db.util -> db.models)
249 from mediagoblin.db.util import check_collection_slug_used
250
251 self.slug = slugify(self.title)
252
253 duplicate = check_collection_slug_used(mg_globals.database,
254 self.creator, self.slug, self.id)
255
256 if duplicate:
257 if self.id is not None:
258 self.slug = u"%s-%s" % (self.id, self.slug)
259 else:
260 self.slug = None
261
262 @property
263 def description_html(self):
264 """
265 Rendered version of the description, run through
266 Markdown and cleaned with our cleaning tool.
267 """
268 return cleaned_markdown_conversion(self.description)
269
270 @property
271 def slug_or_id(self):
5c2b8486 272 return (self.slug or self.id)
be5be115
AW
273
274 def url_for_self(self, urlgen, **extra_args):
275 """
276 Generate an appropriate url for ourselves
277
5c2b8486 278 Use a slug if we have one, else use our 'id'.
be5be115
AW
279 """
280 creator = self.get_creator
281
282 return urlgen(
256f816f 283 'mediagoblin.user_pages.user_collection',
be5be115
AW
284 user=creator.username,
285 collection=self.slug_or_id,
286 **extra_args)
287
6d1e55b2 288
be5be115
AW
289class CollectionItemMixin(object):
290 @property
291 def note_html(self):
292 """
293 the actual html-rendered version of the note displayed.
294 Run through Markdown and the HTML cleaner.
295 """
296 return cleaned_markdown_conversion(self.note)