Merge lp:~canonical-isd-hackers/ubuntu-webcatalog/purchase_777469 into lp:ubuntu-webcatalog

Proposed by Danny Tamez
Status: Merged
Approved by: David Owen
Approved revision: 22
Merged at revision: 20
Proposed branch: lp:~canonical-isd-hackers/ubuntu-webcatalog/purchase_777469
Merge into: lp:ubuntu-webcatalog
Diff against target: 443 lines (+275/-16)
13 files modified
django_project/settings.py (+1/-0)
src/webcatalog/admin.py (+1/-0)
src/webcatalog/forms.py (+1/-1)
src/webcatalog/management/commands/import_for_purchase_apps.py (+63/-0)
src/webcatalog/migrations/0002_add_for_purchase_flag_to_apps.py (+58/-0)
src/webcatalog/migrations/0003_add_archive_id_to_apps.py (+59/-0)
src/webcatalog/models.py (+13/-0)
src/webcatalog/templates/webcatalog/application_detail.html (+1/-1)
src/webcatalog/tests/sca_apps.txt (+18/-0)
src/webcatalog/tests/test_commands.py (+41/-10)
src/webcatalog/tests/test_views.py (+17/-1)
src/webcatalog/urls.py (+1/-1)
src/webcatalog/views.py (+1/-2)
To merge this branch: bzr merge lp:~canonical-isd-hackers/ubuntu-webcatalog/purchase_777469
Reviewer Review Type Date Requested Status
David Owen (community) Approve
Review via email: mp+65035@code.launchpad.net

Commit message

Support for import sca for purchase applications.

Description of the change

This branch adds a management command that will hit the configured api url
for software-center-agent and add to webcatalog applications that it finds.
The applications are marked as for purchase and are uniquely identified by
thier archive_id. In the UI the download button will instead read "Purchase"
for these applications.

To post a comment you must log in.
20. By Danny Tamez

Put in real url for staging.

21. By Danny Tamez

Changing acceptable response codes to only 200

22. By Danny Tamez

Replacing eval with json

Revision history for this message
David Owen (dsowen) wrote :

Nice work!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'django_project/settings.py'
2--- django_project/settings.py 2011-06-17 10:24:40 +0000
3+++ django_project/settings.py 2011-06-17 18:16:34 +0000
4@@ -147,6 +147,7 @@
5
6 # Strictly for use in our dev environment:
7 SERVE_SITE_MEDIA = True
8+SCA_API_URL = 'https://sc.staging.ubuntu.com/api/2.0/'
9 DISK_APT_CACHE_LOCATION = '/tmp/webcat_cache'
10
11 try:
12
13=== modified file 'src/webcatalog/admin.py'
14--- src/webcatalog/admin.py 2011-04-13 01:46:00 +0000
15+++ src/webcatalog/admin.py 2011-06-17 18:16:34 +0000
16@@ -37,6 +37,7 @@
17 list_display = ('package_name', 'name', 'comment')
18 search_fields = ('package_name', 'name', 'comment')
19 list_filter = ('departments',)
20+ exclude = ('for_purchase', 'archive_id')
21
22 admin.site.register(Application, ApplicationAdmin)
23 admin.site.register(Department)
24
25=== modified file 'src/webcatalog/forms.py'
26--- src/webcatalog/forms.py 2011-05-06 09:46:17 +0000
27+++ src/webcatalog/forms.py 2011-06-17 18:16:34 +0000
28@@ -53,7 +53,7 @@
29
30 class Meta:
31 model = Application
32- exclude = ('distroseries',)
33+ exclude = ('distroseries','for_purchase', 'archive_id')
34
35 @classmethod
36 def get_form_from_desktop_data(cls, str_data):
37
38=== added file 'src/webcatalog/management/commands/import_for_purchase_apps.py'
39--- src/webcatalog/management/commands/import_for_purchase_apps.py 1970-01-01 00:00:00 +0000
40+++ src/webcatalog/management/commands/import_for_purchase_apps.py 2011-06-17 18:16:34 +0000
41@@ -0,0 +1,63 @@
42+# -*- coding: utf-8 -*-
43+# This file is part of the Ubuntu Web Catalog
44+# Copyright (C) 2011 Canonical Ltd.
45+#
46+# This program is free software: you can redistribute it and/or modify
47+# it under the terms of the GNU Affero General Public License as
48+# published by the Free Software Foundation, either version 3 of the
49+# License, or (at your option) any later version.
50+#
51+# This program is distributed in the hope that it will be useful,
52+# but WITHOUT ANY WARRANTY; without even the implied warranty of
53+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
54+# GNU Affero General Public License for more details.
55+#
56+# You should have received a copy of the GNU Affero General Public License
57+# along with this program. If not, see <http://www.gnu.org/licenses/>.
58+
59+"""Management command to import for purchase applications from Software Center."""
60+
61+from __future__ import (
62+ absolute_import,
63+ with_statement,
64+ )
65+
66+import json
67+import urllib
68+
69+from django.conf import settings
70+from django.core.management.base import BaseCommand
71+
72+from webcatalog.models import (
73+ Application,
74+)
75+
76+__metaclass__ = type
77+__all__ = [
78+ ]
79+
80+
81+class Command(BaseCommand):
82+ help = "Import for-purchase applications from Software Center."
83+ verbosity = 0
84+
85+ def handle(self, *args, **options):
86+ url = '%sapplications/any/ubuntu/any/any/' % settings.SCA_API_URL
87+ # call api and get apps for purchase
88+ response = urllib.urlopen(url)
89+ if response.code != 200:
90+ raise Exception("Couldn't connect to server at %s" % url)
91+ app_list = json.loads(response.read())
92+ for app_data in app_list:
93+ archive_id = app_data['archive_id']
94+ # see if we've already imported it
95+ try:
96+ app = Application.objects.get(archive_id=archive_id)
97+ continue
98+ except Application.DoesNotExist:
99+ pass
100+ # not already imported - save to webcatalog
101+ app = Application.from_json(app_data)
102+ # mark it as for purchase since it came from sca
103+ app.for_purchase = True
104+ app.save()
105
106=== added file 'src/webcatalog/migrations/0002_add_for_purchase_flag_to_apps.py'
107--- src/webcatalog/migrations/0002_add_for_purchase_flag_to_apps.py 1970-01-01 00:00:00 +0000
108+++ src/webcatalog/migrations/0002_add_for_purchase_flag_to_apps.py 2011-06-17 18:16:34 +0000
109@@ -0,0 +1,58 @@
110+# encoding: utf-8
111+import datetime
112+from south.db import db
113+from south.v2 import SchemaMigration
114+from django.db import models
115+
116+class Migration(SchemaMigration):
117+
118+ def forwards(self, orm):
119+
120+ # Adding field 'Application.for_purchase'
121+ db.add_column('webcatalog_application', 'for_purchase', self.gf('django.db.models.fields.BooleanField')(default=False), keep_default=False)
122+
123+
124+ def backwards(self, orm):
125+
126+ # Deleting field 'Application.for_purchase'
127+ db.delete_column('webcatalog_application', 'for_purchase')
128+
129+
130+ models = {
131+ 'webcatalog.application': {
132+ 'Meta': {'object_name': 'Application'},
133+ 'app_type': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
134+ 'architectures': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
135+ 'categories': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
136+ 'channel': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
137+ 'comment': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
138+ 'departments': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['webcatalog.Department']", 'symmetrical': 'False', 'blank': 'True'}),
139+ 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
140+ 'distroseries': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.DistroSeries']"}),
141+ 'for_purchase': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
142+ 'icon': ('django.db.models.fields.files.ImageField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
143+ 'icon_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
144+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
145+ 'keywords': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
146+ 'mimetype': ('django.db.models.fields.CharField', [], {'max_length': '2048', 'blank': 'True'}),
147+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
148+ 'package_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
149+ 'popcon': ('django.db.models.fields.IntegerField', [], {}),
150+ 'screenshot_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
151+ 'section': ('django.db.models.fields.CharField', [], {'max_length': '32'})
152+ },
153+ 'webcatalog.department': {
154+ 'Meta': {'object_name': 'Department'},
155+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
156+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
157+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Department']", 'null': 'True', 'blank': 'True'})
158+ },
159+ 'webcatalog.distroseries': {
160+ 'Meta': {'object_name': 'DistroSeries'},
161+ 'code_name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '20', 'db_index': 'True'}),
162+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
163+ 'version': ('django.db.models.fields.CharField', [], {'max_length': '10', 'blank': 'True'})
164+ }
165+ }
166+
167+ complete_apps = ['webcatalog']
168
169=== added file 'src/webcatalog/migrations/0003_add_archive_id_to_apps.py'
170--- src/webcatalog/migrations/0003_add_archive_id_to_apps.py 1970-01-01 00:00:00 +0000
171+++ src/webcatalog/migrations/0003_add_archive_id_to_apps.py 2011-06-17 18:16:34 +0000
172@@ -0,0 +1,59 @@
173+# encoding: utf-8
174+import datetime
175+from south.db import db
176+from south.v2 import SchemaMigration
177+from django.db import models
178+
179+class Migration(SchemaMigration):
180+
181+ def forwards(self, orm):
182+
183+ # Adding field 'Application.archive_id'
184+ db.add_column('webcatalog_application', 'archive_id', self.gf('django.db.models.fields.CharField')(db_index=True, max_length=64, unique=True, null=True, blank=True), keep_default=False)
185+
186+
187+ def backwards(self, orm):
188+
189+ # Deleting field 'Application.archive_id'
190+ db.delete_column('webcatalog_application', 'archive_id')
191+
192+
193+ models = {
194+ 'webcatalog.application': {
195+ 'Meta': {'object_name': 'Application'},
196+ 'app_type': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
197+ 'architectures': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
198+ 'archive_id': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}),
199+ 'categories': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
200+ 'channel': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
201+ 'comment': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
202+ 'departments': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['webcatalog.Department']", 'symmetrical': 'False', 'blank': 'True'}),
203+ 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
204+ 'distroseries': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.DistroSeries']"}),
205+ 'for_purchase': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
206+ 'icon': ('django.db.models.fields.files.ImageField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
207+ 'icon_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
208+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
209+ 'keywords': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
210+ 'mimetype': ('django.db.models.fields.CharField', [], {'max_length': '2048', 'blank': 'True'}),
211+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
212+ 'package_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
213+ 'popcon': ('django.db.models.fields.IntegerField', [], {}),
214+ 'screenshot_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
215+ 'section': ('django.db.models.fields.CharField', [], {'max_length': '32'})
216+ },
217+ 'webcatalog.department': {
218+ 'Meta': {'object_name': 'Department'},
219+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
220+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
221+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Department']", 'null': 'True', 'blank': 'True'})
222+ },
223+ 'webcatalog.distroseries': {
224+ 'Meta': {'object_name': 'DistroSeries'},
225+ 'code_name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '20', 'db_index': 'True'}),
226+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
227+ 'version': ('django.db.models.fields.CharField', [], {'max_length': '10', 'blank': 'True'})
228+ }
229+ }
230+
231+ complete_apps = ['webcatalog']
232
233=== modified file 'src/webcatalog/models.py'
234--- src/webcatalog/models.py 2011-05-06 15:03:41 +0000
235+++ src/webcatalog/models.py 2011-06-17 18:16:34 +0000
236@@ -82,6 +82,9 @@
237 # (using python-apt - as above we'll need access to info from different
238 # series etc.)
239 description = models.TextField(blank=True)
240+ for_purchase = models.BooleanField(default=False)
241+ archive_id = models.CharField(max_length=64, null=True,
242+ db_index=True, blank=True, unique=True)
243
244 def __unicode__(self):
245 return u"{0} ({1})".format(self.name, self.package_name)
246@@ -92,6 +95,16 @@
247 stripped = [x.strip() for x in self.categories.split(';')]
248 return set(x for x in stripped if x)
249
250+ @classmethod
251+ def from_json(cls, data):
252+ app = Application()
253+ for name in app._meta.get_all_field_names():
254+ try:
255+ setattr(app, name, data[name])
256+ except KeyError:
257+ pass
258+ return app
259+
260 def update_departments(self):
261 """Update the list of departments for this app"""
262 self.departments.clear()
263
264=== modified file 'src/webcatalog/templates/webcatalog/application_detail.html'
265--- src/webcatalog/templates/webcatalog/application_detail.html 2011-05-06 14:55:19 +0000
266+++ src/webcatalog/templates/webcatalog/application_detail.html 2011-06-17 18:16:34 +0000
267@@ -24,7 +24,7 @@
268 <img src="http://screenshots.ubuntu.com/thumbnail-with-version/{{ application.package_name }}/ignored" />
269 </div>
270 <p>{{ application.description }}</p>
271-<a href="apt://{{ application.package_name }}" class="awesome">Download {{ application.name }}</a>
272+ <a href="apt://{{ application.package_name }}" class="awesome">{%if application.for_purchase %}Install{% else %}Download{% endif %} {{ application.name }}</a>
273 </div>
274 <div class="license">
275 <table>
276
277=== added file 'src/webcatalog/tests/sca_apps.txt'
278--- src/webcatalog/tests/sca_apps.txt 1970-01-01 00:00:00 +0000
279+++ src/webcatalog/tests/sca_apps.txt 2011-06-17 18:16:34 +0000
280@@ -0,0 +1,18 @@
281+[
282+ {
283+ "status": "Published",
284+ "signing_key_id": "",
285+ "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).",
286+ "package_name": "hello",
287+ "series": {},
288+ "price": "1",
289+ "archive_id": "launchpad_zematynnad2/myppa",
290+ "icon_data": "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",
291+ "screenshot_url": "",
292+ "archive_root": "",
293+ "tos_url": "",
294+ "icon_url": "http://localhost:8000/site_media/icons/2011/06/eg_64x64_______.png",
295+ "categories": "Audio",
296+ "name": "MyApp"
297+ }
298+]
299\ No newline at end of file
300
301=== modified file 'src/webcatalog/tests/test_commands.py'
302--- src/webcatalog/tests/test_commands.py 2011-06-17 14:37:58 +0000
303+++ src/webcatalog/tests/test_commands.py 2011-06-17 18:16:34 +0000
304@@ -28,26 +28,19 @@
305
306 from django.conf import settings
307 from django.core.management import call_command
308-from django.test import TestCase
309 from mock import (
310 patch,
311- MagicMock,
312 Mock,
313 )
314
315-from webcatalog.models import (
316- Application,
317- DistroSeries,
318- )
319-from webcatalog.management.commands.import_app_install_data import (
320- Cache as CacheContextManager,
321- Command,
322- )
323+from webcatalog.models import Application
324+from webcatalog.management.commands.import_app_install_data import Command
325 from webcatalog.tests.factory import TestCaseWithFactory
326
327 __metaclass__ = type
328 __all__ = [
329 'ImportAppInstallTestCase',
330+ 'ImportForPurchaseAppsTestCase',
331 ]
332
333
334@@ -317,3 +310,41 @@
335 if self.use_mock_apt_cache:
336 self.mock_cache_class.assert_called_with(rootdir=series_cache)
337 self.assertEqual(1, self.mock_cache.update.call_count)
338+
339+class ImportForPurchaseAppsTestCase(TestCaseWithFactory):
340+
341+ def setUp(self):
342+ curdir = os.path.dirname(__file__)
343+ sca_apps_file = os.path.join(curdir, 'sca_apps.txt')
344+ with open(sca_apps_file) as content:
345+ self.response_content = content.read()
346+ mock_response = Mock()
347+ mock_response.code = 200
348+ mock_response.read = Mock()
349+ mock_response.read.return_value = self.response_content
350+ self.mock_response = mock_response
351+
352+ @patch('urllib.urlopen')
353+ def test_app_for_purchase_is_found(self, mock_urllib):
354+ mock_urllib.return_value = self.mock_response
355+ apps_for_purchase = Application.objects.filter(for_purchase=True)
356+ self.assertEqual(0, len(apps_for_purchase))
357+ call_command('import_for_purchase_apps')
358+ app_for_purchase = Application.objects.get(name='MyApp')
359+ self.assertEqual(True, app_for_purchase.for_purchase)
360+ self.assertTrue(app_for_purchase.description.find('hello') > -1)
361+
362+ @patch('urllib.urlopen')
363+ def test_app_is_not_created_if_already_in_webcatalog(self, mock_urllib):
364+ mock_urllib.return_value = self.mock_response
365+ apps = Application.objects.all()
366+ first = len(apps)
367+ call_command('import_for_purchase_apps')
368+ apps = Application.objects.all()
369+ second = len(apps)
370+ call_command('import_for_purchase_apps')
371+ apps = Application.objects.all()
372+ third = len(apps)
373+ self.assertTrue(second > first)
374+ # number of apps should not increase
375+ self.assertEqual(second, third)
376
377=== modified file 'src/webcatalog/tests/test_views.py'
378--- src/webcatalog/tests/test_views.py 2011-05-06 14:55:19 +0000
379+++ src/webcatalog/tests/test_views.py 2011-06-17 18:16:34 +0000
380@@ -23,7 +23,6 @@
381 )
382
383 from django.core.urlresolvers import reverse
384-from django.test import TestCase
385
386 from webcatalog.tests.factory import TestCaseWithFactory
387
388@@ -76,6 +75,23 @@
389
390 self.assertContains( response, '<a href="apt://pkgfoo"')
391
392+ def test_button_for_non_puchase_app(self):
393+ app = self.factory.make_application(package_name='pkgfoo')
394+ url = reverse('wc-package-detail', args=['pkgfoo'])
395+
396+ response = self.client.get(url)
397+
398+ self.assertContains( response, 'Download %s' % app.name)
399+
400+ def test_button_for_for_puchase_app(self):
401+ app = self.factory.make_application(package_name='pkgfoo')
402+ app.for_purchase = True
403+ app.save()
404+ url = reverse('wc-package-detail', args=['pkgfoo'])
405+
406+ response = self.client.get(url)
407+
408+ self.assertContains( response, 'Install %s' % app.name)
409
410 class SearchTestCase(TestCaseWithFactory):
411 def test_no_search_retrieves_no_apps(self):
412
413=== modified file 'src/webcatalog/urls.py'
414--- src/webcatalog/urls.py 2011-04-19 18:46:21 +0000
415+++ src/webcatalog/urls.py 2011-06-17 18:16:34 +0000
416@@ -21,7 +21,7 @@
417 absolute_import,
418 with_statement,
419 )
420-from django.conf.urls.defaults import patterns, include, url
421+from django.conf.urls.defaults import patterns, url
422 from django.views.generic.detail import DetailView
423
424 from webcatalog.models import Application
425
426=== modified file 'src/webcatalog/views.py'
427--- src/webcatalog/views.py 2011-04-13 15:00:07 +0000
428+++ src/webcatalog/views.py 2011-06-17 18:16:34 +0000
429@@ -24,7 +24,6 @@
430
431 import operator
432 from django.db.models import Q
433-from django.http import HttpResponse
434 from django.template import RequestContext
435 from django.shortcuts import (
436 get_object_or_404,
437@@ -75,4 +74,4 @@
438 'apps': apps,
439 })
440 return render_to_response('webcatalog/department_overview.html',
441- context_instance=context)
442\ No newline at end of file
443+ context_instance=context)

Subscribers

People subscribed via source and target branches