Merge remote-tracking branch 'refs/remotes/tilly-q/ticket-874' into mergetest
[mediagoblin.git] / mediagoblin / plugins / archivalook / tools.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 from mediagoblin.db.models import MediaEntry, User
17 from mediagoblin.plugins.archivalook.models import FeaturedMedia
18 from mediagoblin.tools.translate import lazy_pass_to_ugettext as _
19 from mediagoblin.plugins.archivalook.models import FeaturedMedia
20
21 def get_media_entry_from_uploader_slug(uploader_username, slug):
22 """
23 Accepts two strings and searches to see if those strings identify a
24 MediaEntry
25
26 :param uploader_username A string representing the User.username
27 of the user who uploaded a piece of
28 media.
29 :param slug A string representing the slug of a
30 piece of media
31
32 :returns media A MediaEntry object or None if no entry
33 matches the specifications.
34 """
35 uploader = User.query.filter(
36 User.username == uploader_username).first()
37 media = MediaEntry.query.filter(
38 MediaEntry.get_uploader == uploader ).filter(
39 MediaEntry.slug == slug).first()
40 return media
41
42
43 def parse_url(url):
44 """
45 A simple helper function that extracts the uploader and slug from a full url
46
47 :param url A string containing the url for a piece
48 of media. This should be in the format
49 of "/u/{user}/m/{media}/"
50
51 :returns (uploader_username, slug) Uploader_username is a unicode string
52 representing the username of the user
53 who uploaded the piece of media, slug is
54 the media entry's url slug.
55 """
56 url = unicode(url)
57 u_end, m_start, m_end, end = (url.find('/u/') + 3,
58 url.find('/m/'),
59 url.find('/m/') + 3,
60 url.rfind('/'))
61
62 uploader_username = url[u_end:m_start].strip()
63 slug = url[m_end:end].strip()
64
65 return uploader_username, slug
66
67
68 def split_featured_media_list(featured_media):
69 """
70 This script is part of processing post request on the /mod/feature-media/
71 page. Post requests on these pages will only include the textbox, so this
72 script accepts the textbox's contents as its parameter.
73
74 :parameter featured_media A string from a submitted
75 textarea within the post request
76 on /mod/feature-media/
77
78 :returns all_featured_media A dictionary of the format
79 MediaEntry : 'string'
80 where MediaEntry is a featured
81 piece of media and 'string' is
82 a string representation of its
83 display type (primary, secondary
84 or tertiary)
85 """
86
87 featured_media = unicode(featured_media)
88 featured_media_list = featured_media.split("\n")
89 display_type = 0
90 media_already_featured = []
91 all_featured_media = []
92 for line in featured_media_list:
93 if line == '' or line.isspace(): continue
94 elif line.startswith(u'-'):
95 display_type += 1
96 elif display_type <= 0 or display_type > 3: continue
97 else:
98 uploader, slug = parse_url(line)
99 media = get_media_entry_from_uploader_slug(uploader, slug)
100 # Make sure the media entry referenced exists, and has not already
101 # been featured higher up the list
102 if media == None or media in media_already_featured: continue
103 media_already_featured.append(media)
104 all_featured_media.append((media,
105 [None,
106 u'primary',
107 u'secondary',
108 u'tertiary'][display_type]))
109
110 return all_featured_media
111
112
113 def create_featured_media_textbox():
114 """
115 This script searches through the database of which media is featured and
116 returns a string of each entry in the proper format for use in the
117 /mod/feature-media/ page. This string will be used as the default text in
118 the textbox on that page.
119 """
120
121 primaries = FeaturedMedia.query.order_by(
122 FeaturedMedia.order.asc()).filter(
123 FeaturedMedia.display_type == u'primary').all()
124 secondaries = FeaturedMedia.query.order_by(
125 FeaturedMedia.order.asc()).filter(
126 FeaturedMedia.display_type == u'secondary').all()
127 tertiaries = FeaturedMedia.query.order_by(
128 FeaturedMedia.order.asc()).filter(
129 FeaturedMedia.display_type == u'tertiary').all()
130 output_text = u''
131 for display_type, feature_list in [
132 (_(u'Primary'),primaries),
133 (_(u'Secondary'),secondaries),
134 (_(u'Tertiary'),tertiaries)]:
135 output_text += _(
136 u"""-----------{display_type}-Features---------------------------
137 """).format(display_type=display_type)
138 for feature in feature_list:
139 media_entry = feature.media_entry
140 output_text += u'/u/{uploader_username}/m/{media_slug}/\n'.format(
141 uploader_username = media_entry.get_uploader.username,
142 media_slug = media_entry.slug)
143
144
145 return output_text
146
147 def automatically_add_new_feature(media_entry):
148 """
149 This function automates the addition of a new feature. New features will be
150 placed at the top of the feature stack as 'primary' features. All of the
151 current features are demoted one step to make room.
152
153 :param media_entry :type mediagoblin.db.MediaEntry
154 The media entry that will been
155 featured which this function
156 targets
157 """
158 # Set variables to determine which media entries should be pushed down to
159 # maintain the correct number of primary & secondary featured media. At this
160 # point the program assumes that there should be 1 primary feature and 2
161 # secondary features, but in the future this should be a variable editable
162 # by the site admin.
163 too_many_primaries = FeaturedMedia.query.filter(
164 FeaturedMedia.display_type==u'primary').count() >= 1
165 too_many_secondaries = FeaturedMedia.query.filter(
166 FeaturedMedia.display_type==u'secondary').count() >= 2
167 featured_first_to_last = FeaturedMedia.query.order_by(
168 FeaturedMedia.order.asc()).all()
169
170 for feature in featured_first_to_last:
171 # Some features have the option to demote or promote themselves to a
172 # different display_type, based on their position. But all features move
173 # up and down one step in the stack.
174 if (feature.is_last_of_type() and feature.display_type == u'primary'
175 and too_many_primaries):
176 feature.demote()
177 too_many_primaries = False
178 elif (feature.is_last_of_type() and feature.display_type == u'secondary'
179 and too_many_secondaries):
180 feature.demote()
181 too_many_secondaries = False
182 feature.move_down()
183 feature.save()
184
185 # Create the new feature at the top of the stack.
186 new_feature = FeaturedMedia(
187 media_entry=media_entry,
188 display_type=u"primary",
189 order=0)
190 new_feature.save()
191 return new_feature
192
193 def automatically_remove_feature(media_entry):
194 """
195 This function automates the removal of a feature. All of the features below
196 them are promoted one step to close the gap.
197
198 :param media_entry :type mediagoblin.db.MediaEntry
199 The media entry that will been
200 removed which this function
201 targets
202 """
203 # Get the feature which will be deleted
204 target_feature = FeaturedMedia.query.filter(
205 FeaturedMedia.media_entry_id == media_entry.id).first()
206 # Find out which continuing features will have to be adjusted
207 featured_last_to_first = FeaturedMedia.query.filter(
208 FeaturedMedia.order>target_feature.order).order_by(
209 FeaturedMedia.order.desc()).all()
210
211 for feature in featured_last_to_first:
212 # Maintain the current arrangement of primary/secondary/tertiary
213 # features by moving all the features below the deleted one up on slot
214 # and promoting any features in the proper position.
215 if feature.is_first_of_type():
216 feature.promote()
217 feature.move_up()
218 feature.save()
219
220 # Delete the feature, now that it's space has been closed
221 target_feature.delete()
222
223 def promote_feature(media_entry):
224 """
225 This function takes a current feature and moves it up the stack so that it
226 will be displayed higher up. It swaps the place of the selected feature for
227 the one above it, or if relevant raises the display_type of the feature up
228 one rung (ie. from 'tertiary' to 'secondary')
229
230 :param media_entry :type mediagoblin.db.MediaEntry
231 The media entry that has been
232 featured which this function
233 targets
234 """
235 # Get the FeaturedMedia object
236 target_feature = FeaturedMedia.query.filter(
237 FeaturedMedia.media_entry_id == media_entry.id).first()
238 # Get the first Feature with a lower order than the target
239 above_feature = FeaturedMedia.query.filter(
240 FeaturedMedia.order < target_feature.order).order_by(
241 FeaturedMedia.order.desc()).first()
242 # If the feature is not the uppermost one
243 if above_feature is not None:
244 # Swap the positions of the target feature with the one before it
245 (target_feature.order,
246 above_feature.order) = above_feature.order, target_feature.order
247 (target_feature.display_type,
248 above_feature.display_type) = (above_feature.display_type,
249 target_feature.display_type)
250 above_feature.save()
251 # Change the feature's display type to a more prominent one
252 elif target_feature.display_type == u'secondary':
253 target_feature.display_type = u'primary'
254 elif target_feature.display_type == u'tertiary':
255 target_feature.display_type = u'secondary'
256 target_feature.save()
257
258 def demote_feature(media_entry):
259 """
260 This function takes a current feature and moves it down the stack so that it
261 will be displayed lower down. It swaps the place of the selected feature for
262 the one below it, or if relevant lowers the display_type of the feature down
263 one rung (ie. from 'secondary' to 'tertiary')
264
265 :param media_entry :type mediagoblin.db.MediaEntry
266 The media entry that has been
267 featured which this function
268 targets
269 """
270 # Get the FeaturedMedia object
271 target_feature = FeaturedMedia.query.filter(
272 FeaturedMedia.media_entry_id == media_entry.id).first()
273 # Get the first Feature with a higher order than the target
274 below_feature = FeaturedMedia.query.filter(
275 FeaturedMedia.order > target_feature.order).order_by(
276 FeaturedMedia.order.asc()).first()
277 # If the feature is not the lowest one
278 if below_feature != None:
279 # Swap the positions of the target feature with the one after it
280 (target_feature.order,
281 below_feature.order) = below_feature.order, target_feature.order
282 (target_feature.display_type,
283 below_feature.display_type) = (below_feature.display_type,
284 target_feature.display_type)
285 below_feature.save()
286 # Change the feature's display type to a less prominent one
287 elif target_feature.display_type == u'secondary':
288 target_feature.display_type = u'tertiary'
289 elif target_feature.display_type == u'primary':
290 target_feature.display_type = u'secondary'
291 target_feature.save()
292