Merge lp:~elachuni/ubuntu-webcatalog/lazy-screenshots into lp:ubuntu-webcatalog
- lazy-screenshots
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Anthony Lenton | ||||
Approved revision: | 107 | ||||
Merged at revision: | 106 | ||||
Proposed branch: | lp:~elachuni/ubuntu-webcatalog/lazy-screenshots | ||||
Merge into: | lp:ubuntu-webcatalog | ||||
Diff against target: |
760 lines (+302/-135) 15 files modified
django_project/config/main.cfg (+3/-1) src/webcatalog/admin.py (+12/-0) src/webcatalog/api/urls.py (+8/-7) src/webcatalog/management/commands/import_app_install_data.py (+0/-25) src/webcatalog/static/css/carousel.css (+13/-30) src/webcatalog/static/js/carousel.js (+14/-13) src/webcatalog/templates/webcatalog/application_detail.html (+0/-4) src/webcatalog/templates/webcatalog/application_screenshots.html (+25/-0) src/webcatalog/templates/webcatalog/screenshot_widget.html (+67/-23) src/webcatalog/tests/test_commands.py (+0/-30) src/webcatalog/tests/test_utilities.py (+34/-0) src/webcatalog/tests/test_views.py (+89/-2) src/webcatalog/urls.py (+2/-0) src/webcatalog/utilities.py (+22/-0) src/webcatalog/views.py (+13/-0) |
||||
To merge this branch: | bzr merge lp:~elachuni/ubuntu-webcatalog/lazy-screenshots | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Danny Tamez (community) | Approve | ||
Review via email: mp+102602@code.launchpad.net |
Commit message
Made screenshots load on demand when the details for an app are requested, instead of preloading them all when the import_
Description of the change
This branch makes screenshots load on demand when the details for an app are requested, instead of preloading them all when the import_
The screenshots carousel widget was improved to fetch screenshots for an app via AJAX. The following cases were checked:
a) 2+ screenshots: The first screenshot is displayed by default, but a carousel widget is added via JS on page load.
b) 1 screenshots: The screenshot is included statically
c) 0 screenshots: A link to screenshots.
While I was there I added caching the caching middleware to the dev environment and excepted all of the api calls, so that we can enable caching on staging and prod too.
Preview Diff
1 | === modified file 'django_project/config/main.cfg' | |||
2 | --- django_project/config/main.cfg 2012-03-27 20:43:10 +0000 | |||
3 | +++ django_project/config/main.cfg 2012-04-18 22:40:24 +0000 | |||
4 | @@ -27,12 +27,14 @@ | |||
5 | 27 | pgtools | 27 | pgtools |
6 | 28 | login_url = /openid/login/ | 28 | login_url = /openid/login/ |
7 | 29 | managers = %(admins)s | 29 | managers = %(admins)s |
9 | 30 | middleware_classes = django.middleware.common.CommonMiddleware | 30 | middleware_classes = django.middleware.cache.UpdateCacheMiddleware |
10 | 31 | django.middleware.common.CommonMiddleware | ||
11 | 31 | django.contrib.sessions.middleware.SessionMiddleware | 32 | django.contrib.sessions.middleware.SessionMiddleware |
12 | 32 | django.middleware.csrf.CsrfViewMiddleware | 33 | django.middleware.csrf.CsrfViewMiddleware |
13 | 33 | django.contrib.auth.middleware.AuthenticationMiddleware | 34 | django.contrib.auth.middleware.AuthenticationMiddleware |
14 | 34 | django.contrib.messages.middleware.MessageMiddleware | 35 | django.contrib.messages.middleware.MessageMiddleware |
15 | 35 | webcatalog.middleware.exception.LogExceptionMiddleware | 36 | webcatalog.middleware.exception.LogExceptionMiddleware |
16 | 37 | django.middleware.cache.FetchFromCacheMiddleware | ||
17 | 36 | fixture_dirs = | 38 | fixture_dirs = |
18 | 37 | 39 | ||
19 | 38 | secret_key = eepu9Av5ixage9ahhodovahfaiFoorodahf6keip3eichaeW9f | 40 | secret_key = eepu9Av5ixage9ahhodovahfaiFoorodahf6keip3eichaeW9f |
20 | 39 | 41 | ||
21 | === modified file 'src/webcatalog/admin.py' | |||
22 | --- src/webcatalog/admin.py 2012-03-20 22:23:31 +0000 | |||
23 | +++ src/webcatalog/admin.py 2012-04-18 22:40:24 +0000 | |||
24 | @@ -24,6 +24,7 @@ | |||
25 | 24 | from django.contrib import admin | 24 | from django.contrib import admin |
26 | 25 | from webcatalog.models import ( | 25 | from webcatalog.models import ( |
27 | 26 | Application, | 26 | Application, |
28 | 27 | ApplicationMedia, | ||
29 | 27 | Department, | 28 | Department, |
30 | 28 | DistroSeries, | 29 | DistroSeries, |
31 | 29 | Exhibit, | 30 | Exhibit, |
32 | @@ -57,7 +58,18 @@ | |||
33 | 57 | class DepartmentAdmin(admin.ModelAdmin): | 58 | class DepartmentAdmin(admin.ModelAdmin): |
34 | 58 | prepopulated_fields = {"slug": ("name",)} | 59 | prepopulated_fields = {"slug": ("name",)} |
35 | 59 | 60 | ||
36 | 61 | |||
37 | 62 | class ApplicationMediaAdmin(admin.ModelAdmin): | ||
38 | 63 | search_fields = ('application__package_name',) | ||
39 | 64 | list_filter = ('application__distroseries',) | ||
40 | 65 | list_display = ('__unicode__', 'distroseries', 'url') | ||
41 | 66 | raw_id_fields = ('application',) | ||
42 | 67 | |||
43 | 68 | def distroseries(self, obj): | ||
44 | 69 | return obj.application.distroseries | ||
45 | 70 | |||
46 | 60 | admin.site.register(Application, ApplicationAdmin) | 71 | admin.site.register(Application, ApplicationAdmin) |
47 | 72 | admin.site.register(ApplicationMedia, ApplicationMediaAdmin) | ||
48 | 61 | admin.site.register(Department, DepartmentAdmin) | 73 | admin.site.register(Department, DepartmentAdmin) |
49 | 62 | admin.site.register(DistroSeries) | 74 | admin.site.register(DistroSeries) |
50 | 63 | admin.site.register(Exhibit, ExhibitAdmin) | 75 | admin.site.register(Exhibit, ExhibitAdmin) |
51 | 64 | 76 | ||
52 | === modified file 'src/webcatalog/api/urls.py' | |||
53 | --- src/webcatalog/api/urls.py 2012-01-06 17:54:47 +0000 | |||
54 | +++ src/webcatalog/api/urls.py 2012-04-18 22:40:24 +0000 | |||
55 | @@ -15,6 +15,7 @@ | |||
56 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
57 | 16 | 16 | ||
58 | 17 | from django.conf.urls.defaults import url, patterns | 17 | from django.conf.urls.defaults import url, patterns |
59 | 18 | from django.views.decorators.cache import never_cache | ||
60 | 18 | 19 | ||
61 | 19 | from piston.resource import Resource | 20 | from piston.resource import Resource |
62 | 20 | from webcatalog.api.handlers import ( | 21 | from webcatalog.api.handlers import ( |
63 | @@ -34,13 +35,13 @@ | |||
64 | 34 | super(CSRFExemptResource, self).__init__(handler, authentication) | 35 | super(CSRFExemptResource, self).__init__(handler, authentication) |
65 | 35 | self.csrf_exempt = True | 36 | self.csrf_exempt = True |
66 | 36 | 37 | ||
74 | 37 | server_status_resource = Resource(handler=ServerStatusHandler) | 38 | server_status_resource = never_cache(Resource(handler=ServerStatusHandler)) |
75 | 38 | list_machines_resource = Resource(handler=ListMachinesHandler, | 39 | list_machines_resource = never_cache(Resource(handler=ListMachinesHandler, |
76 | 39 | authentication=auth) | 40 | authentication=auth)) |
77 | 40 | machine_resource = CSRFExemptResource(handler=MachineHandler, | 41 | machine_resource = never_cache(CSRFExemptResource(handler=MachineHandler, |
78 | 41 | authentication=auth) | 42 | authentication=auth)) |
79 | 42 | packages_resource = CSRFExemptResource(handler=PackagesHandler, | 43 | packages_resource = never_cache(CSRFExemptResource(handler=PackagesHandler, |
80 | 43 | authentication=auth) | 44 | authentication=auth)) |
81 | 44 | 45 | ||
82 | 45 | urlpatterns = patterns('', | 46 | urlpatterns = patterns('', |
83 | 46 | # get status of the service (usually just "ok", might be "read-only") | 47 | # get status of the service (usually just "ok", might be "read-only") |
84 | 47 | 48 | ||
85 | === modified file 'src/webcatalog/management/commands/import_app_install_data.py' | |||
86 | --- src/webcatalog/management/commands/import_app_install_data.py 2012-04-03 17:45:07 +0000 | |||
87 | +++ src/webcatalog/management/commands/import_app_install_data.py 2012-04-18 22:40:24 +0000 | |||
88 | @@ -22,7 +22,6 @@ | |||
89 | 22 | with_statement, | 22 | with_statement, |
90 | 23 | ) | 23 | ) |
91 | 24 | 24 | ||
92 | 25 | import json | ||
93 | 26 | import os | 25 | import os |
94 | 27 | import re | 26 | import re |
95 | 28 | import shutil | 27 | import shutil |
96 | @@ -32,7 +31,6 @@ | |||
97 | 32 | from glob import iglob | 31 | from glob import iglob |
98 | 33 | from optparse import make_option | 32 | from optparse import make_option |
99 | 34 | from tempfile import NamedTemporaryFile | 33 | from tempfile import NamedTemporaryFile |
100 | 35 | from urlparse import urljoin | ||
101 | 36 | 34 | ||
102 | 37 | import apt | 35 | import apt |
103 | 38 | from apt.cache import FetchFailedException | 36 | from apt.cache import FetchFailedException |
104 | @@ -166,27 +164,6 @@ | |||
105 | 166 | 164 | ||
106 | 167 | shutil.rmtree(data_dir) | 165 | shutil.rmtree(data_dir) |
107 | 168 | 166 | ||
108 | 169 | def get_screenshot_urls_from_server(self, app): | ||
109 | 170 | url = urljoin(settings.SCREENSHOTS_BASE_URL, | ||
110 | 171 | '/json/package/{0}'.format(app.package_name)) | ||
111 | 172 | try: | ||
112 | 173 | response = urllib.urlopen(url) | ||
113 | 174 | if response.getcode() == 200: | ||
114 | 175 | data = json.loads(response.read()) | ||
115 | 176 | for entry in data['screenshots']: | ||
116 | 177 | screenshot_url = entry['large_image_url'] | ||
117 | 178 | |||
118 | 179 | self.output("Adding {0} to {1}\n".format( | ||
119 | 180 | screenshot_url, | ||
120 | 181 | app.package_name), 2) | ||
121 | 182 | app.applicationmedia_set.get_or_create( | ||
122 | 183 | media_type='screenshot', | ||
123 | 184 | url=screenshot_url, | ||
124 | 185 | ) | ||
125 | 186 | app.save() | ||
126 | 187 | except (IOError, TypeError): | ||
127 | 188 | pass | ||
128 | 189 | |||
129 | 190 | def get_package_data_for_series(self, package_name, distroseries, | 167 | def get_package_data_for_series(self, package_name, distroseries, |
130 | 191 | working_dir): | 168 | working_dir): |
131 | 192 | install_data_url = self.get_package_uri_for_series( | 169 | install_data_url = self.get_package_uri_for_series( |
132 | @@ -216,7 +193,6 @@ | |||
133 | 216 | app.distroseries = distroseries | 193 | app.distroseries = distroseries |
134 | 217 | app.save() | 194 | app.save() |
135 | 218 | app.update_departments() | 195 | app.update_departments() |
136 | 219 | self.get_screenshot_urls_from_server(app) | ||
137 | 220 | self.add_icon_to_app(app, icon_dir) | 196 | self.add_icon_to_app(app, icon_dir) |
138 | 221 | if self.verbosity > 1: | 197 | if self.verbosity > 1: |
139 | 222 | self.output(u"{0} created.\n".format(app.name).encode( | 198 | self.output(u"{0} created.\n".format(app.name).encode( |
140 | @@ -338,7 +314,6 @@ | |||
141 | 338 | version=version, | 314 | version=version, |
142 | 339 | comment=app_comment, | 315 | comment=app_comment, |
143 | 340 | ) | 316 | ) |
144 | 341 | self.get_screenshot_urls_from_server(app) | ||
145 | 342 | self.fetch_icon_from_extras(app, candidate) | 317 | self.fetch_icon_from_extras(app, candidate) |
146 | 343 | if self.verbosity == 1: | 318 | if self.verbosity == 1: |
147 | 344 | self.output("\n", 1) | 319 | self.output("\n", 1) |
148 | 345 | 320 | ||
149 | === modified file 'src/webcatalog/static/css/carousel.css' | |||
150 | --- src/webcatalog/static/css/carousel.css 2012-04-12 09:55:28 +0000 | |||
151 | +++ src/webcatalog/static/css/carousel.css 2012-04-18 22:40:24 +0000 | |||
152 | @@ -16,7 +16,6 @@ | |||
153 | 16 | z-index: 20; | 16 | z-index: 20; |
154 | 17 | left: 50%; | 17 | left: 50%; |
155 | 18 | bottom: 5px; | 18 | bottom: 5px; |
156 | 19 | margin-left: -35px; | ||
157 | 20 | } | 19 | } |
158 | 21 | .carousel .pagination li { | 20 | .carousel .pagination li { |
159 | 22 | float: left; | 21 | float: left; |
160 | @@ -202,40 +201,24 @@ | |||
161 | 202 | } | 201 | } |
162 | 203 | 202 | ||
163 | 204 | /* Screenshots carousel */ | 203 | /* Screenshots carousel */ |
165 | 205 | .screenshot .carousel-wrapper { | 204 | div.screenshot .carousel-wrapper { |
166 | 206 | height: 190px; | 205 | height: 190px; |
167 | 207 | width: 233px; | 206 | width: 233px; |
168 | 208 | } | 207 | } |
170 | 209 | .screenshot .carousel .pagination li a.active { | 208 | div.screenshot .carousel-wrapper .slide { |
171 | 209 | margin: 0; | ||
172 | 210 | width: 233px; | ||
173 | 211 | } | ||
174 | 212 | div.screenshot .carousel-wrapper .slide img { | ||
175 | 213 | margin: 0 auto; | ||
176 | 214 | display: block | ||
177 | 215 | } | ||
178 | 216 | div.screenshot .carousel .pagination li a.active { | ||
179 | 210 | background-color: #DD4814; | 217 | background-color: #DD4814; |
180 | 211 | } | 218 | } |
208 | 212 | #screenshots-controls { | 219 | div.screenshot .carousel .pagination { |
209 | 213 | width: 70px; | 220 | bottom: 0; |
210 | 214 | height: 32px; | 221 | } |
184 | 215 | margin-left: 94px; | ||
185 | 216 | } | ||
186 | 217 | #screenshots-controls .next, #screenshots-controls .prev { | ||
187 | 218 | background: url(/assets/images/arrow-sliders.png) no-repeat 0 0; | ||
188 | 219 | float: left; | ||
189 | 220 | z-index: 20; | ||
190 | 221 | width: 32px; | ||
191 | 222 | height: 29px; | ||
192 | 223 | } | ||
193 | 224 | #screenshots-controls .next span, #screenshots-controls .prev span { | ||
194 | 225 | position:absolute; | ||
195 | 226 | left: -9999em; | ||
196 | 227 | height: 0; | ||
197 | 228 | width: 0; | ||
198 | 229 | } | ||
199 | 230 | #screenshots-controls .prev { | ||
200 | 231 | background-position:0 0; | ||
201 | 232 | } | ||
202 | 233 | #screenshots-controls .next { | ||
203 | 234 | background-position: -32px 0; | ||
204 | 235 | } | ||
205 | 236 | #screenshots-controls .prev:hover, #screenshots-controls .prev:focus, | ||
206 | 237 | #screenshots-controls .next:hover, #screenshots-controls .next:focus { | ||
207 | 238 | outline: none; | ||
211 | 239 | 222 | ||
212 | 240 | .top-rated-stars { | 223 | .top-rated-stars { |
213 | 241 | position: relative; | 224 | position: relative; |
214 | 242 | 225 | ||
215 | === modified file 'src/webcatalog/static/js/carousel.js' | |||
216 | --- src/webcatalog/static/js/carousel.js 2012-03-08 18:00:09 +0000 | |||
217 | +++ src/webcatalog/static/js/carousel.js 2012-04-18 22:40:24 +0000 | |||
218 | @@ -87,16 +87,21 @@ | |||
219 | 87 | this.gotoSlide(parseInt(targetClass.replace("p-", ""), 10)); | 87 | this.gotoSlide(parseInt(targetClass.replace("p-", ""), 10)); |
220 | 88 | } | 88 | } |
221 | 89 | }, this); | 89 | }, this); |
222 | 90 | // Center pagination | ||
223 | 91 | olNode.setStyle('left', parseInt(( | ||
224 | 92 | parseInt(olNode.get('parentNode').getComputedStyle('width')) - | ||
225 | 93 | parseInt(olNode.getComputedStyle('width'))) / 2) + 'px'); | ||
226 | 90 | }, | 94 | }, |
227 | 91 | generateControls: function() { | 95 | generateControls: function() { |
236 | 92 | var prev = Y.Node.create('<a href="#" class="prev"><span>Previous Slide</span></a>'), | 96 | var controlsContainer = this.get("controlsContainer"); |
237 | 93 | next = Y.Node.create('<a href="#" class="next"><span>Next Slide</span></a>'), | 97 | if (controlsContainer) { |
238 | 94 | controlsContainer = this.get("controlsContainer"); | 98 | var prev = Y.Node.create('<a href="#" class="prev"><span>Previous Slide</span></a>'), |
239 | 95 | 99 | next = Y.Node.create('<a href="#" class="next"><span>Next Slide</span></a>'); | |
240 | 96 | next.on("click", this.next, this); | 100 | next.on("click", this.next, this); |
241 | 97 | prev.on("click", this.prev, this); | 101 | prev.on("click", this.prev, this); |
242 | 98 | controlsContainer.appendChild(prev); | 102 | controlsContainer.appendChild(prev); |
243 | 99 | controlsContainer.appendChild(next); | 103 | controlsContainer.appendChild(next); |
244 | 104 | } | ||
245 | 100 | }, | 105 | }, |
246 | 101 | advance: function(e, val){ | 106 | advance: function(e, val){ |
247 | 102 | if (e) { | 107 | if (e) { |
248 | @@ -171,11 +176,7 @@ | |||
249 | 171 | }, | 176 | }, |
250 | 172 | controlsContainer: { | 177 | controlsContainer: { |
251 | 173 | setter: function(sel) { | 178 | setter: function(sel) { |
257 | 174 | var n = Y.one(sel); | 179 | return Y.one(sel); |
253 | 175 | if (!n) { | ||
254 | 176 | Y.log('UWC:Carousel - invalid selector provided: ' + sel); | ||
255 | 177 | } | ||
256 | 178 | return n; | ||
258 | 179 | } | 180 | } |
259 | 180 | }, | 181 | }, |
260 | 181 | slideAnimDuration: { value: 1 }, | 182 | slideAnimDuration: { value: 1 }, |
261 | 182 | 183 | ||
262 | === added file 'src/webcatalog/static/light/images/bullet.png' | |||
263 | 183 | Binary files src/webcatalog/static/light/images/bullet.png 1970-01-01 00:00:00 +0000 and src/webcatalog/static/light/images/bullet.png 2012-04-18 22:40:24 +0000 differ | 184 | Binary files src/webcatalog/static/light/images/bullet.png 1970-01-01 00:00:00 +0000 and src/webcatalog/static/light/images/bullet.png 2012-04-18 22:40:24 +0000 differ |
264 | === modified file 'src/webcatalog/templates/webcatalog/application_detail.html' | |||
265 | --- src/webcatalog/templates/webcatalog/application_detail.html 2012-04-05 01:09:37 +0000 | |||
266 | +++ src/webcatalog/templates/webcatalog/application_detail.html 2012-04-18 22:40:24 +0000 | |||
267 | @@ -83,11 +83,7 @@ | |||
268 | 83 | </div> | 83 | </div> |
269 | 84 | <div class="description"> | 84 | <div class="description"> |
270 | 85 | <div class="screenshot"> | 85 | <div class="screenshot"> |
271 | 86 | {% if application.screenshots %} | ||
272 | 87 | {% include "webcatalog/screenshot_widget.html" %} | 86 | {% include "webcatalog/screenshot_widget.html" %} |
273 | 88 | {% else %} | ||
274 | 89 | <img src="http://screenshots.ubuntu.com/thumbnail-with-version/{{ application.package_name }}/ignored" /> | ||
275 | 90 | {% endif %} | ||
276 | 91 | </div> | 87 | </div> |
277 | 92 | <p>{{ application.description|htmlize_package_description }}</p> | 88 | <p>{{ application.description|htmlize_package_description }}</p> |
278 | 93 | <div class="install-button">{% install_options application %}</div> | 89 | <div class="install-button">{% install_options application %}</div> |
279 | 94 | 90 | ||
280 | === added file 'src/webcatalog/templates/webcatalog/application_screenshots.html' | |||
281 | --- src/webcatalog/templates/webcatalog/application_screenshots.html 1970-01-01 00:00:00 +0000 | |||
282 | +++ src/webcatalog/templates/webcatalog/application_screenshots.html 2012-04-18 22:40:24 +0000 | |||
283 | @@ -0,0 +1,25 @@ | |||
284 | 1 | {% extends "webcatalog/base.html" %} | ||
285 | 2 | {% load i18n %} | ||
286 | 3 | {% load webcatalog %} | ||
287 | 4 | |||
288 | 5 | {% block title %}Screenshots for {{ application.name }}{% endblock %} | ||
289 | 6 | {% block header %}Screenshots for for {{ application.name }}{% endblock %} | ||
290 | 7 | |||
291 | 8 | {% block content %} | ||
292 | 9 | {% include "webcatalog/breadcrumbs_snippet.html" %} | ||
293 | 10 | <div id="sc-mockup"> | ||
294 | 11 | <div class="header"> | ||
295 | 12 | {% rating_summary application.ratings_average 'large' application.ratings_total %} | ||
296 | 13 | <img class="icon64" src="{{ application.icon_url_or_default }}"/> | ||
297 | 14 | <h2>{{ application.name }}</h2> | ||
298 | 15 | <p>{{ application.comment }}</p> | ||
299 | 16 | </div> | ||
300 | 17 | <ul> | ||
301 | 18 | {% for screenshot in screenshots %} | ||
302 | 19 | <li class="screenshot"> | ||
303 | 20 | <img src="{{ screenshot }}"> | ||
304 | 21 | </li> | ||
305 | 22 | {% endfor %} | ||
306 | 23 | </ul> | ||
307 | 24 | </div> | ||
308 | 25 | {% endblock %} | ||
309 | 0 | 26 | ||
310 | === modified file 'src/webcatalog/templates/webcatalog/screenshot_widget.html' | |||
311 | --- src/webcatalog/templates/webcatalog/screenshot_widget.html 2012-03-20 10:04:41 +0000 | |||
312 | +++ src/webcatalog/templates/webcatalog/screenshot_widget.html 2012-04-18 22:40:24 +0000 | |||
313 | @@ -1,28 +1,72 @@ | |||
316 | 1 | {% if application.screenshots|length_is:1 %} | 1 | {% load url from future %} |
317 | 2 | <img src="{{ application.screenshots.0 }}" /> | 2 | <div id="screenshots_placeholder"> |
318 | 3 | {% if application.screenshots|length_is:0 %} | ||
319 | 4 | <img src="http://screenshots.ubuntu.com/thumbnail-with-version/{{ application.package_name }}/ignored" /> | ||
320 | 3 | {% else %} | 5 | {% else %} |
330 | 4 | <div class="carousel-wrapper"> | 6 | <div class="carousel-wrapper"> |
331 | 5 | <div id="screenshots-carousel" class="carousel"> | 7 | <div id="screenshots-carousel" class="carousel"> |
332 | 6 | <ol class="carouselol"> | 8 | <ol class="carouselol">{% for screenshot in application.screenshots %} |
333 | 7 | {% for screenshot in application.screenshots %} | 9 | <li class="slide"><img src="{{ screenshot }}" /></li>{% endfor %} |
334 | 8 | <li class="slide{% if forloop.counter > 1 %} disabled{% endif %}"> | 10 | </ol> |
335 | 9 | <img src="{{ screenshot }}" /> | 11 | </div> |
327 | 10 | </li> | ||
328 | 11 | {% endfor %} | ||
329 | 12 | </ol> | ||
336 | 13 | </div> | 12 | </div> |
339 | 14 | </div> | 13 | {% endif %} |
340 | 15 | <div id="screenshots-controls"></div> | 14 | </div> |
341 | 15 | |||
342 | 16 | {% comment %} | ||
343 | 17 | If there are multiple screenshots, display a carousel. If there are none, | ||
344 | 18 | we could retrieve multiple screenshots via AJAX. So only skip this section | ||
345 | 19 | if the app has exactly 1 screenshot. | ||
346 | 20 | {% endcomment %} | ||
347 | 21 | {% if not application.screenshots|length_is:1 %} | ||
348 | 16 | <script type="text/javascript"> | 22 | <script type="text/javascript"> |
359 | 17 | YUI({combine: true, comboBase: '{% url wc-combo %}?', root: 'yui/3.4.0/build/'}).use('uwc-carousel', function(Y) { | 23 | YUI({combine: true, comboBase: '{% url 'wc-combo' %}?', root: 'yui/3.4.0/build/'}).use('io-base', 'uwc-carousel', function(Y) { |
360 | 18 | var caro = new Y.uwc.Carousel({ | 24 | |
361 | 19 | nodeContainer: "#screenshots-carousel", | 25 | function setup_carousel() { |
362 | 20 | controlsContainer: "#screenshots-controls", | 26 | var caro = new Y.uwc.Carousel({ |
363 | 21 | containerHeight: 170, | 27 | nodeContainer: "#screenshots-carousel", |
364 | 22 | containerWidth: 225, | 28 | controlsContainer: "#nocontrols", |
365 | 23 | autoPlay: true | 29 | autoPlay: false, |
366 | 24 | }); | 30 | slideAnimDuration: 0.6 |
367 | 25 | Y.all('.slide').removeClass('disabled'); | 31 | }); |
368 | 26 | }); | 32 | Y.all('.slide').removeClass('disabled'); |
369 | 33 | } | ||
370 | 34 | |||
371 | 35 | {% if application.screenshots|length_is:0 %} | ||
372 | 36 | var uri = "{% url 'wc-package-screenshots' application.package_name %}"; | ||
373 | 37 | function complete(id, obj) { | ||
374 | 38 | var id = id; // Transaction ID. | ||
375 | 39 | var json = JSON.parse(obj.responseText); | ||
376 | 40 | if (json.length > 1) { | ||
377 | 41 | var content_div = document.createElement('div'); | ||
378 | 42 | content_div.className = 'carousel-wrapper'; | ||
379 | 43 | var carousel_div = document.createElement('div'); | ||
380 | 44 | carousel_div.id = 'screenshots-carousel'; | ||
381 | 45 | carousel_div.className = 'carousel'; | ||
382 | 46 | content_div.appendChild(carousel_div) | ||
383 | 47 | var ol = document.createElement('ol') | ||
384 | 48 | ol.className = 'carouselol'; | ||
385 | 49 | carousel_div.appendChild(ol); | ||
386 | 50 | for (var i in json) { | ||
387 | 51 | var url = json[i]; | ||
388 | 52 | var li = document.createElement('li'); | ||
389 | 53 | li.className = 'slide'; | ||
390 | 54 | ol.appendChild(li); | ||
391 | 55 | var img = document.createElement('img'); | ||
392 | 56 | img.src = url; | ||
393 | 57 | li.appendChild(img); | ||
394 | 58 | } | ||
395 | 59 | var screenshots_div = Y.one('#screenshots_placeholder'); | ||
396 | 60 | screenshots_div.get('childNodes').remove(); | ||
397 | 61 | screenshots_div.appendChild(content_div); | ||
398 | 62 | setup_carousel(); | ||
399 | 63 | } | ||
400 | 64 | }; | ||
401 | 65 | Y.on('io:complete', complete, Y); | ||
402 | 66 | var request = Y.io(uri); | ||
403 | 67 | {% else %} | ||
404 | 68 | setup_carousel(); | ||
405 | 69 | {% endif %} | ||
406 | 70 | }); | ||
407 | 27 | </script> | 71 | </script> |
408 | 28 | {% endif %} | 72 | {% endif %} |
409 | 29 | 73 | ||
410 | === modified file 'src/webcatalog/tests/test_commands.py' | |||
411 | --- src/webcatalog/tests/test_commands.py 2012-04-17 17:09:03 +0000 | |||
412 | +++ src/webcatalog/tests/test_commands.py 2012-04-18 22:40:24 +0000 | |||
413 | @@ -149,11 +149,6 @@ | |||
414 | 149 | 'DISK_APT_CACHE_LOCATION', self.tmp_apt_cache) | 149 | 'DISK_APT_CACHE_LOCATION', self.tmp_apt_cache) |
415 | 150 | self.patch_cache_setting.start() | 150 | self.patch_cache_setting.start() |
416 | 151 | 151 | ||
417 | 152 | self.patch_get_screenshot_urls = patch( | ||
418 | 153 | 'webcatalog.management.commands.import_app_install_data.' | ||
419 | 154 | 'Command.get_screenshot_urls_from_server') | ||
420 | 155 | self.patch_get_screenshot_urls.start() | ||
421 | 156 | |||
422 | 157 | if self.use_mock_apt_cache: | 152 | if self.use_mock_apt_cache: |
423 | 158 | # The apt.Cache class is patched to return our own mock which | 153 | # The apt.Cache class is patched to return our own mock which |
424 | 159 | # will be subscriptable with a pre-set dict with mock ock apt | 154 | # will be subscriptable with a pre-set dict with mock ock apt |
425 | @@ -165,8 +160,6 @@ | |||
426 | 165 | 160 | ||
427 | 166 | def tearDown(self): | 161 | def tearDown(self): |
428 | 167 | self.patch_cache_setting.stop() | 162 | self.patch_cache_setting.stop() |
429 | 168 | if hasattr(self, 'patch_get_screenshot_urls'): | ||
430 | 169 | self.patch_get_screenshot_urls.stop() | ||
431 | 170 | shutil.rmtree(self.tmp_apt_cache) | 163 | shutil.rmtree(self.tmp_apt_cache) |
432 | 171 | if self.use_mock_apt_cache: | 164 | if self.use_mock_apt_cache: |
433 | 172 | self.patch_cache.stop() | 165 | self.patch_cache.stop() |
434 | @@ -290,29 +283,6 @@ | |||
435 | 290 | self.assertTrue(firefox.icon.size > 1) | 283 | self.assertTrue(firefox.icon.size > 1) |
436 | 291 | self.assertTrue(scribus.icon.size > 1) | 284 | self.assertTrue(scribus.icon.size > 1) |
437 | 292 | 285 | ||
438 | 293 | def test_screenshots_added_to_the_model(self): | ||
439 | 294 | fn = 'webcatalog.management.commands.import_app_install_data.urllib' | ||
440 | 295 | self.patch_get_screenshot_urls.stop() | ||
441 | 296 | del self.patch_get_screenshot_urls | ||
442 | 297 | with patch(fn) as mock_urllib: | ||
443 | 298 | mock_response = Mock() | ||
444 | 299 | mock_response.getcode.return_value = 200 | ||
445 | 300 | mock_response.read.return_value = json.dumps( | ||
446 | 301 | {'screenshots': [ | ||
447 | 302 | {'large_image_url': 'http://example.com/1.png'} | ||
448 | 303 | ]} | ||
449 | 304 | ) | ||
450 | 305 | mock_urllib.urlopen.return_value = mock_response | ||
451 | 306 | |||
452 | 307 | call_command('import_app_install_data', 'natty', | ||
453 | 308 | local_app_install_data=self.deb_location, | ||
454 | 309 | local_app_install_data_partner=self.deb_location, | ||
455 | 310 | verbosity=0) | ||
456 | 311 | |||
457 | 312 | scribus = Application.objects.get(package_name='scribus') | ||
458 | 313 | |||
459 | 314 | self.assertEqual(scribus.screenshots, ["http://example.com/1.png"]) | ||
460 | 315 | |||
461 | 316 | def get_package_uri_for_series(self): | 286 | def get_package_uri_for_series(self): |
462 | 317 | uri = import_app_install_data.Command().get_package_uri_for_series( | 287 | uri = import_app_install_data.Command().get_package_uri_for_series( |
463 | 318 | 'app-install-data', 'natty') | 288 | 'app-install-data', 'natty') |
464 | 319 | 289 | ||
465 | === modified file 'src/webcatalog/tests/test_utilities.py' | |||
466 | --- src/webcatalog/tests/test_utilities.py 2012-03-05 08:11:24 +0000 | |||
467 | +++ src/webcatalog/tests/test_utilities.py 2012-04-18 22:40:24 +0000 | |||
468 | @@ -20,8 +20,10 @@ | |||
469 | 20 | __all__ = [ | 20 | __all__ = [ |
470 | 21 | 'CreatePNGFromFileTestCase', | 21 | 'CreatePNGFromFileTestCase', |
471 | 22 | 'IdentityProviderTestCase', | 22 | 'IdentityProviderTestCase', |
472 | 23 | 'ScreenshotGetterTestCase', | ||
473 | 23 | ] | 24 | ] |
474 | 24 | 25 | ||
475 | 26 | import json | ||
476 | 25 | import os | 27 | import os |
477 | 26 | 28 | ||
478 | 27 | from webcatalog.tests.factory import TestCaseWithFactory | 29 | from webcatalog.tests.factory import TestCaseWithFactory |
479 | @@ -124,3 +126,35 @@ | |||
480 | 124 | u'displayname', u'name', u'unverified_emails', u'verified_emails', | 126 | u'displayname', u'name', u'unverified_emails', u'verified_emails', |
481 | 125 | u'consumer_secret', u'token', u'openid_identifier', | 127 | u'consumer_secret', u'token', u'openid_identifier', |
482 | 126 | u'consumer_key', u'token_secret']), set(data)) | 128 | u'consumer_key', u'token_secret']), set(data)) |
483 | 129 | |||
484 | 130 | |||
485 | 131 | class ScreenshotGetterTestCase(TestCase): | ||
486 | 132 | @patch('webcatalog.utilities.urllib.urlopen') | ||
487 | 133 | def test_valid_response_returns_list_of_urls(self, mock_urlopen): | ||
488 | 134 | response = json.dumps({'package': 'digikam', 'screenshots': [ | ||
489 | 135 | {'version': '1.0', 'large_image_url': 'http://example.com/foo'}, | ||
490 | 136 | {'version': '1.1', 'large_image_url': 'http://example.com/bar'}, | ||
491 | 137 | ]}) | ||
492 | 138 | mock_urlopen.return_value.getcode.return_value = 200 | ||
493 | 139 | mock_urlopen.return_value.read.return_value = response | ||
494 | 140 | expected = ['http://example.com/foo', 'http://example.com/bar'] | ||
495 | 141 | self.assertEqual(expected, | ||
496 | 142 | WebServices().get_screenshots_for_package('digikam')) | ||
497 | 143 | |||
498 | 144 | @patch('webcatalog.utilities.urllib.urlopen') | ||
499 | 145 | def test_not_found_response_returns_empty_list(self, mock_urlopen): | ||
500 | 146 | mock_urlopen.return_value.getcode.return_value = 404 | ||
501 | 147 | self.assertEqual([], WebServices().get_screenshots_for_package('foo')) | ||
502 | 148 | |||
503 | 149 | @patch('webcatalog.utilities.urllib.urlopen') | ||
504 | 150 | def test_invalid_response_returns_empty_list(self, mock_urlopen): | ||
505 | 151 | mock_urlopen.return_value.getcode.return_value = 200 | ||
506 | 152 | mock_urlopen.return_value.read.return_value = 'invalid json' | ||
507 | 153 | self.assertEqual([], WebServices().get_screenshots_for_package('foo')) | ||
508 | 154 | |||
509 | 155 | @patch('webcatalog.utilities.urllib.urlopen') | ||
510 | 156 | def test_unexpected_response_returns_empty_list(self, mock_urlopen): | ||
511 | 157 | response = json.dumps({'package': 'The Spanish Inquisition'}) | ||
512 | 158 | mock_urlopen.return_value.getcode.return_value = 200 | ||
513 | 159 | mock_urlopen.return_value.read.return_value = response | ||
514 | 160 | self.assertEqual([], WebServices().get_screenshots_for_package('foo')) | ||
515 | 127 | 161 | ||
516 | === modified file 'src/webcatalog/tests/test_views.py' | |||
517 | --- src/webcatalog/tests/test_views.py 2012-04-13 21:06:13 +0000 | |||
518 | +++ src/webcatalog/tests/test_views.py 2012-04-18 22:40:24 +0000 | |||
519 | @@ -22,12 +22,14 @@ | |||
520 | 22 | with_statement, | 22 | with_statement, |
521 | 23 | ) | 23 | ) |
522 | 24 | 24 | ||
523 | 25 | import json | ||
524 | 25 | import re | 26 | import re |
525 | 26 | 27 | ||
526 | 27 | from decimal import Decimal | 28 | from decimal import Decimal |
527 | 28 | 29 | ||
528 | 29 | from django.conf import settings | 30 | from django.conf import settings |
529 | 30 | from django.core import mail | 31 | from django.core import mail |
530 | 32 | from django.core.cache import cache | ||
531 | 31 | from django.core.urlresolvers import reverse | 33 | from django.core.urlresolvers import reverse |
532 | 32 | from django.test import TestCase | 34 | from django.test import TestCase |
533 | 33 | 35 | ||
534 | @@ -43,6 +45,7 @@ | |||
535 | 43 | 'ApplicationDetailNoSeriesTestCase', | 45 | 'ApplicationDetailNoSeriesTestCase', |
536 | 44 | 'ApplicationDetailTestCase', | 46 | 'ApplicationDetailTestCase', |
537 | 45 | 'ApplicationReviewsTestCase', | 47 | 'ApplicationReviewsTestCase', |
538 | 48 | 'ApplicationScreenshotsTestCase', | ||
539 | 46 | 'IndexTestCase', | 49 | 'IndexTestCase', |
540 | 47 | 'TermsOfServiceTestCase', | 50 | 'TermsOfServiceTestCase', |
541 | 48 | 'OverviewTestCase', | 51 | 'OverviewTestCase', |
542 | @@ -59,6 +62,9 @@ | |||
543 | 59 | 62 | ||
544 | 60 | 63 | ||
545 | 61 | class ApplicationDetailTestCase(TestCaseWithFactory): | 64 | class ApplicationDetailTestCase(TestCaseWithFactory): |
546 | 65 | def setUp(self): | ||
547 | 66 | super(ApplicationDetailTestCase, self).setUp() | ||
548 | 67 | cache.clear() | ||
549 | 62 | 68 | ||
550 | 63 | def get_app_details_url(self, app): | 69 | def get_app_details_url(self, app): |
551 | 64 | return reverse('wc-package-detail', | 70 | return reverse('wc-package-detail', |
552 | @@ -366,6 +372,10 @@ | |||
553 | 366 | 372 | ||
554 | 367 | 373 | ||
555 | 368 | class SearchTestCase(TestCaseWithFactory): | 374 | class SearchTestCase(TestCaseWithFactory): |
556 | 375 | def setUp(self): | ||
557 | 376 | super(SearchTestCase, self).setUp() | ||
558 | 377 | cache.clear() | ||
559 | 378 | |||
560 | 369 | def test_no_search_retrieves_no_apps(self): | 379 | def test_no_search_retrieves_no_apps(self): |
561 | 370 | # Ensure there's some data in the DB | 380 | # Ensure there's some data in the DB |
562 | 371 | distro = self.factory.make_distroseries(code_name='lucid') | 381 | distro = self.factory.make_distroseries(code_name='lucid') |
563 | @@ -474,9 +484,9 @@ | |||
564 | 474 | 484 | ||
565 | 475 | def test_response_shows_number_of_apps_found(self): | 485 | def test_response_shows_number_of_apps_found(self): |
566 | 476 | distro = self.factory.make_distroseries(code_name='lucid') | 486 | distro = self.factory.make_distroseries(code_name='lucid') |
568 | 477 | app1 = self.factory.make_application(package_name='foo', | 487 | self.factory.make_application(package_name='foo', |
569 | 478 | distroseries=distro) | 488 | distroseries=distro) |
571 | 479 | app2 = self.factory.make_application(package_name='foobar', | 489 | self.factory.make_application(package_name='foobar', |
572 | 480 | distroseries=distro) | 490 | distroseries=distro) |
573 | 481 | url = reverse('wc-search', args=[distro.code_name]) | 491 | url = reverse('wc-search', args=[distro.code_name]) |
574 | 482 | 492 | ||
575 | @@ -536,6 +546,10 @@ | |||
576 | 536 | 546 | ||
577 | 537 | 547 | ||
578 | 538 | class IndexTestCase(TestCaseWithFactory): | 548 | class IndexTestCase(TestCaseWithFactory): |
579 | 549 | def setUp(self): | ||
580 | 550 | super(IndexTestCase, self).setUp() | ||
581 | 551 | cache.clear() | ||
582 | 552 | |||
583 | 539 | def test_index_contains_links_to_departments(self): | 553 | def test_index_contains_links_to_departments(self): |
584 | 540 | dept = self.factory.make_department('foo') | 554 | dept = self.factory.make_department('foo') |
585 | 541 | 555 | ||
586 | @@ -671,6 +685,12 @@ | |||
587 | 671 | self.assertContains(response, expected, 2) | 685 | self.assertContains(response, expected, 2) |
588 | 672 | self.assertNotContains(response, app_not_included.package_name) | 686 | self.assertNotContains(response, app_not_included.package_name) |
589 | 673 | 687 | ||
590 | 688 | def test_includes_cache_headers(self): | ||
591 | 689 | with patch_settings(CACHE_MIDDLEWARE_SECONDS=600): | ||
592 | 690 | response = self.client.get(reverse('wc-index')) | ||
593 | 691 | |||
594 | 692 | self.assertEqual('max-age=600', response['cache-control']) | ||
595 | 693 | |||
596 | 674 | 694 | ||
597 | 675 | class TermsOfServiceTestCase(TestCase): | 695 | class TermsOfServiceTestCase(TestCase): |
598 | 676 | 696 | ||
599 | @@ -686,6 +706,9 @@ | |||
600 | 686 | 706 | ||
601 | 687 | 707 | ||
602 | 688 | class OverviewTestCase(TestCaseWithFactory): | 708 | class OverviewTestCase(TestCaseWithFactory): |
603 | 709 | def setUp(self): | ||
604 | 710 | super(OverviewTestCase, self).setUp() | ||
605 | 711 | cache.clear() | ||
606 | 689 | 712 | ||
607 | 690 | def test_department_contains_links_to_subdepartments(self): | 713 | def test_department_contains_links_to_subdepartments(self): |
608 | 691 | dept = self.factory.make_department('foo') | 714 | dept = self.factory.make_department('foo') |
609 | @@ -946,6 +969,70 @@ | |||
610 | 946 | self.assertContains(response, 'review_summary2') | 969 | self.assertContains(response, 'review_summary2') |
611 | 947 | 970 | ||
612 | 948 | 971 | ||
613 | 972 | class ApplicationScreenshotsTestCase(TestCaseWithFactory): | ||
614 | 973 | def setUp(self): | ||
615 | 974 | super(ApplicationScreenshotsTestCase, self).setUp() | ||
616 | 975 | self.app = self.factory.make_application() | ||
617 | 976 | self.expected = ['http://example.com/foo', 'http://example.com/bar'] | ||
618 | 977 | |||
619 | 978 | def test_only_valid_apps(self): | ||
620 | 979 | response = self.client.get(reverse('wc-package-screenshots', | ||
621 | 980 | args=['doesntexist'])) | ||
622 | 981 | |||
623 | 982 | self.assertEqual(404, response.status_code) | ||
624 | 983 | |||
625 | 984 | @patch('webcatalog.utilities.urllib.urlopen') | ||
626 | 985 | def test_uncached_calls_to_rnr_api(self, mock_urlopen): | ||
627 | 986 | self.client.get(reverse('wc-package-screenshots', | ||
628 | 987 | args=[self.app.package_name])) | ||
629 | 988 | |||
630 | 989 | self.assertEqual(1, mock_urlopen.call_count) | ||
631 | 990 | |||
632 | 991 | @patch('webcatalog.utilities.urllib.urlopen') | ||
633 | 992 | def test_second_call_cached(self, mock_urlopen): | ||
634 | 993 | for count in range(2): | ||
635 | 994 | self.client.get(reverse('wc-package-screenshots', | ||
636 | 995 | args=[self.app.package_name])) | ||
637 | 996 | |||
638 | 997 | self.assertEqual(1, mock_urlopen.call_count) | ||
639 | 998 | |||
640 | 999 | @patch('webcatalog.utilities.WebServices.get_screenshots_for_package') | ||
641 | 1000 | def test_renders_screenshots(self, mock_get_screenshots): | ||
642 | 1001 | mock_get_screenshots.return_value = self.expected | ||
643 | 1002 | |||
644 | 1003 | response = self.client.get(reverse('wc-package-screenshots', | ||
645 | 1004 | args=[self.app.package_name])) | ||
646 | 1005 | |||
647 | 1006 | self.assertTemplateUsed( | ||
648 | 1007 | response, 'webcatalog/application_screenshots.html') | ||
649 | 1008 | for url in self.expected: | ||
650 | 1009 | self.assertContains(response, url) | ||
651 | 1010 | |||
652 | 1011 | @patch('webcatalog.utilities.WebServices.get_screenshots_for_package') | ||
653 | 1012 | def test_renders_json_for_json_request(self, mock_get_screenshots): | ||
654 | 1013 | mock_get_screenshots.return_value = self.expected | ||
655 | 1014 | |||
656 | 1015 | response = self.client.get(reverse('wc-package-screenshots', | ||
657 | 1016 | args=[self.app.package_name]), | ||
658 | 1017 | HTTP_X_REQUESTED_WITH='XMLHttpRequest') | ||
659 | 1018 | |||
660 | 1019 | self.assertTemplateNotUsed( | ||
661 | 1020 | response, 'webcatalog/application_review_list.html') | ||
662 | 1021 | self.assertEqual('application/json', response['content-type']) | ||
663 | 1022 | self.assertEqual(json.dumps(self.expected), response.content) | ||
664 | 1023 | |||
665 | 1024 | @patch('webcatalog.utilities.WebServices.get_screenshots_for_package') | ||
666 | 1025 | def test_has_caching_headers(self, mock_get_screenshots): | ||
667 | 1026 | mock_get_screenshots.return_value = self.expected | ||
668 | 1027 | |||
669 | 1028 | with patch_settings(CACHE_MIDDLEWARE_SECONDS=600): | ||
670 | 1029 | response = self.client.get(reverse('wc-package-screenshots', | ||
671 | 1030 | args=[self.app.package_name]), | ||
672 | 1031 | HTTP_X_REQUESTED_WITH='XMLHttpRequest') | ||
673 | 1032 | |||
674 | 1033 | self.assertEqual('max-age=600', response['cache-control']) | ||
675 | 1034 | |||
676 | 1035 | |||
677 | 949 | class ComboViewTestCase(TestCase): | 1036 | class ComboViewTestCase(TestCase): |
678 | 950 | """Tests for ComboView.""" | 1037 | """Tests for ComboView.""" |
679 | 951 | 1038 | ||
680 | 952 | 1039 | ||
681 | === modified file 'src/webcatalog/urls.py' | |||
682 | --- src/webcatalog/urls.py 2012-03-20 11:12:02 +0000 | |||
683 | +++ src/webcatalog/urls.py 2012-04-18 22:40:24 +0000 | |||
684 | @@ -36,6 +36,8 @@ | |||
685 | 36 | 'department_overview', name='wc-department'), | 36 | 'department_overview', name='wc-department'), |
686 | 37 | url(r'^department/(?P<dept_slug_or_id>[\w\d-]+)/$', 'department_overview', | 37 | url(r'^department/(?P<dept_slug_or_id>[\w\d-]+)/$', 'department_overview', |
687 | 38 | name='wc-department'), | 38 | name='wc-department'), |
688 | 39 | url(r'^applications/screenshots/(?P<package_name>[-.+:\w]+)/$', | ||
689 | 40 | 'application_screenshots', name="wc-package-screenshots"), | ||
690 | 39 | url(r'^applications/(?P<distro>[-.+\w]+)/(?P<package_name>[-.+:\w]+)/$', | 41 | url(r'^applications/(?P<distro>[-.+\w]+)/(?P<package_name>[-.+:\w]+)/$', |
691 | 40 | 'application_detail', name="wc-package-detail"), | 42 | 'application_detail', name="wc-package-detail"), |
692 | 41 | # The following URL redirects to the series-specific URL above. | 43 | # The following URL redirects to the series-specific URL above. |
693 | 42 | 44 | ||
694 | === modified file 'src/webcatalog/utilities.py' | |||
695 | --- src/webcatalog/utilities.py 2012-03-19 20:43:17 +0000 | |||
696 | +++ src/webcatalog/utilities.py 2012-04-18 22:40:24 +0000 | |||
697 | @@ -28,10 +28,12 @@ | |||
698 | 28 | 'WebServiceError', | 28 | 'WebServiceError', |
699 | 29 | ] | 29 | ] |
700 | 30 | 30 | ||
701 | 31 | import json | ||
702 | 31 | import logging | 32 | import logging |
703 | 32 | import math | 33 | import math |
704 | 33 | import re | 34 | import re |
705 | 34 | import urllib | 35 | import urllib |
706 | 36 | from urlparse import urljoin | ||
707 | 35 | 37 | ||
708 | 36 | from django.conf import settings | 38 | from django.conf import settings |
709 | 37 | from django.core.cache import cache | 39 | from django.core.cache import cache |
710 | @@ -77,6 +79,26 @@ | |||
711 | 77 | cache.set(key, fresh_reviews, settings.REVIEWS_CACHE_TIMEOUT) | 79 | cache.set(key, fresh_reviews, settings.REVIEWS_CACHE_TIMEOUT) |
712 | 78 | return fresh_reviews | 80 | return fresh_reviews |
713 | 79 | 81 | ||
714 | 82 | def get_screenshots_for_package(self, package_name): | ||
715 | 83 | key = 'get_screenshots_for_package-{0}'.format( | ||
716 | 84 | package_name) | ||
717 | 85 | cached_screenshots = cache.get(key) | ||
718 | 86 | if cached_screenshots is not None: | ||
719 | 87 | return cached_screenshots | ||
720 | 88 | url = urljoin(settings.SCREENSHOTS_BASE_URL, | ||
721 | 89 | '/json/package/{0}'.format(package_name)) | ||
722 | 90 | fresh_screenshots = [] | ||
723 | 91 | try: | ||
724 | 92 | response = urllib.urlopen(url) | ||
725 | 93 | if response.getcode() == 200: | ||
726 | 94 | data = json.loads(response.read()) | ||
727 | 95 | fresh_screenshots = [entry['large_image_url'] | ||
728 | 96 | for entry in data.get('screenshots', [])] | ||
729 | 97 | except (IOError, ValueError, TypeError): | ||
730 | 98 | pass | ||
731 | 99 | cache.set(key, fresh_screenshots, settings.REVIEWS_CACHE_TIMEOUT) | ||
732 | 100 | return fresh_screenshots | ||
733 | 101 | |||
734 | 80 | def _fake_validate_token(self, token, openid_identifier, signature): | 102 | def _fake_validate_token(self, token, openid_identifier, signature): |
735 | 81 | """ This is a version of validate_token that gets the | 103 | """ This is a version of validate_token that gets the |
736 | 82 | token_secret, consumer_secret from the plaintext signature. | 104 | token_secret, consumer_secret from the plaintext signature. |
737 | 83 | 105 | ||
738 | === modified file 'src/webcatalog/views.py' | |||
739 | --- src/webcatalog/views.py 2012-04-13 21:17:09 +0000 | |||
740 | +++ src/webcatalog/views.py 2012-04-18 22:40:24 +0000 | |||
741 | @@ -210,6 +210,19 @@ | |||
742 | 210 | return render_to_response(template, RequestContext(request, context)) | 210 | return render_to_response(template, RequestContext(request, context)) |
743 | 211 | 211 | ||
744 | 212 | 212 | ||
745 | 213 | def application_screenshots(request, package_name): | ||
746 | 214 | app = get_object_or_404(Application, package_name=package_name) | ||
747 | 215 | screenshots = WebServices().get_screenshots_for_package(package_name) | ||
748 | 216 | if request.is_ajax(): | ||
749 | 217 | return HttpResponse(json.dumps(screenshots), | ||
750 | 218 | mimetype='application/json') | ||
751 | 219 | else: | ||
752 | 220 | context = dict(application=app, screenshots=screenshots, | ||
753 | 221 | breadcrumbs=app.crumbs()) | ||
754 | 222 | return render_to_response('webcatalog/application_screenshots.html', | ||
755 | 223 | RequestContext(request, context)) | ||
756 | 224 | |||
757 | 225 | |||
758 | 213 | def combo_view(request): | 226 | def combo_view(request): |
759 | 214 | """Handle a request for combining a set of files.""" | 227 | """Handle a request for combining a set of files.""" |
760 | 215 | fnames = parse_qs(request.META.get("QUERY_STRING", "")) | 228 | fnames = parse_qs(request.META.get("QUERY_STRING", "")) |
Nice work Anthony.