Merge lp:~canonical-isd-hackers/ubuntu-webcatalog/purchase_777469 into lp:ubuntu-webcatalog
- purchase_777469
- Merge into trunk
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 | ||||
Related bugs: |
|
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-
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
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) |
Nice work!