Merge lp:~michael.nelson/ubuntu-webcatalog/form-for-import-data into lp:ubuntu-webcatalog
- form-for-import-data
- Merge into trunk
Proposed by
Michael Nelson
Status: | Merged |
---|---|
Merged at revision: | 6 |
Proposed branch: | lp:~michael.nelson/ubuntu-webcatalog/form-for-import-data |
Merge into: | lp:ubuntu-webcatalog |
Diff against target: |
332 lines (+212/-32) 6 files modified
src/webcatalog/forms.py (+66/-0) src/webcatalog/management/commands/import_app_install_data.py (+15/-17) src/webcatalog/models.py (+14/-13) src/webcatalog/tests/__init__.py (+2/-1) src/webcatalog/tests/factory.py (+1/-1) src/webcatalog/tests/test_forms.py (+114/-0) |
To merge this branch: | bzr merge lp:~michael.nelson/ubuntu-webcatalog/form-for-import-data |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Anthony Lenton (community) | Approve | ||
Review via email: mp+57342@code.launchpad.net |
Commit message
Description of the change
Overview
========
This branch adds the remaining fields which the USC client parses from the desktop files.
Most of these fields are parsed from the desktop files in app-install-
I've also added simplified the importer by included a form for data validation, and ensured the form class can create instances based on the desktop file.
To post a comment you must log in.
- 10. By Michael Nelson
-
MimeType is not required, more helpful error when certain desktop files are ignored.
- 11. By Michael Nelson
-
Fixed the --local-
app-install- deb option. - 12. By Michael Nelson
-
Unicode strings when writing to stdout..
Revision history for this message
Anthony Lenton (elachuni) : | # |
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === added file 'src/webcatalog/forms.py' | |||
2 | --- src/webcatalog/forms.py 1970-01-01 00:00:00 +0000 | |||
3 | +++ src/webcatalog/forms.py 2011-04-12 15:56:34 +0000 | |||
4 | @@ -0,0 +1,66 @@ | |||
5 | 1 | # -*- coding: utf-8 -*- | ||
6 | 2 | # This file is part of the Ubuntu Web Catalog | ||
7 | 3 | # Copyright (C) 2011 Canonical Ltd. | ||
8 | 4 | # | ||
9 | 5 | # This program is free software: you can redistribute it and/or modify | ||
10 | 6 | # it under the terms of the GNU Affero General Public License as | ||
11 | 7 | # published by the Free Software Foundation, either version 3 of the | ||
12 | 8 | # License, or (at your option) any later version. | ||
13 | 9 | # | ||
14 | 10 | # This program is distributed in the hope that it will be useful, | ||
15 | 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
16 | 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
17 | 13 | # GNU Affero General Public License for more details. | ||
18 | 14 | # | ||
19 | 15 | # You should have received a copy of the GNU Affero General Public License | ||
20 | 16 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
21 | 17 | |||
22 | 18 | """Forms used by the web catalog.""" | ||
23 | 19 | |||
24 | 20 | from __future__ import ( | ||
25 | 21 | absolute_import, | ||
26 | 22 | with_statement, | ||
27 | 23 | ) | ||
28 | 24 | |||
29 | 25 | from ConfigParser import ConfigParser | ||
30 | 26 | from StringIO import StringIO | ||
31 | 27 | |||
32 | 28 | from django import forms | ||
33 | 29 | |||
34 | 30 | from webcatalog.models import Application | ||
35 | 31 | |||
36 | 32 | __metaclass__ = type | ||
37 | 33 | __all__ = [ | ||
38 | 34 | 'ApplicationForm', | ||
39 | 35 | 'desktop_field_mappings', | ||
40 | 36 | ] | ||
41 | 37 | |||
42 | 38 | desktop_field_mappings = { | ||
43 | 39 | 'x-appinstall-package': 'package_name', | ||
44 | 40 | 'x-appinstall-popcon': 'popcon', | ||
45 | 41 | 'x-appinstall-channel': 'channel', | ||
46 | 42 | 'x-appinstall-screenshot-url': 'screenshot_url', | ||
47 | 43 | 'x-appinstall-architectures': 'architectures', | ||
48 | 44 | 'x-appinstall-keywords': 'keywords', | ||
49 | 45 | 'x-appinstall-description': 'description', | ||
50 | 46 | 'x-appinstall-section': 'section', | ||
51 | 47 | 'type': 'app_type', | ||
52 | 48 | } | ||
53 | 49 | |||
54 | 50 | |||
55 | 51 | class ApplicationForm(forms.ModelForm): | ||
56 | 52 | |||
57 | 53 | class Meta: | ||
58 | 54 | model = Application | ||
59 | 55 | |||
60 | 56 | @classmethod | ||
61 | 57 | def get_form_from_desktop_data(cls, str_data): | ||
62 | 58 | parser = ConfigParser() | ||
63 | 59 | parser.readfp(StringIO(str_data)) | ||
64 | 60 | data = dict(parser.items('Desktop Entry')) | ||
65 | 61 | for desktop_key, app_key in desktop_field_mappings.items(): | ||
66 | 62 | if data.has_key(desktop_key): | ||
67 | 63 | data[app_key] = data[desktop_key] | ||
68 | 64 | del(data[desktop_key]) | ||
69 | 65 | |||
70 | 66 | return cls(data=data) | ||
71 | 0 | 67 | ||
72 | === modified file 'src/webcatalog/management/commands/import_app_install_data.py' | |||
73 | --- src/webcatalog/management/commands/import_app_install_data.py 2011-04-11 14:08:41 +0000 | |||
74 | +++ src/webcatalog/management/commands/import_app_install_data.py 2011-04-12 15:56:34 +0000 | |||
75 | @@ -25,13 +25,11 @@ | |||
76 | 25 | import tempfile | 25 | import tempfile |
77 | 26 | import urllib | 26 | import urllib |
78 | 27 | from apt_inst import DebFile | 27 | from apt_inst import DebFile |
79 | 28 | from ConfigParser import ConfigParser | ||
80 | 29 | from optparse import make_option | 28 | from optparse import make_option |
81 | 30 | from StringIO import StringIO | ||
82 | 31 | 29 | ||
83 | 32 | from django.core.management.base import BaseCommand | 30 | from django.core.management.base import BaseCommand |
84 | 33 | 31 | ||
86 | 34 | from webcatalog.models import Application | 32 | from webcatalog.forms import ApplicationForm |
87 | 35 | 33 | ||
88 | 36 | __metaclass__ = type | 34 | __metaclass__ = type |
89 | 37 | __all__ = [ | 35 | __all__ = [ |
90 | @@ -43,7 +41,7 @@ | |||
91 | 43 | help = "Update Application data from app-install-data package." | 41 | help = "Update Application data from app-install-data package." |
92 | 44 | option_list = BaseCommand.option_list + ( | 42 | option_list = BaseCommand.option_list + ( |
93 | 45 | make_option('--local-app-install-deb', | 43 | make_option('--local-app-install-deb', |
95 | 46 | action='store_true', | 44 | action='store', |
96 | 47 | dest='local_app_install_deb', | 45 | dest='local_app_install_deb', |
97 | 48 | default='', | 46 | default='', |
98 | 49 | help=('Use a local app-install-data deb package rather than ' | 47 | help=('Use a local app-install-data deb package rather than ' |
99 | @@ -52,7 +50,6 @@ | |||
100 | 52 | 50 | ||
101 | 53 | def handle(self, *args, **options): | 51 | def handle(self, *args, **options): |
102 | 54 | self.verbosity = int(options['verbosity']) | 52 | self.verbosity = int(options['verbosity']) |
103 | 55 | |||
104 | 56 | # Download app install data when requested. | 53 | # Download app install data when requested. |
105 | 57 | if options['local_app_install_deb'] == '': | 54 | if options['local_app_install_deb'] == '': |
106 | 58 | install_data_url = ("http://archive.ubuntu.com/" | 55 | install_data_url = ("http://archive.ubuntu.com/" |
107 | @@ -60,7 +57,7 @@ | |||
108 | 60 | "app-install-data_0.11.04.7.1_all.deb") | 57 | "app-install-data_0.11.04.7.1_all.deb") |
109 | 61 | if self.verbosity > 0: | 58 | if self.verbosity > 0: |
110 | 62 | self.stdout.write( | 59 | self.stdout.write( |
112 | 63 | "Downloading app-install-data-ubuntu from {0}...".format( | 60 | u"Downloading app-install-data-ubuntu from {0}...".format( |
113 | 64 | install_data_url)) | 61 | install_data_url)) |
114 | 65 | tmp_file = tempfile.NamedTemporaryFile() | 62 | tmp_file = tempfile.NamedTemporaryFile() |
115 | 66 | urllib.urlretrieve(install_data_url, tmp_file.name) | 63 | urllib.urlretrieve(install_data_url, tmp_file.name) |
116 | @@ -85,14 +82,15 @@ | |||
117 | 85 | 82 | ||
118 | 86 | def process_archive_member(self, member, data): | 83 | def process_archive_member(self, member, data): |
119 | 87 | if member.name.endswith('desktop'): | 84 | if member.name.endswith('desktop'): |
131 | 88 | parser = ConfigParser() | 85 | form = ApplicationForm.get_form_from_desktop_data(data) |
132 | 89 | parser.readfp(StringIO(data)) | 86 | |
133 | 90 | data = dict(parser.items('Desktop Entry')) | 87 | if form.is_valid(): |
134 | 91 | # TODO: Add a form and validate. | 88 | app = form.save() |
135 | 92 | Application.objects.create( | 89 | if self.verbosity > 0: |
136 | 93 | package_name=data['x-appinstall-package'], | 90 | self.stdout.write( |
137 | 94 | name=data['name'], | 91 | u"{0} created.\n".format(app.name)) |
138 | 95 | comment=data.get('comment', '')) | 92 | else: |
139 | 96 | if self.verbosity > 0: | 93 | if self.verbosity > 0: |
140 | 97 | self.stdout.write( | 94 | self.stdout.write( |
141 | 98 | "{0} created.\n".format(data['x-appinstall-package'])) | 95 | u"Skipping {0} as input failed validation: {1}.\n".format( |
142 | 96 | member.name, form.errors)) | ||
143 | 99 | 97 | ||
144 | === modified file 'src/webcatalog/models.py' | |||
145 | --- src/webcatalog/models.py 2011-04-08 12:25:25 +0000 | |||
146 | +++ src/webcatalog/models.py 2011-04-12 15:56:34 +0000 | |||
147 | @@ -39,27 +39,28 @@ | |||
148 | 39 | # The following fields are extracted from app-install-data. | 39 | # The following fields are extracted from app-install-data. |
149 | 40 | package_name = models.SlugField(max_length=100) | 40 | package_name = models.SlugField(max_length=100) |
150 | 41 | name = models.CharField(max_length=255) | 41 | name = models.CharField(max_length=255) |
152 | 42 | comment = models.CharField(max_length=255) | 42 | comment = models.CharField(max_length=255, blank=True) |
153 | 43 | popcon = models.IntegerField() | ||
154 | 44 | channel = models.CharField(max_length=255, blank=True) | ||
155 | 45 | screenshot_url = models.URLField(blank=True, | ||
156 | 46 | help_text="Only use this if it is other than the normal screenshot url.") | ||
157 | 47 | mimetype = models.CharField(max_length=255, blank=True) | ||
158 | 48 | architectures = models.CharField(max_length=255, blank=True) | ||
159 | 49 | keywords = models.CharField(max_length=255, blank=True) | ||
160 | 50 | app_type = models.CharField(max_length=32) | ||
161 | 51 | section = models.CharField(max_length=32) | ||
162 | 52 | categories = models.CharField(max_length=255) | ||
163 | 53 | |||
164 | 43 | # Other desktop fields used by s-c | 54 | # Other desktop fields used by s-c |
165 | 55 | gnome_full_name = models.CharField(max_length=255, blank=True) | ||
166 | 44 | # x-gnome-fullname | 56 | # x-gnome-fullname |
167 | 45 | # X-AppInstall-Ignore | 57 | # X-AppInstall-Ignore |
168 | 46 | # X-AppInstall-Package | ||
169 | 47 | # X-AppInstall-Section | ||
170 | 48 | # X-AppInstall-Channel | ||
171 | 49 | # X-AppInstall-Screenshot-Url | ||
172 | 50 | # Icon | 58 | # Icon |
173 | 51 | # Type | ||
174 | 52 | # X-AppInstall-Architectures | ||
175 | 53 | # X-AppInstall-Description | ||
176 | 54 | # X-AppInstall-Popcon | ||
177 | 55 | # Comment | ||
178 | 56 | # X-AppInstall-Keywords | ||
179 | 57 | # mime and categories too | ||
180 | 58 | 59 | ||
181 | 59 | # The following fields will need to be imported from the apt-cache | 60 | # The following fields will need to be imported from the apt-cache |
182 | 60 | # (using python-apt - as above we'll need access to info from different | 61 | # (using python-apt - as above we'll need access to info from different |
183 | 61 | # series etc.) | 62 | # series etc.) |
185 | 62 | description = models.TextField() | 63 | description = models.TextField(blank=True) |
186 | 63 | 64 | ||
187 | 64 | def __unicode__(self): | 65 | def __unicode__(self): |
188 | 65 | return u"{0} ({1})".format(self.name, self.package_name) | 66 | return u"{0} ({1})".format(self.name, self.package_name) |
189 | 66 | 67 | ||
190 | === modified file 'src/webcatalog/tests/__init__.py' | |||
191 | --- src/webcatalog/tests/__init__.py 2011-04-08 15:48:54 +0000 | |||
192 | +++ src/webcatalog/tests/__init__.py 2011-04-12 15:56:34 +0000 | |||
193 | @@ -16,5 +16,6 @@ | |||
194 | 16 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | 16 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
195 | 17 | 17 | ||
196 | 18 | """Import various view, model and other tests for django's default runner.""" | 18 | """Import various view, model and other tests for django's default runner.""" |
197 | 19 | from .test_forms import * | ||
198 | 20 | from .test_commands import * | ||
199 | 19 | from .test_views import * | 21 | from .test_views import * |
200 | 20 | from .test_commands import * | ||
201 | 21 | 22 | ||
202 | === modified file 'src/webcatalog/tests/factory.py' | |||
203 | --- src/webcatalog/tests/factory.py 2011-04-11 13:51:17 +0000 | |||
204 | +++ src/webcatalog/tests/factory.py 2011-04-12 15:56:34 +0000 | |||
205 | @@ -82,7 +82,7 @@ | |||
206 | 82 | 82 | ||
207 | 83 | return Application.objects.create( | 83 | return Application.objects.create( |
208 | 84 | package_name=package_name, name=name, comment=comment, | 84 | package_name=package_name, name=name, comment=comment, |
210 | 85 | description=description) | 85 | description=description, popcon=999) |
211 | 86 | 86 | ||
212 | 87 | def get_test_path(self, file_name): | 87 | def get_test_path(self, file_name): |
213 | 88 | return os.path.join( | 88 | return os.path.join( |
214 | 89 | 89 | ||
215 | === added file 'src/webcatalog/tests/test_forms.py' | |||
216 | --- src/webcatalog/tests/test_forms.py 1970-01-01 00:00:00 +0000 | |||
217 | +++ src/webcatalog/tests/test_forms.py 2011-04-12 15:56:34 +0000 | |||
218 | @@ -0,0 +1,114 @@ | |||
219 | 1 | # -*- coding: utf-8 -*- | ||
220 | 2 | # This file is part of the Ubuntu Web Catalog | ||
221 | 3 | # Copyright (C) 2011 Canonical Ltd. | ||
222 | 4 | # | ||
223 | 5 | # This program is free software: you can redistribute it and/or modify | ||
224 | 6 | # it under the terms of the GNU Affero General Public License as | ||
225 | 7 | # published by the Free Software Foundation, either version 3 of the | ||
226 | 8 | # License, or (at your option) any later version. | ||
227 | 9 | # | ||
228 | 10 | # This program is distributed in the hope that it will be useful, | ||
229 | 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
230 | 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
231 | 13 | # GNU Affero General Public License for more details. | ||
232 | 14 | # | ||
233 | 15 | # You should have received a copy of the GNU Affero General Public License | ||
234 | 16 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
235 | 17 | |||
236 | 18 | """Test cases for the web catalog forms.""" | ||
237 | 19 | |||
238 | 20 | from __future__ import ( | ||
239 | 21 | absolute_import, | ||
240 | 22 | with_statement, | ||
241 | 23 | ) | ||
242 | 24 | |||
243 | 25 | from textwrap import dedent | ||
244 | 26 | |||
245 | 27 | from webcatalog.forms import ( | ||
246 | 28 | ApplicationForm, | ||
247 | 29 | desktop_field_mappings, | ||
248 | 30 | ) | ||
249 | 31 | from webcatalog.models import Application | ||
250 | 32 | from webcatalog.tests.factory import TestCaseWithFactory | ||
251 | 33 | |||
252 | 34 | __metaclass__ = type | ||
253 | 35 | __all__ = [ | ||
254 | 36 | 'ApplicationFormTestCase', | ||
255 | 37 | ] | ||
256 | 38 | |||
257 | 39 | |||
258 | 40 | class ApplicationFormTestCase(TestCaseWithFactory): | ||
259 | 41 | |||
260 | 42 | def get_desktop_data(self, overrides): | ||
261 | 43 | data = { | ||
262 | 44 | 'Name': self.factory.get_unique_string( | ||
263 | 45 | prefix='App Name'), | ||
264 | 46 | 'X-AppInstall-Package': self.factory.get_unique_string( | ||
265 | 47 | prefix='pkg_name'), | ||
266 | 48 | 'X-AppInstall-Popcon': self.factory.get_unique_integer(), | ||
267 | 49 | 'X-AppInstall-Section': self.factory.get_unique_string( | ||
268 | 50 | prefix='section'), | ||
269 | 51 | 'Type': 'Application', | ||
270 | 52 | 'Categories': 'cat1;cat2', | ||
271 | 53 | } | ||
272 | 54 | data.update(overrides) | ||
273 | 55 | return data | ||
274 | 56 | |||
275 | 57 | def get_desktop_entry(self, data): | ||
276 | 58 | desktop_entry_template = dedent(""" | ||
277 | 59 | [Desktop Entry] | ||
278 | 60 | GenericName=Page Layout (Stable) | ||
279 | 61 | Terminal=false | ||
280 | 62 | Icon=scribus | ||
281 | 63 | X-StandardInstall=false | ||
282 | 64 | StartupWMClass=scribus | ||
283 | 65 | X-KDE-SubstituteUID=false | ||
284 | 66 | X-KDE-Username= | ||
285 | 67 | |||
286 | 68 | X-Ubuntu-Gettext-Domain=app-install-data""") | ||
287 | 69 | for key, value in data.items(): | ||
288 | 70 | desktop_entry_template += "\n{key}={value}".format( | ||
289 | 71 | key=key, value=value) | ||
290 | 72 | |||
291 | 73 | desktop_data = desktop_entry_template.format(**data) | ||
292 | 74 | |||
293 | 75 | return desktop_data | ||
294 | 76 | |||
295 | 77 | def test_get_form_from_desktop_data(self): | ||
296 | 78 | data = { | ||
297 | 79 | 'Name': 'My Package', | ||
298 | 80 | 'X-AppInstall-Package': 'mypkg', | ||
299 | 81 | } | ||
300 | 82 | desktop_entry = self.get_desktop_entry(self.get_desktop_data(data)) | ||
301 | 83 | |||
302 | 84 | form = ApplicationForm.get_form_from_desktop_data(desktop_entry) | ||
303 | 85 | |||
304 | 86 | self.assertTrue(form.is_valid()) | ||
305 | 87 | self.assertEqual('My Package', form.cleaned_data['name']) | ||
306 | 88 | self.assertEqual('mypkg', form.cleaned_data['package_name']) | ||
307 | 89 | |||
308 | 90 | def test_extra_fields(self): | ||
309 | 91 | # There are a bunch of extra fields which are also supported by | ||
310 | 92 | # the model form. | ||
311 | 93 | extra_desktop_info = { | ||
312 | 94 | 'X-AppInstall-Channel': 'channel-name', | ||
313 | 95 | 'X-AppInstall-Section': 'section-name', | ||
314 | 96 | 'X-AppInstall-Screenshot-Url': 'http://example.com/screenshot', | ||
315 | 97 | 'Type': 'Application', | ||
316 | 98 | 'X-AppInstall-Description': 'A description', | ||
317 | 99 | 'X-AppInstall-Architectures': 'i386r;amd64', | ||
318 | 100 | 'X-AppInstall-Popcon': 9876, | ||
319 | 101 | 'X-AppInstall-Keywords': 'jazz,rock,country', | ||
320 | 102 | 'Categories': 'Graphics;Jazz;Publishing', | ||
321 | 103 | 'MimeType': 'application/vnd.scribus;text/javascript;' | ||
322 | 104 | } | ||
323 | 105 | desktop_entry = self.get_desktop_entry(self.get_desktop_data( | ||
324 | 106 | extra_desktop_info)) | ||
325 | 107 | |||
326 | 108 | form = ApplicationForm.get_form_from_desktop_data(desktop_entry) | ||
327 | 109 | |||
328 | 110 | self.assertTrue(form.is_valid()) | ||
329 | 111 | for key, value in extra_desktop_info.items(): | ||
330 | 112 | form_key = desktop_field_mappings.get(key.lower(), key.lower()) | ||
331 | 113 | self.assertEqual( | ||
332 | 114 | extra_desktop_info[key], form.cleaned_data[form_key]) |