Merge lp:~elachuni/ubuntu-webcatalog/fix-import-for-purchase-apps into lp:ubuntu-webcatalog
- fix-import-for-purchase-apps
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Anthony Lenton |
Approved revision: | 36 |
Merged at revision: | 33 |
Proposed branch: | lp:~elachuni/ubuntu-webcatalog/fix-import-for-purchase-apps |
Merge into: | lp:ubuntu-webcatalog |
Diff against target: |
793 lines (+364/-87) 18 files modified
src/webcatalog/admin.py (+2/-2) src/webcatalog/forms.py (+22/-1) src/webcatalog/management/commands/import_app_install_data.py (+14/-7) src/webcatalog/management/commands/import_for_purchase_apps.py (+50/-10) src/webcatalog/migrations/0004_add_price_and_make_archive_id_not_unique.py (+72/-0) src/webcatalog/models/applications.py (+14/-17) src/webcatalog/static/css/webcatalog.css (+14/-26) src/webcatalog/templates/404.html (+16/-0) src/webcatalog/templates/light/404.html (+73/-0) src/webcatalog/templates/webcatalog/application_detail.html (+4/-1) src/webcatalog/templates/webcatalog/install_options_snippet.html (+5/-8) src/webcatalog/templatetags/webcatalog.py (+1/-1) src/webcatalog/tests/test_commands.py (+38/-1) src/webcatalog/tests/test_data/sca_apps.txt (+3/-3) src/webcatalog/tests/test_models.py (+15/-4) src/webcatalog/tests/test_templatetags.py (+1/-1) src/webcatalog/tests/test_views.py (+17/-5) src/webcatalog/views.py (+3/-0) |
To merge this branch: | bzr merge lp:~elachuni/ubuntu-webcatalog/fix-import-for-purchase-apps |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ricardo Kirkner (community) | Approve | ||
Review via email: mp+66786@code.launchpad.net |
Commit message
Fix several issues with the import_
Description of the change
Overview
========
Fix several issues with the import_
and several other smaller issues.
Details
=======
In order of appearance (in the diff):
- Improves the Application admin screens, by allowing you to
filter by distroseries, as well as displaying the
distroseries for each app in the list display.
- Adds ForPurchaseAppl
import_
instead of validating manually.
- Adds support for tiff icons to import_
allowing gtk2 to handle the conversion to png like we were
doing with svg and xpm icons already.
- Drastically improves the speed of import_
by prefetching all existing apps for a distroseries from
the database, instead of querying the db for each one, and
skipping an app's update if there's nothing to update.
- Improves import_
wasn't being imported and neither were available
distroseries or the icon data, and apps were never being
updated once created.
- Provides a migration to create the 'price' field on
for-purchase apps, that wasn't being tracked at all.
- Removes the unique constraint on archive_id, as it's
actually (archive_id, distroseries) that's unique.
- Allows crumbs() to produce distroseries-
display on distroseries-
- Retouches the css for the "Install" button to make it more
similar to USC.
- Provides a themed 404 template.
- Adds tests for everything.
- Makes invalid distroseries return a 404 response in the
department overview view.
- 36. By Anthony Lenton
-
Two small fixes per code review.
Preview Diff
1 | === modified file 'src/webcatalog/admin.py' |
2 | --- src/webcatalog/admin.py 2011-07-01 16:18:58 +0000 |
3 | +++ src/webcatalog/admin.py 2011-07-04 15:54:51 +0000 |
4 | @@ -35,9 +35,9 @@ |
5 | |
6 | |
7 | class ApplicationAdmin(admin.ModelAdmin): |
8 | - list_display = ('package_name', 'name', 'comment') |
9 | + list_display = ('package_name', 'name', 'comment', 'distroseries') |
10 | search_fields = ('package_name', 'name', 'comment') |
11 | - list_filter = ('departments',) |
12 | + list_filter = ('distroseries', 'departments') |
13 | exclude = ('for_purchase', 'archive_id') |
14 | |
15 | admin.site.register(Application, ApplicationAdmin) |
16 | |
17 | === modified file 'src/webcatalog/forms.py' |
18 | --- src/webcatalog/forms.py 2011-06-24 07:44:24 +0000 |
19 | +++ src/webcatalog/forms.py 2011-07-04 15:54:51 +0000 |
20 | @@ -53,7 +53,7 @@ |
21 | |
22 | class Meta: |
23 | model = Application |
24 | - exclude = ('distroseries','for_purchase', 'archive_id') |
25 | + exclude = ('distroseries','for_purchase', 'archive_id', 'price') |
26 | |
27 | @classmethod |
28 | def get_form_from_desktop_data(cls, str_data, distroseries): |
29 | @@ -85,3 +85,24 @@ |
30 | cleaned_data[key] = instance_value or value |
31 | |
32 | return cleaned_data |
33 | + |
34 | +class ForPurchaseApplicationForm(forms.ModelForm): |
35 | + class Meta: |
36 | + model = Application |
37 | + exclude = ('distroseries', 'section', 'popcon') |
38 | + |
39 | + @classmethod |
40 | + def from_json(cls, app_data, distroseries): |
41 | + app_data = app_data.copy() |
42 | + if 'description' in app_data: |
43 | + tagline, _, description = app_data['description'].partition('\n') |
44 | + app_data['comment'] = tagline |
45 | + app_data['description'] = description |
46 | + try: |
47 | + instance = Application.objects.get( |
48 | + archive_id=app_data.get('archive_id'), |
49 | + distroseries=distroseries) |
50 | + except Application.DoesNotExist: |
51 | + instance = None |
52 | + |
53 | + return cls(data=app_data, instance=instance) |
54 | |
55 | === modified file 'src/webcatalog/management/commands/import_app_install_data.py' |
56 | --- src/webcatalog/management/commands/import_app_install_data.py 2011-07-01 15:27:44 +0000 |
57 | +++ src/webcatalog/management/commands/import_app_install_data.py 2011-07-04 15:54:51 +0000 |
58 | @@ -217,8 +217,9 @@ |
59 | break |
60 | |
61 | if icon_path: |
62 | - if icon_path.endswith('.xpm') or icon_path.endswith('.svg'): |
63 | - target_path = icon_path[:-4] + '.png' |
64 | + parts = os.path.splitext(icon_path) |
65 | + if parts[1] in ['.xpm', '.svg', '.tiff']: |
66 | + target_path = parts[0] + '.png' |
67 | if create_png_from_file(target_path, icon_path, 32, 32): |
68 | icon_path = target_path |
69 | with open(icon_path, "r") as icon_file: |
70 | @@ -236,6 +237,9 @@ |
71 | self.output("Created a DistroSeries record called '{0}'.\n".format( |
72 | distroseries_name), 1) |
73 | |
74 | + prefetched_apps = dict((app.package_name, app) for app in |
75 | + Application.objects.filter(distroseries=distroseries)) |
76 | + |
77 | # For each package in the apt-cache, we update (or possibly |
78 | # create) a matching db entry. |
79 | for package in cache: |
80 | @@ -252,17 +256,20 @@ |
81 | if not app_comment: |
82 | app_comment = package.name |
83 | |
84 | - try: |
85 | - app = Application.objects.get( |
86 | - package_name=package.name, |
87 | - distroseries=distroseries) |
88 | + if package.name in prefetched_apps: |
89 | + app = prefetched_apps[package.name] |
90 | + if (app.description == candidate.description and |
91 | + app.section == candidate.section and |
92 | + app.name == app_name and |
93 | + app.comment == app_comment): |
94 | + continue |
95 | app.description = candidate.description |
96 | app.section = candidate.section |
97 | app.name = app_name |
98 | app.comment = app_comment |
99 | app.save() |
100 | |
101 | - except Application.DoesNotExist: |
102 | + else: |
103 | app = Application.objects.create( |
104 | package_name=package.name, |
105 | distroseries=distroseries, |
106 | |
107 | === modified file 'src/webcatalog/management/commands/import_for_purchase_apps.py' |
108 | --- src/webcatalog/management/commands/import_for_purchase_apps.py 2011-06-17 18:12:02 +0000 |
109 | +++ src/webcatalog/management/commands/import_for_purchase_apps.py 2011-07-04 15:54:51 +0000 |
110 | @@ -23,13 +23,19 @@ |
111 | ) |
112 | |
113 | import json |
114 | +import os |
115 | import urllib |
116 | +from StringIO import StringIO |
117 | +from tempfile import mkstemp |
118 | |
119 | from django.conf import settings |
120 | +from django.core.files.images import ImageFile |
121 | from django.core.management.base import BaseCommand |
122 | |
123 | +from webcatalog.forms import ForPurchaseApplicationForm |
124 | from webcatalog.models import ( |
125 | Application, |
126 | + DistroSeries, |
127 | ) |
128 | |
129 | __metaclass__ = type |
130 | @@ -49,15 +55,49 @@ |
131 | raise Exception("Couldn't connect to server at %s" % url) |
132 | app_list = json.loads(response.read()) |
133 | for app_data in app_list: |
134 | - archive_id = app_data['archive_id'] |
135 | - # see if we've already imported it |
136 | - try: |
137 | - app = Application.objects.get(archive_id=archive_id) |
138 | - continue |
139 | - except Application.DoesNotExist: |
140 | - pass |
141 | - # not already imported - save to webcatalog |
142 | - app = Application.from_json(app_data) |
143 | - # mark it as for purchase since it came from sca |
144 | + for series_name in app_data['series']: |
145 | + distroseries, created = DistroSeries.objects.get_or_create( |
146 | + code_name=series_name) |
147 | + if created: |
148 | + self.output( |
149 | + "Created a DistroSeries record called '{0}'.\n".format( |
150 | + series_name), 1) |
151 | + self.import_app_from_data(app_data, distroseries) |
152 | + |
153 | + def import_app_from_data(self, app_data, distroseries): |
154 | + form = ForPurchaseApplicationForm.from_json(app_data, distroseries) |
155 | + if form.is_valid(): |
156 | + app = form.save(commit=False) |
157 | + app.distroseries = distroseries |
158 | app.for_purchase = True |
159 | app.save() |
160 | + app.update_departments() |
161 | + self.add_icon_to_app(app, data=app_data.get('icon_data', ''), |
162 | + url=app_data.get('icon_url', '')) |
163 | + self.output(u"{0} created.\n".format(app.name).encode('utf-8'), 1) |
164 | + |
165 | + def add_icon_to_app(self, app, data, url): |
166 | + if data: |
167 | + pngfile = StringIO(data.decode('base64')) |
168 | + else: |
169 | + pngfile = urllib.urlopen(url) |
170 | + try: |
171 | + fd, filename = mkstemp(prefix=app.package_name, suffix='.png') |
172 | + iconfile = os.fdopen(fd, 'w') |
173 | + iconfile.write(pngfile.read()) |
174 | + iconfile.close() |
175 | + with open(filename) as f: |
176 | + app.icon = ImageFile(f) |
177 | + app.save() |
178 | + finally: |
179 | + if os.path.exists(filename): |
180 | + os.unlink(filename) |
181 | + |
182 | + def output(self, message, level=None, flush=False): |
183 | + if hasattr(self, 'stdout'): |
184 | + if level is None or self.verbosity >= level: |
185 | + self.stdout.write(message) |
186 | + if flush: |
187 | + self.stdout.flush() |
188 | + |
189 | + |
190 | \ No newline at end of file |
191 | |
192 | === added file 'src/webcatalog/migrations/0004_add_price_and_make_archive_id_not_unique.py' |
193 | --- src/webcatalog/migrations/0004_add_price_and_make_archive_id_not_unique.py 1970-01-01 00:00:00 +0000 |
194 | +++ src/webcatalog/migrations/0004_add_price_and_make_archive_id_not_unique.py 2011-07-04 15:54:51 +0000 |
195 | @@ -0,0 +1,72 @@ |
196 | +# encoding: utf-8 |
197 | +import datetime |
198 | +from south.db import db |
199 | +from south.v2 import SchemaMigration |
200 | +from django.db import models |
201 | + |
202 | +class Migration(SchemaMigration): |
203 | + |
204 | + def forwards(self, orm): |
205 | + |
206 | + # Removing unique constraint on 'Application', fields ['archive_id'] |
207 | + db.delete_unique('webcatalog_application', ['archive_id']) |
208 | + |
209 | + # Adding field 'Application.price' |
210 | + db.add_column('webcatalog_application', 'price', self.gf('django.db.models.fields.DecimalField')(null=True, max_digits=7, decimal_places=2), keep_default=False) |
211 | + |
212 | + # Adding unique constraint on 'Application', fields ['archive_id', 'distroseries'] |
213 | + db.create_unique('webcatalog_application', ['archive_id', 'distroseries_id']) |
214 | + |
215 | + |
216 | + def backwards(self, orm): |
217 | + |
218 | + # Removing unique constraint on 'Application', fields ['archive_id', 'distroseries'] |
219 | + db.delete_unique('webcatalog_application', ['archive_id', 'distroseries_id']) |
220 | + |
221 | + # Deleting field 'Application.price' |
222 | + db.delete_column('webcatalog_application', 'price') |
223 | + |
224 | + # Adding unique constraint on 'Application', fields ['archive_id'] |
225 | + db.create_unique('webcatalog_application', ['archive_id']) |
226 | + |
227 | + |
228 | + models = { |
229 | + 'webcatalog.application': { |
230 | + 'Meta': {'unique_together': "(('distroseries', 'archive_id'),)", 'object_name': 'Application'}, |
231 | + 'app_type': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}), |
232 | + 'architectures': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), |
233 | + 'archive_id': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '64', 'null': 'True', 'blank': 'True'}), |
234 | + 'categories': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), |
235 | + 'channel': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), |
236 | + 'comment': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), |
237 | + 'departments': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['webcatalog.Department']", 'symmetrical': 'False', 'blank': 'True'}), |
238 | + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), |
239 | + 'distroseries': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.DistroSeries']"}), |
240 | + 'for_purchase': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
241 | + 'icon': ('django.db.models.fields.files.ImageField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), |
242 | + 'icon_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), |
243 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
244 | + 'keywords': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), |
245 | + 'mimetype': ('django.db.models.fields.CharField', [], {'max_length': '2048', 'blank': 'True'}), |
246 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
247 | + 'package_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
248 | + 'popcon': ('django.db.models.fields.IntegerField', [], {}), |
249 | + 'price': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '7', 'decimal_places': '2'}), |
250 | + 'screenshot_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}), |
251 | + 'section': ('django.db.models.fields.CharField', [], {'max_length': '32'}) |
252 | + }, |
253 | + 'webcatalog.department': { |
254 | + 'Meta': {'object_name': 'Department'}, |
255 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
256 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}), |
257 | + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Department']", 'null': 'True', 'blank': 'True'}) |
258 | + }, |
259 | + 'webcatalog.distroseries': { |
260 | + 'Meta': {'object_name': 'DistroSeries'}, |
261 | + 'code_name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '20', 'db_index': 'True'}), |
262 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
263 | + 'version': ('django.db.models.fields.CharField', [], {'max_length': '10', 'blank': 'True'}) |
264 | + } |
265 | + } |
266 | + |
267 | + complete_apps = ['webcatalog'] |
268 | |
269 | === modified file 'src/webcatalog/models/applications.py' |
270 | --- src/webcatalog/models/applications.py 2011-07-01 16:18:58 +0000 |
271 | +++ src/webcatalog/models/applications.py 2011-07-04 15:54:51 +0000 |
272 | @@ -54,7 +54,7 @@ |
273 | # will potentially have a different name) and names/descriptions can |
274 | # change for a package for different series. |
275 | # We may need to have one application item per language, depending |
276 | - # whether we have geettext data available that we could use at |
277 | + # whether we have gettext data available that we could use at |
278 | # runtime (much preferred) |
279 | distroseries = models.ForeignKey(DistroSeries) |
280 | |
281 | @@ -78,7 +78,9 @@ |
282 | null=True, blank=True) |
283 | for_purchase = models.BooleanField(default=False) |
284 | archive_id = models.CharField(max_length=64, null=True, |
285 | - db_index=True, blank=True, unique=True) |
286 | + db_index=True, blank=True) |
287 | + price = models.DecimalField(max_digits=7, decimal_places=2, null=True, |
288 | + help_text="For-purchase applications (in US Dollars).") |
289 | |
290 | # Other desktop fields used by s-c |
291 | # x-gnome-fullname |
292 | @@ -104,16 +106,6 @@ |
293 | return DistroSeries.objects.filter( |
294 | application__package_name=self.package_name).order_by('version') |
295 | |
296 | - @classmethod |
297 | - def from_json(cls, data): |
298 | - app = Application() |
299 | - for name in app._meta.get_all_field_names(): |
300 | - try: |
301 | - setattr(app, name, data[name]) |
302 | - except KeyError: |
303 | - pass |
304 | - return app |
305 | - |
306 | def update_departments(self): |
307 | """Update the list of departments for this app""" |
308 | self.departments.clear() |
309 | @@ -131,7 +123,7 @@ |
310 | def crumbs(self): |
311 | depts = self.departments.order_by('-parent') |
312 | if depts.count(): |
313 | - crumbs = depts[0].crumbs() |
314 | + crumbs = depts[0].crumbs(distro=self.distroseries.code_name) |
315 | else: |
316 | crumbs = [{'name': 'Get Software', 'url': reverse('wc-index')}] |
317 | crumbs.append({'name': self.name, 'url': reverse('wc-package-detail', |
318 | @@ -144,6 +136,7 @@ |
319 | |
320 | class Meta: |
321 | app_label = 'webcatalog' |
322 | + unique_together = ('distroseries', 'archive_id') |
323 | |
324 | |
325 | class Department(models.Model): |
326 | @@ -157,13 +150,17 @@ |
327 | def normalized_name(self): |
328 | return re.sub(r'[-&.,;" \']', '', self.name) |
329 | |
330 | - def crumbs(self): |
331 | + def crumbs(self, distro=None): |
332 | if self.parent: |
333 | - crumbs = self.parent.crumbs() |
334 | + crumbs = self.parent.crumbs(distro=distro) |
335 | else: |
336 | crumbs = [{'name': 'Get Software', 'url': reverse('wc-index')}] |
337 | - crumbs.append({'name': self.name, 'url': reverse('wc-department', |
338 | - args=[self.id])}) |
339 | + args = [] |
340 | + if distro is not None: |
341 | + args = [distro] |
342 | + args.append(self.id) |
343 | + url =reverse('wc-department', args=args) |
344 | + crumbs.append({'name': self.name, 'url': url}) |
345 | return crumbs |
346 | |
347 | class Meta: |
348 | |
349 | === modified file 'src/webcatalog/static/css/webcatalog.css' |
350 | --- src/webcatalog/static/css/webcatalog.css 2011-07-01 16:18:58 +0000 |
351 | +++ src/webcatalog/static/css/webcatalog.css 2011-07-04 15:54:51 +0000 |
352 | @@ -162,33 +162,21 @@ |
353 | width: 300px; |
354 | } |
355 | |
356 | -.awesome, .awesome:visited { |
357 | - background-color: #222222; |
358 | - border-bottom: 1px solid rgba(0, 0, 0, 0.25); |
359 | - border-radius: 5px 5px 5px 5px; |
360 | - box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15); |
361 | - cursor: pointer; |
362 | - display: inline-block; |
363 | - font-size: 20px; |
364 | - font-weight: bold; |
365 | - line-height: 1; |
366 | - margin: 5px; |
367 | +.install-bar { |
368 | + background: #B1CEE6; |
369 | + border: 1px solid #639BCE; |
370 | + height: 28px; |
371 | + margin: 0 16px; |
372 | + padding: 4px; |
373 | +} |
374 | +.install-bar p { |
375 | + font-size: 14px; |
376 | padding: 5px; |
377 | - position: relative; |
378 | - text-decoration: none; |
379 | - text-shadow: 0 -1px 1px rgba(0, 0, 0, 0.25); |
380 | -} |
381 | -.awesome:hover { |
382 | - background-color: #D45500; |
383 | - color: #FFFFFF; |
384 | - text-decoration: none; |
385 | -} |
386 | -.awesome:active { |
387 | - top: 1px; |
388 | -} |
389 | -.awesome, .awesome:visited { |
390 | - background-color: #FF5C00; |
391 | - color: #FFFFFF; |
392 | +} |
393 | +.install-bar div.install-button { |
394 | + float: right; |
395 | + height: 28px; |
396 | + line-height: 28px; |
397 | } |
398 | |
399 | .dept-icon { |
400 | |
401 | === added file 'src/webcatalog/templates/404.html' |
402 | --- src/webcatalog/templates/404.html 1970-01-01 00:00:00 +0000 |
403 | +++ src/webcatalog/templates/404.html 2011-07-04 15:54:51 +0000 |
404 | @@ -0,0 +1,16 @@ |
405 | +{% extends "light/404.html" %} |
406 | +{% load i18n %} |
407 | + |
408 | +{% comment %} |
409 | +Copyright 2010 Canonical Ltd. This software is licensed under the |
410 | +GNU Affero General Public License version 3 (see the file LICENSE). |
411 | +{% endcomment %} |
412 | + |
413 | +{% block content_container_extra %} |
414 | + <div> |
415 | + <p> |
416 | + {% blocktrans %}(Error <abbr>ID</abbr>: {{ oopsid }}){% endblocktrans %} |
417 | + </p> |
418 | + </div> |
419 | +{% endblock %} |
420 | + |
421 | |
422 | === added file 'src/webcatalog/templates/light/404.html' |
423 | --- src/webcatalog/templates/light/404.html 1970-01-01 00:00:00 +0000 |
424 | +++ src/webcatalog/templates/light/404.html 2011-07-04 15:54:51 +0000 |
425 | @@ -0,0 +1,73 @@ |
426 | +{% extends 'light/index.1col.html' %} |
427 | + |
428 | +{% block title %}Error | Ubuntu{% endblock %} |
429 | + |
430 | +{% block header %}{% endblock %} |
431 | + |
432 | +{% block head_extra %} |
433 | + <link rel="stylesheet" type="text/css" href="{{ ASSET_ROOT }}/assets/light/css/404.css"/> |
434 | +{% endblock %} |
435 | + |
436 | +{% block content_container %} |
437 | +<div id="template-err2"> |
438 | + <div id="row-main-row" > |
439 | + <div class="pane-content"> |
440 | + <div class="img_404"> |
441 | + <h1>404</h1> |
442 | + </div> |
443 | + <h2>This page does not exist.</h2> |
444 | + <h3>Well, obviously <em>this</em> page exists. But the |
445 | + page you requested does not exist. This page is just |
446 | + here to tell you that the page you requested does not exist. |
447 | + </h3> |
448 | + {% block content_container_default %} |
449 | + <p>You can use the search box above to find what you need. |
450 | + Or you can make a fresh start at the |
451 | + <a href="http://www.ubuntu.com">Ubuntu home page</a>. |
452 | + </p> |
453 | + <p>Still can't find what you're looking for? |
454 | + <a href="/contact-us">Get in touch today</a>. |
455 | + </p> |
456 | + {% endblock %} |
457 | + </div> |
458 | + </div> |
459 | + {% block content_container_extra %}{% endblock %} |
460 | + |
461 | + <div class="clear"> </div> |
462 | + |
463 | + <div id="row-2" class="row-wrapper-2"> |
464 | + |
465 | + <div class="grid-4 err2_pane_4" > |
466 | + <div> |
467 | + <h2><a href="http://www.ubuntu.com/project">About Ubuntu</a></h2> |
468 | + <p></p> |
469 | + <p>Find out more about the Ubuntu Project and how you can contribute.</p> |
470 | + <p></p> |
471 | + <p><img src="http://www.ubuntu.com/sites/default/files/active/error_page_imagery/pictogram_knowledge_medium.png" alt="pictogram_knowledge_medium" /></p> |
472 | + </div> |
473 | + </div> |
474 | + |
475 | + <div class="grid-4 err2_pane_4" > |
476 | + <div> |
477 | + <h2><a href="http://www.ubuntu.com/desktop/get-ubuntu">Get Ubuntu</a></h2> |
478 | + <p></p> |
479 | + <p>Download Ubuntu today.</p> |
480 | + <p></p> |
481 | + <p><img src="http://www.ubuntu.com/sites/default/files/active/03_global/02_pictograms/pictogram_download_medium.png" alt="pictogram_download_medium" /></p> |
482 | + </div> |
483 | + </div> |
484 | + |
485 | + <div class="grid-4 err2_pane_4" > |
486 | + <div> |
487 | + <h2><a href="http://shop.canonical.com">Visit the shop</a></h2> |
488 | + <p></p> |
489 | + <p>Buy Ubuntu CDs, accessories or training at the Canonical shop.</p> |
490 | + <p></p> |
491 | + <p><img src="http://www.ubuntu.com/sites/default/files/active/error_page_imagery/pictogram_shop_medium.png" alt="pictogram_shop_medium.png" /></p> |
492 | + </div> |
493 | + </div> |
494 | + |
495 | + <div class="clear"> </div> |
496 | + </div> |
497 | +</div> |
498 | +{% endblock %} |
499 | |
500 | === modified file 'src/webcatalog/templates/webcatalog/application_detail.html' |
501 | --- src/webcatalog/templates/webcatalog/application_detail.html 2011-07-01 16:18:58 +0000 |
502 | +++ src/webcatalog/templates/webcatalog/application_detail.html 2011-07-04 15:54:51 +0000 |
503 | @@ -33,6 +33,10 @@ |
504 | <h2>{{ application.name }}</h2> |
505 | <p>{{ application.comment }}</p> |
506 | </div> |
507 | + <div class="install-bar"> |
508 | + <div class="install-button">{% install_options application %}</div> |
509 | + <p>{% if application.price %}US$ {{ application.price }}{% else %}Free{% endif %}</p> |
510 | + </div> |
511 | <div class="description"> |
512 | <div class="screenshot"> |
513 | {% comment %} |
514 | @@ -42,7 +46,6 @@ |
515 | <img src="http://screenshots.ubuntu.com/thumbnail-with-version/{{ application.package_name }}/ignored" /> |
516 | </div> |
517 | <p>{{ application.description }}</p> |
518 | - {% install_options application %} |
519 | </div> |
520 | <div class="license"> |
521 | <table> |
522 | |
523 | === modified file 'src/webcatalog/templates/webcatalog/install_options_snippet.html' |
524 | --- src/webcatalog/templates/webcatalog/install_options_snippet.html 2011-06-29 14:43:55 +0000 |
525 | +++ src/webcatalog/templates/webcatalog/install_options_snippet.html 2011-07-04 15:54:51 +0000 |
526 | @@ -2,14 +2,11 @@ |
527 | {% if message_text %} |
528 | {{ message_text }} |
529 | {% endif %}{% if display_install_button %} |
530 | - <a href="apt://{{ application.package_name }}" class="awesome"> |
531 | - {% if application.for_purchase %} |
532 | - Purchase {{ application.name }} |
533 | - {% else %} |
534 | - Install {{ application.name }} |
535 | - {% endif %} |
536 | - </a> |
537 | -{% endif %}{% if display_ubuntu_download %} |
538 | + <form action="apt://{{ application.package_name }}" method="GET"> |
539 | + <input type="submit" value="{% if application.for_purchase %}Purchase{% else %}Install{% endif %} {{ application.name }}" /> |
540 | + </form> |
541 | +{% endif %} |
542 | +{% if display_ubuntu_download %} |
543 | <a href="http://www.ubuntu.com/download" class="awesome">{% trans "Download Ubuntu" %}</a> |
544 | {% endif %} |
545 | |
546 | |
547 | === modified file 'src/webcatalog/templatetags/webcatalog.py' |
548 | --- src/webcatalog/templatetags/webcatalog.py 2011-06-29 15:37:50 +0000 |
549 | +++ src/webcatalog/templatetags/webcatalog.py 2011-07-04 15:54:51 +0000 |
550 | @@ -70,7 +70,7 @@ |
551 | distroseries__code_name=useragent.distroseries) |
552 | app_in_series_url = reverse( |
553 | 'wc-package-detail', |
554 | - args=[application.package_name, useragent.distroseries]) |
555 | + args=[useragent.distroseries, application.package_name]) |
556 | template_context['message_text'] = _( |
557 | '{application_name} is also available for ' |
558 | '<a href="{app_in_series_url}">your version of ' |
559 | |
560 | === modified file 'src/webcatalog/tests/test_commands.py' |
561 | --- src/webcatalog/tests/test_commands.py 2011-06-30 16:38:56 +0000 |
562 | +++ src/webcatalog/tests/test_commands.py 2011-07-04 15:54:51 +0000 |
563 | @@ -367,6 +367,7 @@ |
564 | def setUp(self): |
565 | curdir = os.path.dirname(__file__) |
566 | sca_apps_file = os.path.join(curdir, 'test_data', 'sca_apps.txt') |
567 | + self.distroseries = DistroSeries.objects.create(code_name='natty') |
568 | with open(sca_apps_file) as content: |
569 | self.response_content = content.read() |
570 | mock_response = Mock() |
571 | @@ -381,7 +382,8 @@ |
572 | apps_for_purchase = Application.objects.filter(for_purchase=True) |
573 | self.assertEqual(0, len(apps_for_purchase)) |
574 | call_command('import_for_purchase_apps') |
575 | - app_for_purchase = Application.objects.get(name='MyApp') |
576 | + app_for_purchase = Application.objects.get(name='MyApp', |
577 | + distroseries=self.distroseries) |
578 | self.assertEqual(True, app_for_purchase.for_purchase) |
579 | self.assertTrue(app_for_purchase.description.find('hello') > -1) |
580 | |
581 | @@ -399,3 +401,38 @@ |
582 | self.assertTrue(second > first) |
583 | # number of apps should not increase |
584 | self.assertEqual(second, third) |
585 | + |
586 | + @patch('urllib.urlopen') |
587 | + def test_app_gets_distroseries(self, mock_urllib): |
588 | + mock_urllib.return_value = self.mock_response |
589 | + call_command('import_for_purchase_apps') |
590 | + app = Application.objects.get(name='MyApp', |
591 | + distroseries=self.distroseries) |
592 | + self.assertEqual(2, len(app.available_distroseries())) |
593 | + |
594 | + @patch('urllib.urlopen') |
595 | + def test_app_gets_price(self, mock_urllib): |
596 | + mock_urllib.return_value = self.mock_response |
597 | + call_command('import_for_purchase_apps') |
598 | + app = Application.objects.get(name='MyApp', |
599 | + distroseries=self.distroseries) |
600 | + self.assertEqual(2.50, app.price) |
601 | + |
602 | + @patch('urllib.urlopen') |
603 | + def test_app_gets_updated(self, mock_urllib): |
604 | + mock_urllib.return_value = self.mock_response |
605 | + call_command('import_for_purchase_apps') |
606 | + updated = self.response_content.replace('hello', 'bye') |
607 | + self.mock_response.read.return_value = updated |
608 | + call_command('import_for_purchase_apps') |
609 | + app = Application.objects.get(name='MyApp', |
610 | + distroseries=self.distroseries) |
611 | + self.assertEqual('bye', app.package_name) |
612 | + |
613 | + @patch('urllib.urlopen') |
614 | + def test_app_gets_icon(self, mock_urllib): |
615 | + mock_urllib.return_value = self.mock_response |
616 | + call_command('import_for_purchase_apps') |
617 | + app = Application.objects.get(name='MyApp', |
618 | + distroseries=self.distroseries) |
619 | + self.assertEqual(6461, app.icon.size) |
620 | |
621 | === modified file 'src/webcatalog/tests/test_data/sca_apps.txt' |
622 | --- src/webcatalog/tests/test_data/sca_apps.txt 2011-06-17 21:27:33 +0000 |
623 | +++ src/webcatalog/tests/test_data/sca_apps.txt 2011-07-04 15:54:51 +0000 |
624 | @@ -4,14 +4,14 @@ |
625 | "signing_key_id": "", |
626 | "description": "MyAppTagline\nThe classic greeting, and a good example\r\n The GNU hello program produces a familiar, friendly greeting. It\r\n allows non-programmers to use a classic computer science tool which\r\n would otherwise be unavailable to them.\r\n .\r\n Seriously, though: this is an example of how to do a Debian package.\r\n It is the Debian version of the GNU Project's `hello world' program\r\n (which is itself an example for the GNU Project).", |
627 | "package_name": "hello", |
628 | - "series": {}, |
629 | - "price": "1", |
630 | + "series": {"maverick": ["i386", "amd64"], "natty": ["i386", "amd64"]}, |
631 | + "price": "2.50", |
632 | "archive_id": "launchpad_zematynnad2/myppa", |
633 | "icon_data": "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", |
634 | "screenshot_url": "", |
635 | "archive_root": "", |
636 | "tos_url": "", |
637 | - "icon_url": "http://localhost:8000/site_media/icons/2011/06/eg_64x64_______.png", |
638 | + "icon_url": "http://localhost:8000/site_media/icons/2011/06/eg_64x64.png", |
639 | "categories": "Audio", |
640 | "name": "MyApp" |
641 | } |
642 | |
643 | === modified file 'src/webcatalog/tests/test_models.py' |
644 | --- src/webcatalog/tests/test_models.py 2011-06-28 15:03:12 +0000 |
645 | +++ src/webcatalog/tests/test_models.py 2011-07-04 15:54:51 +0000 |
646 | @@ -76,7 +76,7 @@ |
647 | dept = app.departments.get() |
648 | expected = [{'name': 'Get Software', 'url': reverse('wc-index')}, |
649 | {'name': dept.name, 'url': reverse('wc-department', |
650 | - args=[dept.id])}, |
651 | + args=[app.distroseries.code_name, dept.id])}, |
652 | {'name': app.name, 'url': reverse('wc-package-detail', |
653 | args=[app.distroseries.code_name, app.package_name])}] |
654 | |
655 | @@ -89,16 +89,16 @@ |
656 | dept = app.departments.get() |
657 | expected = [{'name': 'Get Software', 'url': reverse('wc-index')}, |
658 | {'name': dept.parent.name, 'url': reverse('wc-department', |
659 | - args=[dept.parent.id])}, |
660 | + args=[app.distroseries.code_name, dept.parent.id])}, |
661 | {'name': dept.name, 'url': reverse('wc-department', |
662 | - args=[dept.id])}, |
663 | + args=[app.distroseries.code_name, dept.id])}, |
664 | {'name': app.name, 'url': reverse('wc-package-detail', |
665 | args=[app.distroseries.code_name, app.package_name])}] |
666 | |
667 | self.assertEquals(expected, app.crumbs()) |
668 | |
669 | |
670 | -def DepartmentTestCse(TestCaseWithFactory): |
671 | +def DepartmentTestCase(TestCaseWithFactory): |
672 | def test_normalized_name(self): |
673 | cases = { |
674 | 'Foo': 'Foo', |
675 | @@ -129,3 +129,14 @@ |
676 | args=[dept.id])}] |
677 | |
678 | self.assertEquals(expected, dept.crumbs()) |
679 | + |
680 | + def test_crumbs_with_distroseries(self): |
681 | + parent = self.factory.make_department('Foo') |
682 | + dept = self.factory.make_department('Bar', parent=parent) |
683 | + expected = [{'name': 'Get Software', 'url': reverse('wc-index')}, |
684 | + {'name': dept.parent.name, 'url': reverse('wc-department', |
685 | + args=['frobbly', dept.parent.id])}, |
686 | + {'name': dept.name, 'url': reverse('wc-department', |
687 | + args=['frobbly', dept.id])}] |
688 | + |
689 | + self.assertEquals(expected, dept.crumbs(distro='frobbly')) |
690 | |
691 | === modified file 'src/webcatalog/tests/test_templatetags.py' |
692 | --- src/webcatalog/tests/test_templatetags.py 2011-06-29 14:09:36 +0000 |
693 | +++ src/webcatalog/tests/test_templatetags.py 2011-07-04 15:54:51 +0000 |
694 | @@ -94,7 +94,7 @@ |
695 | context = Context( |
696 | dict(user_agent=self.make_useragent(ubuntu_version='10.04'))) |
697 | |
698 | - lucid_app_url = reverse('wc-package-detail', args=['pkgfoo', 'lucid']) |
699 | + lucid_app_url = reverse('wc-package-detail', args=['lucid', 'pkgfoo']) |
700 | self.assertEqual(dict( |
701 | application=natty_application, display_install_button=False, |
702 | message_text='Package Foo is also available for ' |
703 | |
704 | === modified file 'src/webcatalog/tests/test_views.py' |
705 | --- src/webcatalog/tests/test_views.py 2011-07-01 17:04:53 +0000 |
706 | +++ src/webcatalog/tests/test_views.py 2011-07-04 15:54:51 +0000 |
707 | @@ -103,7 +103,7 @@ |
708 | def test_link_to_apt_package(self): |
709 | response, app = self.get_app_and_response() |
710 | |
711 | - self.assertContains(response, '<a href="apt://pkgfoo"') |
712 | + self.assertContains(response, 'action="apt://pkgfoo"') |
713 | |
714 | def test_button_for_non_puchase_app(self): |
715 | response, app = self.get_app_and_response() |
716 | @@ -129,7 +129,7 @@ |
717 | response, app = self.get_app_and_response(name="<a>Escape me", |
718 | useragent="blah Ubuntu/10.04 blah") |
719 | |
720 | - lucid_app_url = reverse('wc-package-detail', args=['pkgfoo', 'lucid']) |
721 | + lucid_app_url = reverse('wc-package-detail', args=['lucid', 'pkgfoo']) |
722 | self.assertContains(response, |
723 | '<a>Escape me is also available for ' |
724 | '<a href="{lucid_app_url}">your version of Ubuntu</a>.'.format( |
725 | @@ -313,9 +313,10 @@ |
726 | def test_department_contains_links_to_subdepartments(self): |
727 | dept = self.factory.make_department('foo') |
728 | subdept = self.factory.make_department('bar', parent=dept) |
729 | + distro = self.factory.make_distroseries(code_name='lucid') |
730 | |
731 | response = self.client.get(reverse('wc-department', args=[ |
732 | - settings.DEFAULT_DISTRO, dept.id])) |
733 | + 'lucid', dept.id])) |
734 | |
735 | self.assertContains(response, reverse('wc-department', |
736 | args=[subdept.id])) |
737 | @@ -333,9 +334,10 @@ |
738 | |
739 | def test_department_with_no_subdepts_doesnt_contain_header(self): |
740 | dept = self.factory.make_department('bar') |
741 | + distro = self.factory.make_distroseries(code_name='lucid') |
742 | |
743 | response = self.client.get(reverse('wc-department', args=[ |
744 | - settings.DEFAULT_DISTRO, dept.id])) |
745 | + 'lucid', dept.id])) |
746 | |
747 | self.assertNotContains(response, 'Subsections') |
748 | |
749 | @@ -361,7 +363,8 @@ |
750 | |
751 | def test_invalid_page_doesnt_error(self): |
752 | dept = self.factory.make_department('bar') |
753 | - url = reverse('wc-department', args=[settings.DEFAULT_DISTRO, dept.id]) |
754 | + distro = self.factory.make_distroseries(code_name='lucid') |
755 | + url = reverse('wc-department', args=['lucid', dept.id]) |
756 | response = self.client.get(url, data={'page': 'aef8'}) |
757 | page = response.context['page'] |
758 | self.assertEqual(1, page.number) |
759 | @@ -376,6 +379,8 @@ |
760 | |
761 | def test_department_includes_right_navigation(self): |
762 | dept = self.factory.make_department('bar') |
763 | + default = self.factory.make_distroseries( |
764 | + code_name=settings.DEFAULT_DISTRO, version='11.04') |
765 | lucid = self.factory.make_distroseries(code_name='lucid', |
766 | version='10.04') |
767 | maverick = self.factory.make_distroseries(code_name='maverick', |
768 | @@ -388,3 +393,10 @@ |
769 | url = reverse('wc-department', args=[ds, dept.id]) |
770 | self.assertContains(response, '<a href="{0}">Ubuntu'.format(url)) |
771 | |
772 | + def test_invalid_distroseries_returns_404(self): |
773 | + dept = self.factory.make_department('bar') |
774 | + |
775 | + response = self.client.get(reverse('wc-department', args=[ |
776 | + 'amnesiac', dept.id])) |
777 | + |
778 | + self.assertEqual(404, response.status_code) |
779 | \ No newline at end of file |
780 | |
781 | === modified file 'src/webcatalog/views.py' |
782 | --- src/webcatalog/views.py 2011-07-01 16:18:58 +0000 |
783 | +++ src/webcatalog/views.py 2011-07-04 15:54:51 +0000 |
784 | @@ -108,6 +108,9 @@ |
785 | return HttpResponseRedirect( |
786 | reverse('wc-department', args=[distro, dept_id])) |
787 | |
788 | + # Attempt to retrieve the DistroSeries to ensure that it actually exists |
789 | + get_object_or_404(DistroSeries, code_name=distro) |
790 | + |
791 | dept = get_object_or_404(Department, pk=dept_id) |
792 | subdepts = Department.objects.filter(parent=dept) |
793 | subdepts = subdepts.order_by('name') |
LGTM. Just update the copyright date on the templates before the final approval.