Merge lp:~elachuni/ubuntu-webcatalog/departments into lp:ubuntu-webcatalog
- departments
- Merge into trunk
Proposed by
Anthony Lenton
Status: | Merged |
---|---|
Approved by: | Michael Nelson |
Approved revision: | 9 |
Merged at revision: | 8 |
Proposed branch: | lp:~elachuni/ubuntu-webcatalog/departments |
Merge into: | lp:ubuntu-webcatalog |
Diff against target: |
764 lines (+663/-8) 8 files modified
src/webcatalog/admin.py (+6/-2) src/webcatalog/department_filters.py (+116/-0) src/webcatalog/fixtures/initial_data.json (+370/-0) src/webcatalog/management/commands/import_app_install_data.py (+3/-2) src/webcatalog/models.py (+37/-4) src/webcatalog/tests/__init__.py (+2/-0) src/webcatalog/tests/test_department_filters.py (+69/-0) src/webcatalog/tests/test_models.py (+60/-0) |
To merge this branch: | bzr merge lp:~elachuni/ubuntu-webcatalog/departments |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Michael Nelson (community) | Approve | ||
Review via email: mp+57424@code.launchpad.net |
Commit message
Description of the change
Overview
========
Add a Department model to loosely group applications.
Details
=======
This is needed to allow simple browsing of the site, to have apps grouped into departments like the software-center does.
A few tiny bugs were fixed while I was there, to fix failures I was getting when importing app data locally:
- Encoded unicode strings when writing to stdout in import_
- Changed Application.
- Extended max_length for Application.
- Allowed blank Application.
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'src/webcatalog/admin.py' | |||
2 | --- src/webcatalog/admin.py 2011-04-08 12:25:25 +0000 | |||
3 | +++ src/webcatalog/admin.py 2011-04-13 02:40:59 +0000 | |||
4 | @@ -22,7 +22,10 @@ | |||
5 | 22 | with_statement, | 22 | with_statement, |
6 | 23 | ) | 23 | ) |
7 | 24 | from django.contrib import admin | 24 | from django.contrib import admin |
9 | 25 | from webcatalog.models import Application | 25 | from webcatalog.models import ( |
10 | 26 | Application, | ||
11 | 27 | Department, | ||
12 | 28 | ) | ||
13 | 26 | 29 | ||
14 | 27 | __metaclass__ = type | 30 | __metaclass__ = type |
15 | 28 | __all__ = [ | 31 | __all__ = [ |
16 | @@ -33,6 +36,7 @@ | |||
17 | 33 | class ApplicationAdmin(admin.ModelAdmin): | 36 | class ApplicationAdmin(admin.ModelAdmin): |
18 | 34 | list_display = ('package_name', 'name', 'comment') | 37 | list_display = ('package_name', 'name', 'comment') |
19 | 35 | search_fields = ('package_name', 'name', 'comment') | 38 | search_fields = ('package_name', 'name', 'comment') |
21 | 36 | 39 | list_filter = ('departments',) | |
22 | 37 | 40 | ||
23 | 38 | admin.site.register(Application, ApplicationAdmin) | 41 | admin.site.register(Application, ApplicationAdmin) |
24 | 42 | admin.site.register(Department) | ||
25 | 39 | 43 | ||
26 | === added file 'src/webcatalog/department_filters.py' | |||
27 | --- src/webcatalog/department_filters.py 1970-01-01 00:00:00 +0000 | |||
28 | +++ src/webcatalog/department_filters.py 2011-04-13 02:40:59 +0000 | |||
29 | @@ -0,0 +1,116 @@ | |||
30 | 1 | # -*- coding: utf-8 -*- | ||
31 | 2 | # This file is part of the Ubuntu Web Catalog | ||
32 | 3 | # Copyright (C) 2011 Canonical Ltd. | ||
33 | 4 | # | ||
34 | 5 | # This program is free software: you can redistribute it and/or modify | ||
35 | 6 | # it under the terms of the GNU Affero General Public License as | ||
36 | 7 | # published by the Free Software Foundation, either version 3 of the | ||
37 | 8 | # License, or (at your option) any later version. | ||
38 | 9 | # | ||
39 | 10 | # This program is distributed in the hope that it will be useful, | ||
40 | 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
41 | 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
42 | 13 | # GNU Affero General Public License for more details. | ||
43 | 14 | # | ||
44 | 15 | # You should have received a copy of the GNU Affero General Public License | ||
45 | 16 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
46 | 17 | |||
47 | 18 | """Department filters.""" | ||
48 | 19 | |||
49 | 20 | from __future__ import ( | ||
50 | 21 | absolute_import, | ||
51 | 22 | with_statement, | ||
52 | 23 | ) | ||
53 | 24 | |||
54 | 25 | import re | ||
55 | 26 | |||
56 | 27 | from django.db import models | ||
57 | 28 | |||
58 | 29 | __metaclass__ = type | ||
59 | 30 | __all__ = [ | ||
60 | 31 | 'department_filters', | ||
61 | 32 | ] | ||
62 | 33 | |||
63 | 34 | |||
64 | 35 | def category_filter(categories_set): | ||
65 | 36 | """Returns a filter func that checks an app for certain categories.""" | ||
66 | 37 | def filter_func(app): | ||
67 | 38 | return bool(app.categories_set.intersection(categories_set)) | ||
68 | 39 | return filter_func | ||
69 | 40 | |||
70 | 41 | def package_name_filter(name_regex): | ||
71 | 42 | """Returns a filter func that checks if an app's name matches a regex""" | ||
72 | 43 | def filter_func(app): | ||
73 | 44 | return re.match(name_regex, app.package_name) is not None | ||
74 | 45 | return filter_func | ||
75 | 46 | |||
76 | 47 | def section_filter(sections): | ||
77 | 48 | """Returns a filter that checks if an app is in certain sections.""" | ||
78 | 49 | def filter_func(app): | ||
79 | 50 | return app.section in sections | ||
80 | 51 | return filter_func | ||
81 | 52 | |||
82 | 53 | # Taken from https://wiki.ubuntu.com/SoftwareCenter#line-793 | ||
83 | 54 | |||
84 | 55 | department_filters = { | ||
85 | 56 | 'Accessories': [category_filter(set(['Utility', 'System']))], | ||
86 | 57 | 'Education': [category_filter(set(['Education']))], | ||
87 | 58 | 'Fonts': [package_name_filter(r'[to]tf-.*')], | ||
88 | 59 | 'Games': [category_filter(set(['Game', 'Sports']))], | ||
89 | 60 | 'Board Games': [category_filter(set(['BoardGame']))], | ||
90 | 61 | 'Card Games': [category_filter(set(['CardGame']))], | ||
91 | 62 | 'Puzzles': [category_filter(set(['LogicGame']))], | ||
92 | 63 | 'Role-Playing': [category_filter(set(['RolePlaying']))], | ||
93 | 64 | 'Sports': [category_filter(set(['SportsGame']))], | ||
94 | 65 | 'Graphics': [category_filter(set(['Graphics']))], | ||
95 | 66 | '3D': [category_filter(set(['3DGraphics']))], | ||
96 | 67 | 'Drawing': [category_filter(set(['VectorGraphics']))], | ||
97 | 68 | 'Painting': [category_filter(set(['RasterGraphics']))], | ||
98 | 69 | 'Photography': [category_filter(set(['Photography']))], | ||
99 | 70 | 'Publishing': [category_filter(set(['Publishing']))], | ||
100 | 71 | 'Scanning & OCR': [category_filter(set(['Scanning', 'OCR']))], | ||
101 | 72 | 'Viewers': [category_filter(set(['Viewer']))], | ||
102 | 73 | 'Internet': [category_filter(set(['Network']))], | ||
103 | 74 | 'Chat': [category_filter(set(['InstantMessaging', 'IRCClient']))], | ||
104 | 75 | 'File Sharing': [category_filter(set(['FileTransfer']))], | ||
105 | 76 | 'Mail': [category_filter(set(['Email']))], | ||
106 | 77 | 'Web Browsers': [category_filter(set(['WebBrowser']))], | ||
107 | 78 | 'Office': [category_filter(set(['Office']))], | ||
108 | 79 | 'Science & Engineering': [category_filter(set(['Science'])), | ||
109 | 80 | section_filter(['science'])], | ||
110 | 81 | 'Astronomy': [category_filter(set(['Astronomy']))], | ||
111 | 82 | 'Biology': [category_filter(set(['Biology']))], | ||
112 | 83 | 'Chemistry': [category_filter(set(['Chemistry']))], | ||
113 | 84 | 'Computing & Robotics': [category_filter(set(['ArtificialIntelligence', | ||
114 | 85 | 'ComputerScience', 'Robotics']))], | ||
115 | 86 | 'Electronics': [category_filter(set(['Electronics']))], | ||
116 | 87 | 'Engineering': [category_filter(set(['Engineering']))], | ||
117 | 88 | 'Geography': [category_filter(set(['Geography']))], | ||
118 | 89 | 'Geology': [category_filter(set(['Geology', 'Geoscience']))], | ||
119 | 90 | 'Mathematics': [category_filter(set(['DataVisualization', 'Math', | ||
120 | 91 | 'NumericalAnalysis'])), section_filter(['math', 'gnu-r'])], | ||
121 | 92 | 'Medicine': [category_filter(set(['MedicalSoftware']))], | ||
122 | 93 | 'Physics': [category_filter(set(['Electricity', 'Physics']))], | ||
123 | 94 | 'Sound & Video': [category_filter(set(['AudioVideo', 'Audio', 'Video']))], | ||
124 | 95 | 'Themes & Tweaks': [category_filter(set(['Settings']))], | ||
125 | 96 | 'Universal Access': [category_filter(set(['Accessibility']))], | ||
126 | 97 | 'Developer Tools': [category_filter(set(['Development'])), | ||
127 | 98 | section_filter(['devel'])], | ||
128 | 99 | 'Debugging': [category_filter(set(['Debugger']))], | ||
129 | 100 | 'Graphic Interface Design': [category_filter(set(['GUIDesigner']))], | ||
130 | 101 | 'Haskell': [section_filter(['haskell'])], | ||
131 | 102 | 'IDEs': [category_filter(set(['IDE']))], | ||
132 | 103 | 'Java': [section_filter(['java'])], | ||
133 | 104 | 'Libraries': [section_filter(['libdevel'])], | ||
134 | 105 | 'Lisp': [section_filter(['lisp'])], | ||
135 | 106 | 'Localization': [category_filter(set(['Translation']))], | ||
136 | 107 | 'Mono/CLI': [section_filter(['cli-mono'])], | ||
137 | 108 | 'OCaml': [section_filter(['ocaml'])], | ||
138 | 109 | 'Perl': [section_filter(['perl'])], | ||
139 | 110 | 'Profiling': [category_filter(set(['Profiling']))], | ||
140 | 111 | 'Python': [section_filter(['python'])], | ||
141 | 112 | 'Ruby': [section_filter(['ruby'])], | ||
142 | 113 | 'Version Control': [category_filter(set(['RevisionControl'])), | ||
143 | 114 | section_filter(['vcs'])], | ||
144 | 115 | 'Web Development': [category_filter(set(['WebDevelopment']))], | ||
145 | 116 | } | ||
146 | 0 | 117 | ||
147 | === added directory 'src/webcatalog/fixtures' | |||
148 | === added file 'src/webcatalog/fixtures/initial_data.json' | |||
149 | --- src/webcatalog/fixtures/initial_data.json 1970-01-01 00:00:00 +0000 | |||
150 | +++ src/webcatalog/fixtures/initial_data.json 2011-04-13 02:40:59 +0000 | |||
151 | @@ -0,0 +1,370 @@ | |||
152 | 1 | [ | ||
153 | 2 | { | ||
154 | 3 | "pk": 1, | ||
155 | 4 | "model": "webcatalog.department", | ||
156 | 5 | "fields": { | ||
157 | 6 | "name": "Games", | ||
158 | 7 | "parent": null | ||
159 | 8 | } | ||
160 | 9 | }, | ||
161 | 10 | { | ||
162 | 11 | "pk": 2, | ||
163 | 12 | "model": "webcatalog.department", | ||
164 | 13 | "fields": { | ||
165 | 14 | "name": "Office", | ||
166 | 15 | "parent": null | ||
167 | 16 | } | ||
168 | 17 | }, | ||
169 | 18 | { | ||
170 | 19 | "pk": 3, | ||
171 | 20 | "model": "webcatalog.department", | ||
172 | 21 | "fields": { | ||
173 | 22 | "name": "Sound & Video", | ||
174 | 23 | "parent": null | ||
175 | 24 | } | ||
176 | 25 | }, | ||
177 | 26 | { | ||
178 | 27 | "pk": 4, | ||
179 | 28 | "model": "webcatalog.department", | ||
180 | 29 | "fields": { | ||
181 | 30 | "name": "Developer Tools", | ||
182 | 31 | "parent": null | ||
183 | 32 | } | ||
184 | 33 | }, | ||
185 | 34 | { | ||
186 | 35 | "pk": 5, | ||
187 | 36 | "model": "webcatalog.department", | ||
188 | 37 | "fields": { | ||
189 | 38 | "name": "Science & Engineering", | ||
190 | 39 | "parent": null | ||
191 | 40 | } | ||
192 | 41 | }, | ||
193 | 42 | { | ||
194 | 43 | "pk": 6, | ||
195 | 44 | "model": "webcatalog.department", | ||
196 | 45 | "fields": { | ||
197 | 46 | "name": "Education", | ||
198 | 47 | "parent": null | ||
199 | 48 | } | ||
200 | 49 | }, | ||
201 | 50 | { | ||
202 | 51 | "pk": 7, | ||
203 | 52 | "model": "webcatalog.department", | ||
204 | 53 | "fields": { | ||
205 | 54 | "name": "Biology", | ||
206 | 55 | "parent": 5 | ||
207 | 56 | } | ||
208 | 57 | }, | ||
209 | 58 | { | ||
210 | 59 | "pk": 8, | ||
211 | 60 | "model": "webcatalog.department", | ||
212 | 61 | "fields": { | ||
213 | 62 | "name": "Accessories", | ||
214 | 63 | "parent": null | ||
215 | 64 | } | ||
216 | 65 | }, | ||
217 | 66 | { | ||
218 | 67 | "pk": 9, | ||
219 | 68 | "model": "webcatalog.department", | ||
220 | 69 | "fields": { | ||
221 | 70 | "name": "Role-Playing", | ||
222 | 71 | "parent": 1 | ||
223 | 72 | } | ||
224 | 73 | }, | ||
225 | 74 | { | ||
226 | 75 | "pk": 10, | ||
227 | 76 | "model": "webcatalog.department", | ||
228 | 77 | "fields": { | ||
229 | 78 | "name": "Geography", | ||
230 | 79 | "parent": 5 | ||
231 | 80 | } | ||
232 | 81 | }, | ||
233 | 82 | { | ||
234 | 83 | "pk": 11, | ||
235 | 84 | "model": "webcatalog.department", | ||
236 | 85 | "fields": { | ||
237 | 86 | "name": "Medicine", | ||
238 | 87 | "parent": 5 | ||
239 | 88 | } | ||
240 | 89 | }, | ||
241 | 90 | { | ||
242 | 91 | "pk": 12, | ||
243 | 92 | "model": "webcatalog.department", | ||
244 | 93 | "fields": { | ||
245 | 94 | "name": "Viewers", | ||
246 | 95 | "parent": 13 | ||
247 | 96 | } | ||
248 | 97 | }, | ||
249 | 98 | { | ||
250 | 99 | "pk": 13, | ||
251 | 100 | "model": "webcatalog.department", | ||
252 | 101 | "fields": { | ||
253 | 102 | "name": "Graphics", | ||
254 | 103 | "parent": null | ||
255 | 104 | } | ||
256 | 105 | }, | ||
257 | 106 | { | ||
258 | 107 | "pk": 14, | ||
259 | 108 | "model": "webcatalog.department", | ||
260 | 109 | "fields": { | ||
261 | 110 | "name": "Themes & Tweaks", | ||
262 | 111 | "parent": null | ||
263 | 112 | } | ||
264 | 113 | }, | ||
265 | 114 | { | ||
266 | 115 | "pk": 15, | ||
267 | 116 | "model": "webcatalog.department", | ||
268 | 117 | "fields": { | ||
269 | 118 | "name": "Internet", | ||
270 | 119 | "parent": null | ||
271 | 120 | } | ||
272 | 121 | }, | ||
273 | 122 | { | ||
274 | 123 | "pk": 16, | ||
275 | 124 | "model": "webcatalog.department", | ||
276 | 125 | "fields": { | ||
277 | 126 | "name": "Debugging", | ||
278 | 127 | "parent": 4 | ||
279 | 128 | } | ||
280 | 129 | }, | ||
281 | 130 | { | ||
282 | 131 | "pk": 17, | ||
283 | 132 | "model": "webcatalog.department", | ||
284 | 133 | "fields": { | ||
285 | 134 | "name": "Profiling", | ||
286 | 135 | "parent": 4 | ||
287 | 136 | } | ||
288 | 137 | }, | ||
289 | 138 | { | ||
290 | 139 | "pk": 18, | ||
291 | 140 | "model": "webcatalog.department", | ||
292 | 141 | "fields": { | ||
293 | 142 | "name": "Chat", | ||
294 | 143 | "parent": 15 | ||
295 | 144 | } | ||
296 | 145 | }, | ||
297 | 146 | { | ||
298 | 147 | "pk": 19, | ||
299 | 148 | "model": "webcatalog.department", | ||
300 | 149 | "fields": { | ||
301 | 150 | "name": "IDEs", | ||
302 | 151 | "parent": 4 | ||
303 | 152 | } | ||
304 | 153 | }, | ||
305 | 154 | { | ||
306 | 155 | "pk": 20, | ||
307 | 156 | "model": "webcatalog.department", | ||
308 | 157 | "fields": { | ||
309 | 158 | "name": "3D", | ||
310 | 159 | "parent": 13 | ||
311 | 160 | } | ||
312 | 161 | }, | ||
313 | 162 | { | ||
314 | 163 | "pk": 21, | ||
315 | 164 | "model": "webcatalog.department", | ||
316 | 165 | "fields": { | ||
317 | 166 | "name": "Engineering", | ||
318 | 167 | "parent": 5 | ||
319 | 168 | } | ||
320 | 169 | }, | ||
321 | 170 | { | ||
322 | 171 | "pk": 22, | ||
323 | 172 | "model": "webcatalog.department", | ||
324 | 173 | "fields": { | ||
325 | 174 | "name": "Electronics", | ||
326 | 175 | "parent": 5 | ||
327 | 176 | } | ||
328 | 177 | }, | ||
329 | 178 | { | ||
330 | 179 | "pk": 23, | ||
331 | 180 | "model": "webcatalog.department", | ||
332 | 181 | "fields": { | ||
333 | 182 | "name": "Web Browsers", | ||
334 | 183 | "parent": 15 | ||
335 | 184 | } | ||
336 | 185 | }, | ||
337 | 186 | { | ||
338 | 187 | "pk": 24, | ||
339 | 188 | "model": "webcatalog.department", | ||
340 | 189 | "fields": { | ||
341 | 190 | "name": "Mathematics", | ||
342 | 191 | "parent": 5 | ||
343 | 192 | } | ||
344 | 193 | }, | ||
345 | 194 | { | ||
346 | 195 | "pk": 25, | ||
347 | 196 | "model": "webcatalog.department", | ||
348 | 197 | "fields": { | ||
349 | 198 | "name": "Chemistry", | ||
350 | 199 | "parent": 5 | ||
351 | 200 | } | ||
352 | 201 | }, | ||
353 | 202 | { | ||
354 | 203 | "pk": 26, | ||
355 | 204 | "model": "webcatalog.department", | ||
356 | 205 | "fields": { | ||
357 | 206 | "name": "Physics", | ||
358 | 207 | "parent": 5 | ||
359 | 208 | } | ||
360 | 209 | }, | ||
361 | 210 | { | ||
362 | 211 | "pk": 27, | ||
363 | 212 | "model": "webcatalog.department", | ||
364 | 213 | "fields": { | ||
365 | 214 | "name": "FileSharing", | ||
366 | 215 | "parent": null | ||
367 | 216 | } | ||
368 | 217 | }, | ||
369 | 218 | { | ||
370 | 219 | "pk": 28, | ||
371 | 220 | "model": "webcatalog.department", | ||
372 | 221 | "fields": { | ||
373 | 222 | "name": "Mail", | ||
374 | 223 | "parent": 15 | ||
375 | 224 | } | ||
376 | 225 | }, | ||
377 | 226 | { | ||
378 | 227 | "pk": 29, | ||
379 | 228 | "model": "webcatalog.department", | ||
380 | 229 | "fields": { | ||
381 | 230 | "name": "Computing & Robotics", | ||
382 | 231 | "parent": 5 | ||
383 | 232 | } | ||
384 | 233 | }, | ||
385 | 234 | { | ||
386 | 235 | "pk": 30, | ||
387 | 236 | "model": "webcatalog.department", | ||
388 | 237 | "fields": { | ||
389 | 238 | "name": "Web Development", | ||
390 | 239 | "parent": 4 | ||
391 | 240 | } | ||
392 | 241 | }, | ||
393 | 242 | { | ||
394 | 243 | "pk": 31, | ||
395 | 244 | "model": "webcatalog.department", | ||
396 | 245 | "fields": { | ||
397 | 246 | "name": "Graphic Interface Design", | ||
398 | 247 | "parent": 4 | ||
399 | 248 | } | ||
400 | 249 | }, | ||
401 | 250 | { | ||
402 | 251 | "pk": 32, | ||
403 | 252 | "model": "webcatalog.department", | ||
404 | 253 | "fields": { | ||
405 | 254 | "name": "Version Control", | ||
406 | 255 | "parent": 4 | ||
407 | 256 | } | ||
408 | 257 | }, | ||
409 | 258 | { | ||
410 | 259 | "pk": 33, | ||
411 | 260 | "model": "webcatalog.department", | ||
412 | 261 | "fields": { | ||
413 | 262 | "name": "Photography", | ||
414 | 263 | "parent": 13 | ||
415 | 264 | } | ||
416 | 265 | }, | ||
417 | 266 | { | ||
418 | 267 | "pk": 34, | ||
419 | 268 | "model": "webcatalog.department", | ||
420 | 269 | "fields": { | ||
421 | 270 | "name": "Astronomy", | ||
422 | 271 | "parent": 5 | ||
423 | 272 | } | ||
424 | 273 | }, | ||
425 | 274 | { | ||
426 | 275 | "pk": 35, | ||
427 | 276 | "model": "webcatalog.department", | ||
428 | 277 | "fields": { | ||
429 | 278 | "name": "Universal Access", | ||
430 | 279 | "parent": null | ||
431 | 280 | } | ||
432 | 281 | }, | ||
433 | 282 | { | ||
434 | 283 | "pk": 36, | ||
435 | 284 | "model": "webcatalog.department", | ||
436 | 285 | "fields": { | ||
437 | 286 | "name": "Drawing", | ||
438 | 287 | "parent": 13 | ||
439 | 288 | } | ||
440 | 289 | }, | ||
441 | 290 | { | ||
442 | 291 | "pk": 37, | ||
443 | 292 | "model": "webcatalog.department", | ||
444 | 293 | "fields": { | ||
445 | 294 | "name": "Painting", | ||
446 | 295 | "parent": 13 | ||
447 | 296 | } | ||
448 | 297 | }, | ||
449 | 298 | { | ||
450 | 299 | "pk": 38, | ||
451 | 300 | "model": "webcatalog.department", | ||
452 | 301 | "fields": { | ||
453 | 302 | "name": "Publishing", | ||
454 | 303 | "parent": 13 | ||
455 | 304 | } | ||
456 | 305 | }, | ||
457 | 306 | { | ||
458 | 307 | "pk": 39, | ||
459 | 308 | "model": "webcatalog.department", | ||
460 | 309 | "fields": { | ||
461 | 310 | "name": "Localization", | ||
462 | 311 | "parent": 4 | ||
463 | 312 | } | ||
464 | 313 | }, | ||
465 | 314 | { | ||
466 | 315 | "pk": 40, | ||
467 | 316 | "model": "webcatalog.department", | ||
468 | 317 | "fields": { | ||
469 | 318 | "name": "Scanning & OCR", | ||
470 | 319 | "parent": 13 | ||
471 | 320 | } | ||
472 | 321 | }, | ||
473 | 322 | { | ||
474 | 323 | "pk": 41, | ||
475 | 324 | "model": "webcatalog.department", | ||
476 | 325 | "fields": { | ||
477 | 326 | "name": "Geology", | ||
478 | 327 | "parent": 5 | ||
479 | 328 | } | ||
480 | 329 | }, | ||
481 | 330 | { | ||
482 | 331 | "pk": 42, | ||
483 | 332 | "model": "webcatalog.department", | ||
484 | 333 | "fields": { | ||
485 | 334 | "name": "Board Games", | ||
486 | 335 | "parent": 1 | ||
487 | 336 | } | ||
488 | 337 | }, | ||
489 | 338 | { | ||
490 | 339 | "pk": 43, | ||
491 | 340 | "model": "webcatalog.department", | ||
492 | 341 | "fields": { | ||
493 | 342 | "name": "Puzzles", | ||
494 | 343 | "parent": 1 | ||
495 | 344 | } | ||
496 | 345 | }, | ||
497 | 346 | { | ||
498 | 347 | "pk": 44, | ||
499 | 348 | "model": "webcatalog.department", | ||
500 | 349 | "fields": { | ||
501 | 350 | "name": "File Sharing", | ||
502 | 351 | "parent": 15 | ||
503 | 352 | } | ||
504 | 353 | }, | ||
505 | 354 | { | ||
506 | 355 | "pk": 45, | ||
507 | 356 | "model": "webcatalog.department", | ||
508 | 357 | "fields": { | ||
509 | 358 | "name": "Sports", | ||
510 | 359 | "parent": 1 | ||
511 | 360 | } | ||
512 | 361 | }, | ||
513 | 362 | { | ||
514 | 363 | "pk": 46, | ||
515 | 364 | "model": "webcatalog.department", | ||
516 | 365 | "fields": { | ||
517 | 366 | "name": "Card Games", | ||
518 | 367 | "parent": 1 | ||
519 | 368 | } | ||
520 | 369 | } | ||
521 | 370 | ] | ||
522 | 0 | \ No newline at end of file | 371 | \ No newline at end of file |
523 | 1 | 372 | ||
524 | === modified file 'src/webcatalog/management/commands/import_app_install_data.py' | |||
525 | --- src/webcatalog/management/commands/import_app_install_data.py 2011-04-12 15:55:30 +0000 | |||
526 | +++ src/webcatalog/management/commands/import_app_install_data.py 2011-04-13 02:40:59 +0000 | |||
527 | @@ -86,11 +86,12 @@ | |||
528 | 86 | 86 | ||
529 | 87 | if form.is_valid(): | 87 | if form.is_valid(): |
530 | 88 | app = form.save() | 88 | app = form.save() |
531 | 89 | app.update_departments() | ||
532 | 89 | if self.verbosity > 0: | 90 | if self.verbosity > 0: |
533 | 90 | self.stdout.write( | 91 | self.stdout.write( |
535 | 91 | u"{0} created.\n".format(app.name)) | 92 | u"{0} created.\n".format(app.name).encode('utf-8')) |
536 | 92 | else: | 93 | else: |
537 | 93 | if self.verbosity > 0: | 94 | if self.verbosity > 0: |
538 | 94 | self.stdout.write( | 95 | self.stdout.write( |
539 | 95 | u"Skipping {0} as input failed validation: {1}.\n".format( | 96 | u"Skipping {0} as input failed validation: {1}.\n".format( |
541 | 96 | member.name, form.errors)) | 97 | member.name, form.errors).encode('utf-8')) |
542 | 97 | 98 | ||
543 | === modified file 'src/webcatalog/models.py' | |||
544 | --- src/webcatalog/models.py 2011-04-12 15:22:54 +0000 | |||
545 | +++ src/webcatalog/models.py 2011-04-13 02:40:59 +0000 | |||
546 | @@ -21,8 +21,13 @@ | |||
547 | 21 | absolute_import, | 21 | absolute_import, |
548 | 22 | with_statement, | 22 | with_statement, |
549 | 23 | ) | 23 | ) |
550 | 24 | |||
551 | 25 | import logging | ||
552 | 26 | |||
553 | 24 | from django.db import models | 27 | from django.db import models |
554 | 25 | 28 | ||
555 | 29 | from webcatalog.department_filters import department_filters | ||
556 | 30 | |||
557 | 26 | __metaclass__ = type | 31 | __metaclass__ = type |
558 | 27 | __all__ = [ | 32 | __all__ = [ |
559 | 28 | 'Application', | 33 | 'Application', |
560 | @@ -37,19 +42,20 @@ | |||
561 | 37 | # at runtime instead. | 42 | # at runtime instead. |
562 | 38 | 43 | ||
563 | 39 | # The following fields are extracted from app-install-data. | 44 | # The following fields are extracted from app-install-data. |
565 | 40 | package_name = models.SlugField(max_length=100) | 45 | package_name = models.CharField(max_length=100) |
566 | 41 | name = models.CharField(max_length=255) | 46 | name = models.CharField(max_length=255) |
567 | 42 | comment = models.CharField(max_length=255, blank=True) | 47 | comment = models.CharField(max_length=255, blank=True) |
568 | 43 | popcon = models.IntegerField() | 48 | popcon = models.IntegerField() |
569 | 44 | channel = models.CharField(max_length=255, blank=True) | 49 | channel = models.CharField(max_length=255, blank=True) |
570 | 45 | screenshot_url = models.URLField(blank=True, | 50 | screenshot_url = models.URLField(blank=True, |
571 | 46 | help_text="Only use this if it is other than the normal screenshot url.") | 51 | help_text="Only use this if it is other than the normal screenshot url.") |
573 | 47 | mimetype = models.CharField(max_length=255, blank=True) | 52 | mimetype = models.CharField(max_length=2048, blank=True) |
574 | 48 | architectures = models.CharField(max_length=255, blank=True) | 53 | architectures = models.CharField(max_length=255, blank=True) |
575 | 49 | keywords = models.CharField(max_length=255, blank=True) | 54 | keywords = models.CharField(max_length=255, blank=True) |
577 | 50 | app_type = models.CharField(max_length=32) | 55 | app_type = models.CharField(max_length=32, blank=True) |
578 | 51 | section = models.CharField(max_length=32) | 56 | section = models.CharField(max_length=32) |
580 | 52 | categories = models.CharField(max_length=255) | 57 | categories = models.CharField(max_length=255, blank=True) |
581 | 58 | departments = models.ManyToManyField('Department', blank=True) | ||
582 | 53 | 59 | ||
583 | 54 | # Other desktop fields used by s-c | 60 | # Other desktop fields used by s-c |
584 | 55 | gnome_full_name = models.CharField(max_length=255, blank=True) | 61 | gnome_full_name = models.CharField(max_length=255, blank=True) |
585 | @@ -64,3 +70,30 @@ | |||
586 | 64 | 70 | ||
587 | 65 | def __unicode__(self): | 71 | def __unicode__(self): |
588 | 66 | return u"{0} ({1})".format(self.name, self.package_name) | 72 | return u"{0} ({1})".format(self.name, self.package_name) |
589 | 73 | |||
590 | 74 | @property | ||
591 | 75 | def categories_set(self): | ||
592 | 76 | """Return the set of categories for this app""" | ||
593 | 77 | stripped = [x.strip() for x in self.categories.split(';')] | ||
594 | 78 | return set(x for x in stripped if x) | ||
595 | 79 | |||
596 | 80 | def update_departments(self): | ||
597 | 81 | """Update the list of departments for this app""" | ||
598 | 82 | self.departments.clear() | ||
599 | 83 | for dept_name, dept_filters in department_filters.items(): | ||
600 | 84 | for dept_filter in dept_filters: | ||
601 | 85 | if dept_filter(self): | ||
602 | 86 | dept, created = Department.objects.get_or_create( | ||
603 | 87 | name=dept_name) | ||
604 | 88 | if created: | ||
605 | 89 | logging.warn("Department %s automatically created!" % | ||
606 | 90 | dept_name) | ||
607 | 91 | self.departments.add(dept) | ||
608 | 92 | break | ||
609 | 93 | |||
610 | 94 | class Department(models.Model): | ||
611 | 95 | parent = models.ForeignKey('self', blank=True, null=True) | ||
612 | 96 | name = models.CharField(max_length=64) | ||
613 | 97 | |||
614 | 98 | def __unicode__(self): | ||
615 | 99 | return self.name | ||
616 | 67 | 100 | ||
617 | === modified file 'src/webcatalog/tests/__init__.py' | |||
618 | --- src/webcatalog/tests/__init__.py 2011-04-12 12:54:38 +0000 | |||
619 | +++ src/webcatalog/tests/__init__.py 2011-04-13 02:40:59 +0000 | |||
620 | @@ -19,3 +19,5 @@ | |||
621 | 19 | from .test_forms import * | 19 | from .test_forms import * |
622 | 20 | from .test_commands import * | 20 | from .test_commands import * |
623 | 21 | from .test_views import * | 21 | from .test_views import * |
624 | 22 | from .test_department_filters import * | ||
625 | 23 | from .test_models import * | ||
626 | 22 | 24 | ||
627 | === added file 'src/webcatalog/tests/test_department_filters.py' | |||
628 | --- src/webcatalog/tests/test_department_filters.py 1970-01-01 00:00:00 +0000 | |||
629 | +++ src/webcatalog/tests/test_department_filters.py 2011-04-13 02:40:59 +0000 | |||
630 | @@ -0,0 +1,69 @@ | |||
631 | 1 | # -*- coding: utf-8 -*- | ||
632 | 2 | # This file is part of the Ubuntu Web Catalog | ||
633 | 3 | # Copyright (C) 2011 Canonical Ltd. | ||
634 | 4 | # | ||
635 | 5 | # This program is free software: you can redistribute it and/or modify | ||
636 | 6 | # it under the terms of the GNU Affero General Public License as | ||
637 | 7 | # published by the Free Software Foundation, either version 3 of the | ||
638 | 8 | # License, or (at your option) any later version. | ||
639 | 9 | # | ||
640 | 10 | # This program is distributed in the hope that it will be useful, | ||
641 | 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
642 | 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
643 | 13 | # GNU Affero General Public License for more details. | ||
644 | 14 | # | ||
645 | 15 | # You should have received a copy of the GNU Affero General Public License | ||
646 | 16 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
647 | 17 | |||
648 | 18 | """Test cases for department filters.""" | ||
649 | 19 | |||
650 | 20 | from __future__ import ( | ||
651 | 21 | absolute_import, | ||
652 | 22 | with_statement, | ||
653 | 23 | ) | ||
654 | 24 | |||
655 | 25 | |||
656 | 26 | from webcatalog.tests.factory import TestCaseWithFactory | ||
657 | 27 | from webcatalog.department_filters import ( | ||
658 | 28 | category_filter, | ||
659 | 29 | package_name_filter, | ||
660 | 30 | section_filter | ||
661 | 31 | ) | ||
662 | 32 | |||
663 | 33 | __metaclass__ = type | ||
664 | 34 | __all__ = [ | ||
665 | 35 | 'DepartmentFilterTestCase', | ||
666 | 36 | ] | ||
667 | 37 | |||
668 | 38 | |||
669 | 39 | class DepartmentFilterTestCase(TestCaseWithFactory): | ||
670 | 40 | def test_package_name_filter(self): | ||
671 | 41 | dept_filter = package_name_filter('a*$') | ||
672 | 42 | app1 = self.factory.make_application(package_name='aaaaa') | ||
673 | 43 | self.assertTrue(dept_filter(app1)) | ||
674 | 44 | |||
675 | 45 | app2 = self.factory.make_application(package_name='aaabaa') | ||
676 | 46 | self.assertFalse(dept_filter(app2)) | ||
677 | 47 | |||
678 | 48 | def test_category_filter(self): | ||
679 | 49 | dept_filter = category_filter(['foo', 'bar']) | ||
680 | 50 | app = self.factory.make_application() | ||
681 | 51 | self.assertFalse(dept_filter(app)) | ||
682 | 52 | app.categories = 'foo;bin' | ||
683 | 53 | self.assertTrue(dept_filter(app)) | ||
684 | 54 | app.categories = 'foobin' | ||
685 | 55 | self.assertFalse(dept_filter(app)) | ||
686 | 56 | app.categories = '' | ||
687 | 57 | self.assertFalse(dept_filter(app)) | ||
688 | 58 | |||
689 | 59 | def test_section_filter(self): | ||
690 | 60 | dept_filter = section_filter(['foo', 'bar']) | ||
691 | 61 | app = self.factory.make_application() | ||
692 | 62 | |||
693 | 63 | self.assertFalse(dept_filter(app)) | ||
694 | 64 | app.section = 'foo' | ||
695 | 65 | self.assertTrue(dept_filter(app)) | ||
696 | 66 | app.section = 'foobin' | ||
697 | 67 | self.assertFalse(dept_filter(app)) | ||
698 | 68 | app.section = '' | ||
699 | 69 | self.assertFalse(dept_filter(app)) | ||
700 | 0 | 70 | ||
701 | === added file 'src/webcatalog/tests/test_models.py' | |||
702 | --- src/webcatalog/tests/test_models.py 1970-01-01 00:00:00 +0000 | |||
703 | +++ src/webcatalog/tests/test_models.py 2011-04-13 02:40:59 +0000 | |||
704 | @@ -0,0 +1,60 @@ | |||
705 | 1 | # -*- coding: utf-8 -*- | ||
706 | 2 | # This file is part of the Ubuntu Web Catalog | ||
707 | 3 | # Copyright (C) 2011 Canonical Ltd. | ||
708 | 4 | # | ||
709 | 5 | # This program is free software: you can redistribute it and/or modify | ||
710 | 6 | # it under the terms of the GNU Affero General Public License as | ||
711 | 7 | # published by the Free Software Foundation, either version 3 of the | ||
712 | 8 | # License, or (at your option) any later version. | ||
713 | 9 | # | ||
714 | 10 | # This program is distributed in the hope that it will be useful, | ||
715 | 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
716 | 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
717 | 13 | # GNU Affero General Public License for more details. | ||
718 | 14 | # | ||
719 | 15 | # You should have received a copy of the GNU Affero General Public License | ||
720 | 16 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
721 | 17 | |||
722 | 18 | """Test cases for models.""" | ||
723 | 19 | |||
724 | 20 | from __future__ import ( | ||
725 | 21 | absolute_import, | ||
726 | 22 | with_statement, | ||
727 | 23 | ) | ||
728 | 24 | |||
729 | 25 | |||
730 | 26 | from webcatalog.tests.factory import TestCaseWithFactory | ||
731 | 27 | from webcatalog.models import Application | ||
732 | 28 | |||
733 | 29 | __metaclass__ = type | ||
734 | 30 | __all__ = [ | ||
735 | 31 | 'ApplicationTestCase', | ||
736 | 32 | ] | ||
737 | 33 | |||
738 | 34 | |||
739 | 35 | class ApplicationTestCase(TestCaseWithFactory): | ||
740 | 36 | def test_categories_set(self): | ||
741 | 37 | app = self.factory.make_application() | ||
742 | 38 | app.categories = 'foo;;;; bar ' | ||
743 | 39 | self.assertEqual(set(['foo', 'bar']), app.categories_set) | ||
744 | 40 | |||
745 | 41 | def test_empty_category_set(self): | ||
746 | 42 | app = self.factory.make_application() | ||
747 | 43 | app.categories = '' | ||
748 | 44 | self.assertEqual(set(), app.categories_set) | ||
749 | 45 | |||
750 | 46 | def test_update_empty_departments(self): | ||
751 | 47 | app = self.factory.make_application() | ||
752 | 48 | |||
753 | 49 | app.update_departments() | ||
754 | 50 | |||
755 | 51 | self.assertEqual(0, app.departments.count()) | ||
756 | 52 | |||
757 | 53 | def test_update_departments(self): | ||
758 | 54 | app = self.factory.make_application() | ||
759 | 55 | app.categories = 'Game;' | ||
760 | 56 | |||
761 | 57 | app.update_departments() | ||
762 | 58 | |||
763 | 59 | self.assertEqual(1, app.departments.count()) | ||
764 | 60 | self.assertEqual('Games', app.departments.get().name) |
Excellent Anthony! I'll assume that you're also working on the ui aspect of this and grab something else this morning.
Thanks for the small import fixes too.