Merge lp:~elachuni/ubuntu-webcatalog/department-slug into lp:ubuntu-webcatalog

Proposed by Anthony Lenton
Status: Merged
Approved by: Michael Nelson
Approved revision: 80
Merged at revision: 75
Proposed branch: lp:~elachuni/ubuntu-webcatalog/department-slug
Merge into: lp:ubuntu-webcatalog
Diff against target: 1453 lines (+667/-117)
15 files modified
src/webcatalog/admin.py (+5/-1)
src/webcatalog/fixtures/initial_data.json (+93/-48)
src/webcatalog/migrations/0010_add_department_slug.py (+154/-0)
src/webcatalog/migrations/0011_populate_department_slug.py (+153/-0)
src/webcatalog/migrations/0012_unique_department_slug.py (+160/-0)
src/webcatalog/models/applications.py (+6/-9)
src/webcatalog/static/css/carousel.css (+5/-0)
src/webcatalog/templates/webcatalog/department_overview.html (+1/-1)
src/webcatalog/templates/webcatalog/department_overview_snippet.html (+3/-3)
src/webcatalog/templates/webcatalog/featured_apps_widget.html (+4/-4)
src/webcatalog/tests/factory.py (+4/-2)
src/webcatalog/tests/test_models.py (+15/-22)
src/webcatalog/tests/test_views.py (+50/-20)
src/webcatalog/urls.py (+2/-2)
src/webcatalog/views.py (+12/-5)
To merge this branch: bzr merge lp:~elachuni/ubuntu-webcatalog/department-slug
Reviewer Review Type Date Requested Status
Michael Nelson (community) Approve
Review via email: mp+96792@code.launchpad.net

Commit message

Added a 'slug' field to the Department models, and made it possible to specify department overview requests by the department's slug.

Description of the change

Overview
========
This branch adds a 'slug' field to the Department models, and makes it possible to specify department overview requests by the department's slug.

Details
=======
First, apologies for the long MP. Most of it is migrations and fixture fix, I thought this was going to be a simple and small branch.
Three migrations were needed: One to add the 'slug' field (null, non-unique), a data migration to populate it, and one to add a non-null and unique restriction.

The branch also makes all the links on the site use the sluggified version of the department overview URL, although specifying department by ID still works, to not break links already out there.

As I thought this branch was going to be small, I sorted out a couple of minor drive-through fixes before starting :-/ Related to the featured_apps widget, it now never displays blank slides (it did before if you happened to have a muliple of 4 featured apps), and a css fix to remove the right-side border on the last featured app.

To post a comment you must log in.
Revision history for this message
Anthony Lenton (elachuni) wrote :

Ok, I also removed Department.normalized_name because it was kind of working as a slug, except you couldn't use it for this use case (as you can't retrieve departments by their normalized_name).
So I removed all the uses of normalized_name and replaced them for calls to slug instead.

Revision history for this message
Michael Nelson (michael.nelson) wrote :
Download full text (14.1 KiB)

On Fri, Mar 9, 2012 at 6:33 PM, Anthony Lenton
<email address hidden> wrote:
> Anthony Lenton has proposed merging lp:~elachuni/ubuntu-webcatalog/department-slug into lp:ubuntu-webcatalog.
>
> Overview
> ========
> This branch adds a 'slug' field to the Department models, and makes it possible to specify department overview requests by the department's slug.
>
> Details
> =======
> First, apologies for the long MP.  Most of it is migrations and fixture fix, I thought this was going to be a simple and small branch.
> Three migrations were needed: One to add the 'slug' field (null, non-unique), a data migration to populate it, and one to add a non-null and unique restriction.

No worries - I don't need to go through much of that code :-)

>
> The branch also makes all the links on the site use the sluggified version of the department overview URL, although specifying department by ID still works, to not break links already out there.

Excellent.

>
> As I thought this branch was going to be small, I sorted out a couple of minor drive-through fixes before starting :-/  Related to the featured_apps widget, it now never displays blank slides (it did before if you happened to have a muliple of 4 featured apps), and a css fix to remove the right-side border on the last featured app.

k.

>
>
> --
> https://code.launchpad.net/~elachuni/ubuntu-webcatalog/department-slug/+merge/96792
> You are subscribed to branch lp:ubuntu-webcatalog.
>
> === modified file 'src/webcatalog/admin.py'
> --- src/webcatalog/admin.py     2012-03-01 21:38:31 +0000
> +++ src/webcatalog/admin.py     2012-03-09 16:27:28 +0000
> === modified file 'src/webcatalog/fixtures/initial_data.json'
> --- src/webcatalog/fixtures/initial_data.json   2011-04-19 18:46:21 +0000
> +++ src/webcatalog/fixtures/initial_data.json   2012-03-09 16:27:28 +0000
> === added file 'src/webcatalog/migrations/0010_add_department_slug.py'
> --- src/webcatalog/migrations/0010_add_department_slug.py       1970-01-01 00:00:00 +0000
> +++ src/webcatalog/migrations/0010_add_department_slug.py       2012-03-09 16:27:28 +0000
> === added file 'src/webcatalog/migrations/0011_populate_department_slug.py'
> --- src/webcatalog/migrations/0011_populate_department_slug.py  1970-01-01 00:00:00 +0000
> +++ src/webcatalog/migrations/0011_populate_department_slug.py  2012-03-09 16:27:28 +0000
> @@ -0,0 +1,162 @@
> +# encoding: utf-8
> +import datetime
> +from south.db import db
> +from south.v2 import DataMigration
> +from django.db import models
> +
> +class Migration(DataMigration):
> +
> +    # A static version of sluggify name, for if the one on the
> +    # Department model changes
> +    def sluggify_name(self, name):
> +        slug = name.lower()
> +        for char in ' _&,.':
> +            slug = slug.replace(char, '-')
> +        while '--' in slug:
> +            slug = slug.replace('--', '-')
> +        return slug

You can use the slugify method built in to django if you want? less code :-)

> +
> +    def forwards(self, orm):
> +        for dept in orm.Department.objects.all():
> +            dept.slug = self.sluggify_name(dept.name)
> +            dept.save()
> +
> === added file 'src/webcatalog/mig...

78. By Anthony Lenton

Replaced custom sluggify_name for Django's out-of-the-box slugify filter.

Revision history for this message
Michael Nelson (michael.nelson) :
review: Approve
79. By Anthony Lenton

Replaced dept_slug and dept_id args in the department overview view for a single dept_slug_or_id arg.

80. By Anthony Lenton

Merged in changes from trunk.

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 2012-03-01 21:38:31 +0000
3+++ src/webcatalog/admin.py 2012-03-12 15:48:24 +0000
4@@ -52,8 +52,12 @@
5 class ExhibitAdmin(admin.ModelAdmin):
6 list_display = ('package_names', 'published', 'display')
7
8+
9+class DepartmentAdmin(admin.ModelAdmin):
10+ prepopulated_fields = {"slug": ("name",)}
11+
12 admin.site.register(Application, ApplicationAdmin)
13-admin.site.register(Department)
14+admin.site.register(Department, DepartmentAdmin)
15 admin.site.register(DistroSeries)
16 admin.site.register(Exhibit, ExhibitAdmin)
17 admin.site.register(Machine, MachineAdmin)
18
19=== modified file 'src/webcatalog/fixtures/initial_data.json'
20--- src/webcatalog/fixtures/initial_data.json 2011-04-19 18:46:21 +0000
21+++ src/webcatalog/fixtures/initial_data.json 2012-03-12 15:48:24 +0000
22@@ -4,7 +4,8 @@
23 "model": "webcatalog.department",
24 "fields": {
25 "name": "Games",
26- "parent": null
27+ "parent": null,
28+ "slug": "games"
29 }
30 },
31 {
32@@ -12,7 +13,8 @@
33 "model": "webcatalog.department",
34 "fields": {
35 "name": "Office",
36- "parent": null
37+ "parent": null,
38+ "slug": "office"
39 }
40 },
41 {
42@@ -20,7 +22,8 @@
43 "model": "webcatalog.department",
44 "fields": {
45 "name": "Sound & Video",
46- "parent": null
47+ "parent": null,
48+ "slug": "sound-video"
49 }
50 },
51 {
52@@ -28,7 +31,8 @@
53 "model": "webcatalog.department",
54 "fields": {
55 "name": "Developer Tools",
56- "parent": null
57+ "parent": null,
58+ "slug": "developer-tools"
59 }
60 },
61 {
62@@ -36,7 +40,8 @@
63 "model": "webcatalog.department",
64 "fields": {
65 "name": "Science & Engineering",
66- "parent": null
67+ "parent": null,
68+ "slug": "science-engineering"
69 }
70 },
71 {
72@@ -44,7 +49,8 @@
73 "model": "webcatalog.department",
74 "fields": {
75 "name": "Education",
76- "parent": null
77+ "parent": null,
78+ "slug": "education"
79 }
80 },
81 {
82@@ -52,7 +58,8 @@
83 "model": "webcatalog.department",
84 "fields": {
85 "name": "Biology",
86- "parent": 5
87+ "parent": 5,
88+ "slug": "biology"
89 }
90 },
91 {
92@@ -60,7 +67,8 @@
93 "model": "webcatalog.department",
94 "fields": {
95 "name": "Accessories",
96- "parent": null
97+ "parent": null,
98+ "slug": "accessories"
99 }
100 },
101 {
102@@ -68,7 +76,8 @@
103 "model": "webcatalog.department",
104 "fields": {
105 "name": "Role-Playing",
106- "parent": 1
107+ "parent": 1,
108+ "slug": "role-playing"
109 }
110 },
111 {
112@@ -76,7 +85,8 @@
113 "model": "webcatalog.department",
114 "fields": {
115 "name": "Geography",
116- "parent": 5
117+ "parent": 5,
118+ "slug": "geography"
119 }
120 },
121 {
122@@ -84,7 +94,8 @@
123 "model": "webcatalog.department",
124 "fields": {
125 "name": "Viewers",
126- "parent": 13
127+ "parent": 13,
128+ "slug": "viewers"
129 }
130 },
131 {
132@@ -92,7 +103,8 @@
133 "model": "webcatalog.department",
134 "fields": {
135 "name": "Graphics",
136- "parent": null
137+ "parent": null,
138+ "slug": "graphics"
139 }
140 },
141 {
142@@ -100,7 +112,8 @@
143 "model": "webcatalog.department",
144 "fields": {
145 "name": "Themes & Tweaks",
146- "parent": null
147+ "parent": null,
148+ "slug": "themes-tweaks"
149 }
150 },
151 {
152@@ -108,7 +121,8 @@
153 "model": "webcatalog.department",
154 "fields": {
155 "name": "Internet",
156- "parent": null
157+ "parent": null,
158+ "slug": "internet"
159 }
160 },
161 {
162@@ -116,7 +130,8 @@
163 "model": "webcatalog.department",
164 "fields": {
165 "name": "Debugging",
166- "parent": 4
167+ "parent": 4,
168+ "slug": "debugging"
169 }
170 },
171 {
172@@ -124,7 +139,8 @@
173 "model": "webcatalog.department",
174 "fields": {
175 "name": "Profiling",
176- "parent": 4
177+ "parent": 4,
178+ "slug": "profiling"
179 }
180 },
181 {
182@@ -132,7 +148,8 @@
183 "model": "webcatalog.department",
184 "fields": {
185 "name": "Chat",
186- "parent": 15
187+ "parent": 15,
188+ "slug": "chat"
189 }
190 },
191 {
192@@ -140,7 +157,8 @@
193 "model": "webcatalog.department",
194 "fields": {
195 "name": "IDEs",
196- "parent": 4
197+ "parent": 4,
198+ "slug": "ides"
199 }
200 },
201 {
202@@ -148,7 +166,8 @@
203 "model": "webcatalog.department",
204 "fields": {
205 "name": "3D",
206- "parent": 13
207+ "parent": 13,
208+ "slug": "3d"
209 }
210 },
211 {
212@@ -156,7 +175,8 @@
213 "model": "webcatalog.department",
214 "fields": {
215 "name": "Engineering",
216- "parent": 5
217+ "parent": 5,
218+ "slug": "engineering"
219 }
220 },
221 {
222@@ -164,7 +184,8 @@
223 "model": "webcatalog.department",
224 "fields": {
225 "name": "Electronics",
226- "parent": 5
227+ "parent": 5,
228+ "slug": "electronics"
229 }
230 },
231 {
232@@ -172,7 +193,8 @@
233 "model": "webcatalog.department",
234 "fields": {
235 "name": "Web Browsers",
236- "parent": 15
237+ "parent": 15,
238+ "slug": "web-browsers"
239 }
240 },
241 {
242@@ -180,7 +202,8 @@
243 "model": "webcatalog.department",
244 "fields": {
245 "name": "Mathematics",
246- "parent": 5
247+ "parent": 5,
248+ "slug": "mathematics"
249 }
250 },
251 {
252@@ -188,7 +211,8 @@
253 "model": "webcatalog.department",
254 "fields": {
255 "name": "Chemistry",
256- "parent": 5
257+ "parent": 5,
258+ "slug": "chemistry"
259 }
260 },
261 {
262@@ -196,7 +220,8 @@
263 "model": "webcatalog.department",
264 "fields": {
265 "name": "Physics",
266- "parent": 5
267+ "parent": 5,
268+ "slug": "physics"
269 }
270 },
271 {
272@@ -204,7 +229,8 @@
273 "model": "webcatalog.department",
274 "fields": {
275 "name": "Mail",
276- "parent": 15
277+ "parent": 15,
278+ "slug": "mail"
279 }
280 },
281 {
282@@ -212,7 +238,8 @@
283 "model": "webcatalog.department",
284 "fields": {
285 "name": "Computing & Robotics",
286- "parent": 5
287+ "parent": 5,
288+ "slug": "computing-robotics"
289 }
290 },
291 {
292@@ -220,7 +247,8 @@
293 "model": "webcatalog.department",
294 "fields": {
295 "name": "Web Development",
296- "parent": 4
297+ "parent": 4,
298+ "slug": "web-development"
299 }
300 },
301 {
302@@ -228,7 +256,8 @@
303 "model": "webcatalog.department",
304 "fields": {
305 "name": "Graphic Interface Design",
306- "parent": 4
307+ "parent": 4,
308+ "slug": "graphic-interface-design"
309 }
310 },
311 {
312@@ -236,7 +265,8 @@
313 "model": "webcatalog.department",
314 "fields": {
315 "name": "Version Control",
316- "parent": 4
317+ "parent": 4,
318+ "slug": "version-control"
319 }
320 },
321 {
322@@ -244,7 +274,8 @@
323 "model": "webcatalog.department",
324 "fields": {
325 "name": "Photography",
326- "parent": 13
327+ "parent": 13,
328+ "slug": "photography"
329 }
330 },
331 {
332@@ -252,7 +283,8 @@
333 "model": "webcatalog.department",
334 "fields": {
335 "name": "Astronomy",
336- "parent": 5
337+ "parent": 5,
338+ "slug": "astronomy"
339 }
340 },
341 {
342@@ -260,7 +292,8 @@
343 "model": "webcatalog.department",
344 "fields": {
345 "name": "Universal Access",
346- "parent": null
347+ "parent": null,
348+ "slug": "universal-access"
349 }
350 },
351 {
352@@ -268,7 +301,8 @@
353 "model": "webcatalog.department",
354 "fields": {
355 "name": "Drawing",
356- "parent": 13
357+ "parent": 13,
358+ "slug": "drawing"
359 }
360 },
361 {
362@@ -276,7 +310,8 @@
363 "model": "webcatalog.department",
364 "fields": {
365 "name": "Painting",
366- "parent": 13
367+ "parent": 13,
368+ "slug": "painting"
369 }
370 },
371 {
372@@ -284,7 +319,8 @@
373 "model": "webcatalog.department",
374 "fields": {
375 "name": "Publishing",
376- "parent": 13
377+ "parent": 13,
378+ "slug": "publishing"
379 }
380 },
381 {
382@@ -292,7 +328,8 @@
383 "model": "webcatalog.department",
384 "fields": {
385 "name": "Localization",
386- "parent": 4
387+ "parent": 4,
388+ "slug": "localization"
389 }
390 },
391 {
392@@ -300,7 +337,8 @@
393 "model": "webcatalog.department",
394 "fields": {
395 "name": "Scanning & OCR",
396- "parent": 13
397+ "parent": 13,
398+ "slug": "scanning-ocr"
399 }
400 },
401 {
402@@ -308,7 +346,8 @@
403 "model": "webcatalog.department",
404 "fields": {
405 "name": "Geology",
406- "parent": 5
407+ "parent": 5,
408+ "slug": "geology"
409 }
410 },
411 {
412@@ -316,7 +355,8 @@
413 "model": "webcatalog.department",
414 "fields": {
415 "name": "Board Games",
416- "parent": 1
417+ "parent": 1,
418+ "slug": "board-games"
419 }
420 },
421 {
422@@ -324,7 +364,8 @@
423 "model": "webcatalog.department",
424 "fields": {
425 "name": "Puzzles",
426- "parent": 1
427+ "parent": 1,
428+ "slug": "puzzles"
429 }
430 },
431 {
432@@ -332,7 +373,8 @@
433 "model": "webcatalog.department",
434 "fields": {
435 "name": "File Sharing",
436- "parent": 15
437+ "parent": 15,
438+ "slug": "file-sharing"
439 }
440 },
441 {
442@@ -340,7 +382,8 @@
443 "model": "webcatalog.department",
444 "fields": {
445 "name": "Sports",
446- "parent": 1
447+ "parent": 1,
448+ "slug": "sports"
449 }
450 },
451 {
452@@ -348,15 +391,17 @@
453 "model": "webcatalog.department",
454 "fields": {
455 "name": "Card Games",
456- "parent": 1
457+ "parent": 1,
458+ "slug": "card-games"
459 }
460- },
461+ },
462 {
463 "pk": 47,
464 "model": "webcatalog.department",
465 "fields": {
466 "name": "Fonts",
467- "parent": null
468+ "parent": null,
469+ "slug": "fonts"
470 }
471- }
472-]
473\ No newline at end of file
474+ }
475+]
476
477=== added file 'src/webcatalog/migrations/0010_add_department_slug.py'
478--- src/webcatalog/migrations/0010_add_department_slug.py 1970-01-01 00:00:00 +0000
479+++ src/webcatalog/migrations/0010_add_department_slug.py 2012-03-12 15:48:24 +0000
480@@ -0,0 +1,154 @@
481+# encoding: utf-8
482+import datetime
483+from south.db import db
484+from south.v2 import SchemaMigration
485+from django.db import models
486+
487+class Migration(SchemaMigration):
488+
489+ def forwards(self, orm):
490+
491+ # Adding field 'Department.slug'
492+ db.add_column('webcatalog_department', 'slug', self.gf('django.db.models.fields.SlugField')(max_length=50, null=True, db_index=True), keep_default=False)
493+
494+
495+ def backwards(self, orm):
496+
497+ # Deleting field 'Department.slug'
498+ db.delete_column('webcatalog_department', 'slug')
499+
500+
501+ models = {
502+ 'auth.group': {
503+ 'Meta': {'object_name': 'Group'},
504+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
505+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
506+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
507+ },
508+ 'auth.permission': {
509+ 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
510+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
511+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
512+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
513+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
514+ },
515+ 'auth.user': {
516+ 'Meta': {'object_name': 'User'},
517+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
518+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
519+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
520+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
521+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
522+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
523+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
524+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
525+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
526+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
527+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
528+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
529+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
530+ },
531+ 'contenttypes.contenttype': {
532+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
533+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
534+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
535+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
536+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
537+ },
538+ 'webcatalog.application': {
539+ 'Meta': {'unique_together': "(('distroseries', 'archive_id'),)", 'object_name': 'Application'},
540+ 'app_type': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
541+ 'architectures': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
542+ 'archive_id': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '64', 'null': 'True', 'blank': 'True'}),
543+ 'categories': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
544+ 'channel': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
545+ 'comment': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
546+ 'departments': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['webcatalog.Department']", 'symmetrical': 'False', 'blank': 'True'}),
547+ 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
548+ 'distroseries': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.DistroSeries']"}),
549+ 'for_purchase': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
550+ 'icon': ('django.db.models.fields.files.ImageField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
551+ 'icon_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
552+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
553+ 'keywords': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
554+ 'mimetype': ('django.db.models.fields.CharField', [], {'max_length': '2048', 'blank': 'True'}),
555+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
556+ 'package_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
557+ 'popcon': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
558+ 'price': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '7', 'decimal_places': '2', 'blank': 'True'}),
559+ 'ratings_average': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '3', 'decimal_places': '2', 'blank': 'True'}),
560+ 'ratings_histogram': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}),
561+ 'ratings_total': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
562+ 'screenshot_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
563+ 'section': ('django.db.models.fields.CharField', [], {'max_length': '32'})
564+ },
565+ 'webcatalog.consumer': {
566+ 'Meta': {'object_name': 'Consumer'},
567+ 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
568+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
569+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
570+ 'secret': ('django.db.models.fields.CharField', [], {'default': "'VXPcqxReXucklUvCpbyRIxgjjAOLNM'", 'max_length': '255', 'blank': 'True'}),
571+ 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
572+ 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'oauth_consumer'", 'unique': 'True', 'to': "orm['auth.User']"})
573+ },
574+ 'webcatalog.department': {
575+ 'Meta': {'object_name': 'Department'},
576+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
577+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
578+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Department']", 'null': 'True', 'blank': 'True'}),
579+ 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'null': 'True', 'db_index': 'True'})
580+ },
581+ 'webcatalog.distroseries': {
582+ 'Meta': {'object_name': 'DistroSeries'},
583+ 'code_name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '20', 'db_index': 'True'}),
584+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
585+ 'version': ('django.db.models.fields.CharField', [], {'max_length': '10', 'blank': 'True'})
586+ },
587+ 'webcatalog.exhibit': {
588+ 'Meta': {'object_name': 'Exhibit'},
589+ 'banner_url': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
590+ 'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
591+ 'display': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
592+ 'distroseries': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['webcatalog.DistroSeries']", 'symmetrical': 'False'}),
593+ 'html': ('django.db.models.fields.TextField', [], {}),
594+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
595+ 'package_names': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
596+ 'published': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
597+ 'sca_id': ('django.db.models.fields.IntegerField', [], {})
598+ },
599+ 'webcatalog.machine': {
600+ 'Meta': {'unique_together': "(('owner', 'uuid'),)", 'object_name': 'Machine'},
601+ 'hostname': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
602+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
603+ 'logo_checksum': ('django.db.models.fields.CharField', [], {'max_length': '56', 'blank': 'True'}),
604+ 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
605+ 'package_list': ('django.db.models.fields.TextField', [], {}),
606+ 'packages_checksum': ('django.db.models.fields.CharField', [], {'max_length': '56'}),
607+ 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'})
608+ },
609+ 'webcatalog.nonce': {
610+ 'Meta': {'object_name': 'Nonce'},
611+ 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Consumer']"}),
612+ 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
613+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
614+ 'nonce': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
615+ 'token': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Token']"})
616+ },
617+ 'webcatalog.reviewstatsimport': {
618+ 'Meta': {'object_name': 'ReviewStatsImport'},
619+ 'distroseries': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.DistroSeries']", 'unique': 'True'}),
620+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
621+ 'last_import': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.utcnow'})
622+ },
623+ 'webcatalog.token': {
624+ 'Meta': {'object_name': 'Token'},
625+ 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Consumer']"}),
626+ 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
627+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
628+ 'token': ('django.db.models.fields.CharField', [], {'default': "'lNTrEuIeWzyjJuFkpRDfxMjrRYTctAsxDmaYOnwasgDncZmAHa'", 'max_length': '50', 'primary_key': 'True'}),
629+ 'token_secret': ('django.db.models.fields.CharField', [], {'default': "'OWMLurVHqVwKOGfcvWTwWBLBUtQNfaTfIiHOhVdDFYPMdWorhu'", 'max_length': '50'}),
630+ 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
631+ }
632+ }
633+
634+ complete_apps = ['webcatalog']
635
636=== added file 'src/webcatalog/migrations/0011_populate_department_slug.py'
637--- src/webcatalog/migrations/0011_populate_department_slug.py 1970-01-01 00:00:00 +0000
638+++ src/webcatalog/migrations/0011_populate_department_slug.py 2012-03-12 15:48:24 +0000
639@@ -0,0 +1,153 @@
640+# encoding: utf-8
641+import datetime
642+from south.db import db
643+from south.v2 import DataMigration
644+from django.db import models
645+from django.template.defaultfilters import slugify
646+
647+class Migration(DataMigration):
648+
649+ def forwards(self, orm):
650+ for dept in orm.Department.objects.all():
651+ dept.slug = slugify(dept.name)
652+ dept.save()
653+
654+
655+ def backwards(self, orm):
656+ pass
657+
658+
659+ models = {
660+ 'auth.group': {
661+ 'Meta': {'object_name': 'Group'},
662+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
663+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
664+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
665+ },
666+ 'auth.permission': {
667+ 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
668+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
669+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
670+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
671+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
672+ },
673+ 'auth.user': {
674+ 'Meta': {'object_name': 'User'},
675+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
676+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
677+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
678+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
679+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
680+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
681+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
682+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
683+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
684+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
685+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
686+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
687+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
688+ },
689+ 'contenttypes.contenttype': {
690+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
691+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
692+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
693+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
694+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
695+ },
696+ 'webcatalog.application': {
697+ 'Meta': {'unique_together': "(('distroseries', 'archive_id'),)", 'object_name': 'Application'},
698+ 'app_type': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
699+ 'architectures': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
700+ 'archive_id': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '64', 'null': 'True', 'blank': 'True'}),
701+ 'categories': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
702+ 'channel': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
703+ 'comment': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
704+ 'departments': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['webcatalog.Department']", 'symmetrical': 'False', 'blank': 'True'}),
705+ 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
706+ 'distroseries': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.DistroSeries']"}),
707+ 'for_purchase': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
708+ 'icon': ('django.db.models.fields.files.ImageField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
709+ 'icon_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
710+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
711+ 'keywords': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
712+ 'mimetype': ('django.db.models.fields.CharField', [], {'max_length': '2048', 'blank': 'True'}),
713+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
714+ 'package_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
715+ 'popcon': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
716+ 'price': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '7', 'decimal_places': '2', 'blank': 'True'}),
717+ 'ratings_average': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '3', 'decimal_places': '2', 'blank': 'True'}),
718+ 'ratings_histogram': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}),
719+ 'ratings_total': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
720+ 'screenshot_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
721+ 'section': ('django.db.models.fields.CharField', [], {'max_length': '32'})
722+ },
723+ 'webcatalog.consumer': {
724+ 'Meta': {'object_name': 'Consumer'},
725+ 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
726+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
727+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
728+ 'secret': ('django.db.models.fields.CharField', [], {'default': "'eMxgHKWQFIAYuEMIIToVaDrRnKNzQb'", 'max_length': '255', 'blank': 'True'}),
729+ 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
730+ 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'oauth_consumer'", 'unique': 'True', 'to': "orm['auth.User']"})
731+ },
732+ 'webcatalog.department': {
733+ 'Meta': {'object_name': 'Department'},
734+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
735+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
736+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Department']", 'null': 'True', 'blank': 'True'}),
737+ 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'null': 'True', 'db_index': 'True'})
738+ },
739+ 'webcatalog.distroseries': {
740+ 'Meta': {'object_name': 'DistroSeries'},
741+ 'code_name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '20', 'db_index': 'True'}),
742+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
743+ 'version': ('django.db.models.fields.CharField', [], {'max_length': '10', 'blank': 'True'})
744+ },
745+ 'webcatalog.exhibit': {
746+ 'Meta': {'object_name': 'Exhibit'},
747+ 'banner_url': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
748+ 'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
749+ 'display': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
750+ 'distroseries': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['webcatalog.DistroSeries']", 'symmetrical': 'False'}),
751+ 'html': ('django.db.models.fields.TextField', [], {}),
752+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
753+ 'package_names': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
754+ 'published': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
755+ 'sca_id': ('django.db.models.fields.IntegerField', [], {})
756+ },
757+ 'webcatalog.machine': {
758+ 'Meta': {'unique_together': "(('owner', 'uuid'),)", 'object_name': 'Machine'},
759+ 'hostname': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
760+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
761+ 'logo_checksum': ('django.db.models.fields.CharField', [], {'max_length': '56', 'blank': 'True'}),
762+ 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
763+ 'package_list': ('django.db.models.fields.TextField', [], {}),
764+ 'packages_checksum': ('django.db.models.fields.CharField', [], {'max_length': '56'}),
765+ 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'})
766+ },
767+ 'webcatalog.nonce': {
768+ 'Meta': {'object_name': 'Nonce'},
769+ 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Consumer']"}),
770+ 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
771+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
772+ 'nonce': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
773+ 'token': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Token']"})
774+ },
775+ 'webcatalog.reviewstatsimport': {
776+ 'Meta': {'object_name': 'ReviewStatsImport'},
777+ 'distroseries': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.DistroSeries']", 'unique': 'True'}),
778+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
779+ 'last_import': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.utcnow'})
780+ },
781+ 'webcatalog.token': {
782+ 'Meta': {'object_name': 'Token'},
783+ 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Consumer']"}),
784+ 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
785+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
786+ 'token': ('django.db.models.fields.CharField', [], {'default': "'wIDOKFDyfTtXPceLzIWJZixhGXlKFwjSushOLHzrXEYlMtidGA'", 'max_length': '50', 'primary_key': 'True'}),
787+ 'token_secret': ('django.db.models.fields.CharField', [], {'default': "'aRrkCDLnsQJCJYgreczXsyLUQujPikUDOydAaQGLQpiqOsVeRp'", 'max_length': '50'}),
788+ 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
789+ }
790+ }
791+
792+ complete_apps = ['webcatalog']
793
794=== added file 'src/webcatalog/migrations/0012_unique_department_slug.py'
795--- src/webcatalog/migrations/0012_unique_department_slug.py 1970-01-01 00:00:00 +0000
796+++ src/webcatalog/migrations/0012_unique_department_slug.py 2012-03-12 15:48:24 +0000
797@@ -0,0 +1,160 @@
798+# encoding: utf-8
799+import datetime
800+from south.db import db
801+from south.v2 import SchemaMigration
802+from django.db import models
803+
804+class Migration(SchemaMigration):
805+
806+ def forwards(self, orm):
807+
808+ # Changing field 'Department.slug'
809+ db.alter_column('webcatalog_department', 'slug', self.gf('django.db.models.fields.SlugField')(default='', unique=True, max_length=50))
810+
811+ # Adding unique constraint on 'Department', fields ['slug']
812+ db.create_unique('webcatalog_department', ['slug'])
813+
814+
815+ def backwards(self, orm):
816+
817+ # Removing unique constraint on 'Department', fields ['slug']
818+ db.delete_unique('webcatalog_department', ['slug'])
819+
820+ # Changing field 'Department.slug'
821+ db.alter_column('webcatalog_department', 'slug', self.gf('django.db.models.fields.SlugField')(max_length=50, null=True))
822+
823+
824+ models = {
825+ 'auth.group': {
826+ 'Meta': {'object_name': 'Group'},
827+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
828+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
829+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
830+ },
831+ 'auth.permission': {
832+ 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
833+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
834+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
835+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
836+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
837+ },
838+ 'auth.user': {
839+ 'Meta': {'object_name': 'User'},
840+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
841+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
842+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
843+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
844+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
845+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
846+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
847+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
848+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
849+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
850+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
851+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
852+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
853+ },
854+ 'contenttypes.contenttype': {
855+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
856+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
857+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
858+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
859+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
860+ },
861+ 'webcatalog.application': {
862+ 'Meta': {'unique_together': "(('distroseries', 'archive_id'),)", 'object_name': 'Application'},
863+ 'app_type': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
864+ 'architectures': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
865+ 'archive_id': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '64', 'null': 'True', 'blank': 'True'}),
866+ 'categories': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
867+ 'channel': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
868+ 'comment': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
869+ 'departments': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['webcatalog.Department']", 'symmetrical': 'False', 'blank': 'True'}),
870+ 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
871+ 'distroseries': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.DistroSeries']"}),
872+ 'for_purchase': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
873+ 'icon': ('django.db.models.fields.files.ImageField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
874+ 'icon_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
875+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
876+ 'keywords': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
877+ 'mimetype': ('django.db.models.fields.CharField', [], {'max_length': '2048', 'blank': 'True'}),
878+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
879+ 'package_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
880+ 'popcon': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
881+ 'price': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '7', 'decimal_places': '2', 'blank': 'True'}),
882+ 'ratings_average': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '3', 'decimal_places': '2', 'blank': 'True'}),
883+ 'ratings_histogram': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}),
884+ 'ratings_total': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
885+ 'screenshot_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
886+ 'section': ('django.db.models.fields.CharField', [], {'max_length': '32'})
887+ },
888+ 'webcatalog.consumer': {
889+ 'Meta': {'object_name': 'Consumer'},
890+ 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
891+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
892+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
893+ 'secret': ('django.db.models.fields.CharField', [], {'default': "'vSCKgfkvLgUIVdNWrMGYJQlttWOpbo'", 'max_length': '255', 'blank': 'True'}),
894+ 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
895+ 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'oauth_consumer'", 'unique': 'True', 'to': "orm['auth.User']"})
896+ },
897+ 'webcatalog.department': {
898+ 'Meta': {'object_name': 'Department'},
899+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
900+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
901+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Department']", 'null': 'True', 'blank': 'True'}),
902+ 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50', 'db_index': 'True'})
903+ },
904+ 'webcatalog.distroseries': {
905+ 'Meta': {'object_name': 'DistroSeries'},
906+ 'code_name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '20', 'db_index': 'True'}),
907+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
908+ 'version': ('django.db.models.fields.CharField', [], {'max_length': '10', 'blank': 'True'})
909+ },
910+ 'webcatalog.exhibit': {
911+ 'Meta': {'object_name': 'Exhibit'},
912+ 'banner_url': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
913+ 'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
914+ 'display': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
915+ 'distroseries': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['webcatalog.DistroSeries']", 'symmetrical': 'False'}),
916+ 'html': ('django.db.models.fields.TextField', [], {}),
917+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
918+ 'package_names': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
919+ 'published': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
920+ 'sca_id': ('django.db.models.fields.IntegerField', [], {})
921+ },
922+ 'webcatalog.machine': {
923+ 'Meta': {'unique_together': "(('owner', 'uuid'),)", 'object_name': 'Machine'},
924+ 'hostname': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
925+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
926+ 'logo_checksum': ('django.db.models.fields.CharField', [], {'max_length': '56', 'blank': 'True'}),
927+ 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
928+ 'package_list': ('django.db.models.fields.TextField', [], {}),
929+ 'packages_checksum': ('django.db.models.fields.CharField', [], {'max_length': '56'}),
930+ 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'})
931+ },
932+ 'webcatalog.nonce': {
933+ 'Meta': {'object_name': 'Nonce'},
934+ 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Consumer']"}),
935+ 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
936+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
937+ 'nonce': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
938+ 'token': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Token']"})
939+ },
940+ 'webcatalog.reviewstatsimport': {
941+ 'Meta': {'object_name': 'ReviewStatsImport'},
942+ 'distroseries': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.DistroSeries']", 'unique': 'True'}),
943+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
944+ 'last_import': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.utcnow'})
945+ },
946+ 'webcatalog.token': {
947+ 'Meta': {'object_name': 'Token'},
948+ 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Consumer']"}),
949+ 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
950+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
951+ 'token': ('django.db.models.fields.CharField', [], {'default': "'POzAvRZABjUIXjSjeDWxcsWSfBIGbCtkcVkVaQmMELqOqUitPR'", 'max_length': '50', 'primary_key': 'True'}),
952+ 'token_secret': ('django.db.models.fields.CharField', [], {'default': "'smrslTHrGlbKfWTzEPNyEIJNPKYmyZKzIValymhyldsXRqdeez'", 'max_length': '50'}),
953+ 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
954+ }
955+ }
956+
957+ complete_apps = ['webcatalog']
958
959=== modified file 'src/webcatalog/models/applications.py'
960--- src/webcatalog/models/applications.py 2012-03-08 18:00:09 +0000
961+++ src/webcatalog/models/applications.py 2012-03-12 15:48:24 +0000
962@@ -29,6 +29,7 @@
963 from django.contrib.auth.models import User
964 from django.core.urlresolvers import reverse
965 from django.db import models
966+from django.template.defaultfilters import slugify
967
968 from webcatalog.department_filters import department_filters
969 from webcatalog.managers import ApplicationManager
970@@ -127,7 +128,7 @@
971 for dept_filter in dept_filters:
972 if dept_filter(self):
973 dept, created = Department.objects.get_or_create(
974- name=dept_name)
975+ name=dept_name, defaults={'slug': slugify(dept_name)})
976 if created:
977 logging.warn("Department %s automatically created!" %
978 dept_name)
979@@ -162,24 +163,20 @@
980 class Department(models.Model):
981 parent = models.ForeignKey('self', blank=True, null=True)
982 name = models.CharField(max_length=64)
983+ slug = models.SlugField(unique=True)
984
985 def __unicode__(self):
986 return self.name
987
988- @property
989- def normalized_name(self):
990- return re.sub(r'[-&.,;" \']', '', self.name)
991-
992 def crumbs(self, distro=None):
993 if self.parent:
994 crumbs = self.parent.crumbs(distro=distro)
995 else:
996 crumbs = [{'name': 'Get Software', 'url': reverse('wc-index')}]
997- args = []
998+ kwargs = {'dept_slug_or_id': self.slug}
999 if distro is not None:
1000- args = [distro]
1001- args.append(self.id)
1002- url = reverse('wc-department', args=args)
1003+ kwargs['distro'] = distro
1004+ url = reverse('wc-department', kwargs=kwargs)
1005 crumbs.append({'name': self.name, 'url': url})
1006 return crumbs
1007
1008
1009=== modified file 'src/webcatalog/static/css/carousel.css'
1010--- src/webcatalog/static/css/carousel.css 2012-03-08 18:00:09 +0000
1011+++ src/webcatalog/static/css/carousel.css 2012-03-12 15:48:24 +0000
1012@@ -133,6 +133,11 @@
1013 padding: 0 10px;
1014 width: 202px;
1015 }
1016+
1017+#content .featured-widget table td.lastrow {
1018+ border-right: 0;
1019+}
1020+
1021 #featured-carousel .pagination li a.active {
1022 background-color: #333;
1023 }
1024
1025=== renamed file 'src/webcatalog/static/images/dept_icons/3D.png' => 'src/webcatalog/static/images/dept_icons/3d.png'
1026=== renamed file 'src/webcatalog/static/images/dept_icons/Accessories.png' => 'src/webcatalog/static/images/dept_icons/accessories.png'
1027=== renamed file 'src/webcatalog/static/images/dept_icons/Astronomy.png' => 'src/webcatalog/static/images/dept_icons/astronomy.png'
1028=== renamed file 'src/webcatalog/static/images/dept_icons/Biology.png' => 'src/webcatalog/static/images/dept_icons/biology.png'
1029=== renamed file 'src/webcatalog/static/images/dept_icons/BoardGames.png' => 'src/webcatalog/static/images/dept_icons/board-games.png'
1030=== renamed file 'src/webcatalog/static/images/dept_icons/CardGames.png' => 'src/webcatalog/static/images/dept_icons/card-games.png'
1031=== renamed file 'src/webcatalog/static/images/dept_icons/Chat.png' => 'src/webcatalog/static/images/dept_icons/chat.png'
1032=== renamed file 'src/webcatalog/static/images/dept_icons/Chemistry.png' => 'src/webcatalog/static/images/dept_icons/chemistry.png'
1033=== renamed file 'src/webcatalog/static/images/dept_icons/ComputingRobotics.png' => 'src/webcatalog/static/images/dept_icons/computing-robotics.png'
1034=== renamed file 'src/webcatalog/static/images/dept_icons/Debugging.png' => 'src/webcatalog/static/images/dept_icons/debugging.png'
1035=== renamed file 'src/webcatalog/static/images/dept_icons/DeveloperTools.png' => 'src/webcatalog/static/images/dept_icons/developer-tools.png'
1036=== renamed file 'src/webcatalog/static/images/dept_icons/Drawing.png' => 'src/webcatalog/static/images/dept_icons/drawing.png'
1037=== renamed file 'src/webcatalog/static/images/dept_icons/Education.png' => 'src/webcatalog/static/images/dept_icons/education.png'
1038=== renamed file 'src/webcatalog/static/images/dept_icons/Electronics.png' => 'src/webcatalog/static/images/dept_icons/electronics.png'
1039=== renamed file 'src/webcatalog/static/images/dept_icons/Engineering.png' => 'src/webcatalog/static/images/dept_icons/engineering.png'
1040=== renamed file 'src/webcatalog/static/images/dept_icons/FileSharing.png' => 'src/webcatalog/static/images/dept_icons/file-sharing.png'
1041=== renamed file 'src/webcatalog/static/images/dept_icons/Fonts.png' => 'src/webcatalog/static/images/dept_icons/fonts.png'
1042=== renamed file 'src/webcatalog/static/images/dept_icons/Games.png' => 'src/webcatalog/static/images/dept_icons/games.png'
1043=== renamed file 'src/webcatalog/static/images/dept_icons/Geography.png' => 'src/webcatalog/static/images/dept_icons/geography.png'
1044=== renamed file 'src/webcatalog/static/images/dept_icons/Geology.png' => 'src/webcatalog/static/images/dept_icons/geology.png'
1045=== renamed file 'src/webcatalog/static/images/dept_icons/GraphicInterfaceDesign.png' => 'src/webcatalog/static/images/dept_icons/graphic-interface-design.png'
1046=== renamed file 'src/webcatalog/static/images/dept_icons/Graphics.png' => 'src/webcatalog/static/images/dept_icons/graphics.png'
1047=== renamed file 'src/webcatalog/static/images/dept_icons/IDEs.png' => 'src/webcatalog/static/images/dept_icons/ides.png'
1048=== renamed file 'src/webcatalog/static/images/dept_icons/Internet.png' => 'src/webcatalog/static/images/dept_icons/internet.png'
1049=== renamed file 'src/webcatalog/static/images/dept_icons/Localization.png' => 'src/webcatalog/static/images/dept_icons/localization.png'
1050=== renamed file 'src/webcatalog/static/images/dept_icons/Mail.png' => 'src/webcatalog/static/images/dept_icons/mail.png'
1051=== renamed file 'src/webcatalog/static/images/dept_icons/Mathematics.png' => 'src/webcatalog/static/images/dept_icons/mathematics.png'
1052=== renamed file 'src/webcatalog/static/images/dept_icons/Office.png' => 'src/webcatalog/static/images/dept_icons/office.png'
1053=== renamed file 'src/webcatalog/static/images/dept_icons/Painting.png' => 'src/webcatalog/static/images/dept_icons/painting.png'
1054=== renamed file 'src/webcatalog/static/images/dept_icons/Photography.png' => 'src/webcatalog/static/images/dept_icons/photography.png'
1055=== renamed file 'src/webcatalog/static/images/dept_icons/Physics.png' => 'src/webcatalog/static/images/dept_icons/physics.png'
1056=== renamed file 'src/webcatalog/static/images/dept_icons/Profiling.png' => 'src/webcatalog/static/images/dept_icons/profiling.png'
1057=== renamed file 'src/webcatalog/static/images/dept_icons/Publishing.png' => 'src/webcatalog/static/images/dept_icons/publishing.png'
1058=== renamed file 'src/webcatalog/static/images/dept_icons/Puzzles.png' => 'src/webcatalog/static/images/dept_icons/puzzles.png'
1059=== renamed file 'src/webcatalog/static/images/dept_icons/RolePlaying.png' => 'src/webcatalog/static/images/dept_icons/role-playing.png'
1060=== renamed file 'src/webcatalog/static/images/dept_icons/ScanningOCR.png' => 'src/webcatalog/static/images/dept_icons/scanning-ocr.png'
1061=== renamed file 'src/webcatalog/static/images/dept_icons/ScienceEngineering.png' => 'src/webcatalog/static/images/dept_icons/science-engineering.png'
1062=== renamed file 'src/webcatalog/static/images/dept_icons/SoundVideo.png' => 'src/webcatalog/static/images/dept_icons/sound-video.png'
1063=== renamed file 'src/webcatalog/static/images/dept_icons/Sports.png' => 'src/webcatalog/static/images/dept_icons/sports.png'
1064=== renamed file 'src/webcatalog/static/images/dept_icons/ThemesTweaks.png' => 'src/webcatalog/static/images/dept_icons/themes-tweaks.png'
1065=== renamed file 'src/webcatalog/static/images/dept_icons/UniversalAccess.png' => 'src/webcatalog/static/images/dept_icons/universal-access.png'
1066=== renamed file 'src/webcatalog/static/images/dept_icons/VersionControl.png' => 'src/webcatalog/static/images/dept_icons/version-control.png'
1067=== renamed file 'src/webcatalog/static/images/dept_icons/Viewers.png' => 'src/webcatalog/static/images/dept_icons/viewers.png'
1068=== renamed file 'src/webcatalog/static/images/dept_icons/WebBrowsers.png' => 'src/webcatalog/static/images/dept_icons/web-browsers.png'
1069=== renamed file 'src/webcatalog/static/images/dept_icons/WebDevelopment.png' => 'src/webcatalog/static/images/dept_icons/web-development.png'
1070=== modified file 'src/webcatalog/templates/webcatalog/department_overview.html'
1071--- src/webcatalog/templates/webcatalog/department_overview.html 2011-09-12 18:37:16 +0000
1072+++ src/webcatalog/templates/webcatalog/department_overview.html 2012-03-12 15:48:24 +0000
1073@@ -16,7 +16,7 @@
1074 {% ifequal ds.code_name distroseries %}
1075 <span>Ubuntu {{ ds.version }} ({{ ds.code_name }})</span>
1076 {% else %}
1077- <a href="{% url wc-department ds.code_name dept.id %}">Ubuntu {{ ds.version }} ({{ ds.code_name }})</a>
1078+ <a href="{% url wc-department distro=ds.code_name dept_slug_or_id=dept.slug %}">Ubuntu {{ ds.version }} ({{ ds.code_name }})</a>
1079 {% endifequal %}</li>
1080 {% endfor %}
1081 </ul>
1082
1083=== modified file 'src/webcatalog/templates/webcatalog/department_overview_snippet.html'
1084--- src/webcatalog/templates/webcatalog/department_overview_snippet.html 2011-04-20 19:41:44 +0000
1085+++ src/webcatalog/templates/webcatalog/department_overview_snippet.html 2012-03-12 15:48:24 +0000
1086@@ -1,8 +1,8 @@
1087 <div class="department">
1088 <div class="dept-icon">
1089- <a href="{% url wc-department dept.id %}">
1090+ <a href="{% url wc-department dept_slug_or_id=dept.slug %}">
1091 <img width="48" height="48"
1092- src="{{ STATIC_URL }}images/dept_icons/{{ dept.normalized_name }}.png"/>
1093+ src="{{ STATIC_URL }}images/dept_icons/{{ dept.slug }}.png"/>
1094 </a></div>
1095- <h3><a href="{% url wc-department dept.id %}">{{ dept.name }}</a></h3>
1096+ <h3><a href="{% url wc-department dept_slug_or_id=dept.slug %}">{{ dept.name }}</a></h3>
1097 </div>
1098
1099=== modified file 'src/webcatalog/templates/webcatalog/featured_apps_widget.html'
1100--- src/webcatalog/templates/webcatalog/featured_apps_widget.html 2012-03-08 18:21:07 +0000
1101+++ src/webcatalog/templates/webcatalog/featured_apps_widget.html 2012-03-12 15:48:24 +0000
1102@@ -12,9 +12,9 @@
1103 Javascript.
1104 {% endcomment %}
1105 <li class="slide{% if forloop.counter > 1%} disabled{% endif %}">
1106- <table><tr>
1107+ <table class="apps"><tr>
1108 {% for app in featured_apps %}
1109- <td>
1110+ <td{% if forloop.counter|divisibleby:4 %} class="lastrow"{% endif %}>
1111 {% if app.icon %}
1112 <img class="icon64" src="{{ app.icon.url }}"/>
1113 {% else %}
1114@@ -24,11 +24,11 @@
1115 <p>{{ app.departments.all.0.name }} | <b>{% if app.price %}${{ app.price }}{% else %}FREE{% endif %}</b></p>
1116 <p>{{ app.comment }}</p>
1117 </td>
1118- {% if forloop.counter|divisibleby:4 %}
1119+ {% if forloop.counter|divisibleby:4 and not forloop.last %}
1120 </tr></table>
1121 </li>
1122 <li class="slide{% if forloop.counter > 1%} disabled{% endif %}">
1123- <table><tr>
1124+ <table class="apps"><tr>
1125 {% endif %}
1126 {% endfor %}
1127 </tr></table>
1128
1129=== modified file 'src/webcatalog/tests/factory.py'
1130--- src/webcatalog/tests/factory.py 2012-03-01 21:38:31 +0000
1131+++ src/webcatalog/tests/factory.py 2012-03-12 15:48:24 +0000
1132@@ -112,8 +112,10 @@
1133 ratings_average=ratings_average, ratings_total=ratings_total,
1134 ratings_histogram=ratings_histogram, screenshot_url=screenshot_url)
1135
1136- def make_department(self, name, parent=None):
1137- return Department.objects.create(name=name, parent=parent)
1138+ def make_department(self, name, parent=None, slug=None):
1139+ if slug is None:
1140+ slug = self.get_unique_string(prefix='slug-')
1141+ return Department.objects.create(name=name, parent=parent, slug=slug)
1142
1143 def make_exhibit(self, package_names=None, published=True, display=None,
1144 distroseries=None):
1145
1146=== modified file 'src/webcatalog/tests/test_models.py'
1147--- src/webcatalog/tests/test_models.py 2012-03-01 21:38:31 +0000
1148+++ src/webcatalog/tests/test_models.py 2012-03-12 15:48:24 +0000
1149@@ -25,7 +25,7 @@
1150 from django.core.urlresolvers import reverse
1151
1152 from webcatalog.tests.factory import TestCaseWithFactory
1153-from webcatalog.models import Application
1154+from webcatalog.models import Department
1155
1156 __metaclass__ = type
1157 __all__ = [
1158@@ -78,7 +78,8 @@
1159 dept = app.departments.get()
1160 expected = [{'name': 'Get Software', 'url': reverse('wc-index')},
1161 {'name': dept.name, 'url': reverse('wc-department',
1162- args=[app.distroseries.code_name, dept.id])},
1163+ kwargs={'distro': app.distroseries.code_name,
1164+ 'dept_slug_or_id': dept.slug})},
1165 {'name': app.name, 'url': reverse('wc-package-detail',
1166 args=[app.distroseries.code_name, app.package_name])}]
1167
1168@@ -91,33 +92,23 @@
1169 dept = app.departments.get()
1170 expected = [{'name': 'Get Software', 'url': reverse('wc-index')},
1171 {'name': dept.parent.name, 'url': reverse('wc-department',
1172- args=[app.distroseries.code_name, dept.parent.id])},
1173+ kwargs={'distro': app.distroseries.code_name,
1174+ 'dept_slug_or_id': dept.parent.slug})},
1175 {'name': dept.name, 'url': reverse('wc-department',
1176- args=[app.distroseries.code_name, dept.id])},
1177+ kwargs={'distro': app.distroseries.code_name,
1178+ 'dept_slug_or_id': dept.slug})},
1179 {'name': app.name, 'url': reverse('wc-package-detail',
1180 args=[app.distroseries.code_name, app.package_name])}]
1181
1182 self.assertEquals(expected, app.crumbs())
1183
1184
1185-def DepartmentTestCase(TestCaseWithFactory):
1186- def test_normalized_name(self):
1187- cases = {
1188- 'Foo': 'Foo',
1189- 'Foo & Bar': 'FooBar',
1190- ' Foo, Bar ': 'FooBar',
1191- ' && , ': '',
1192- '': ''
1193- }
1194- for case, expected in cases.items():
1195- dept = self.factory.make_department(case)
1196- self.assertEquals(dept.normalized_name, expected)
1197-
1198+class DepartmentTestCase(TestCaseWithFactory):
1199 def test_crumbs_no_parent(self):
1200 dept = self.factory.make_department('Foo')
1201 expected = [{'name': 'Get Software', 'url': reverse('wc-index')},
1202 {'name': dept.name, 'url': reverse('wc-department',
1203- args=[dept.id])}]
1204+ kwargs={'dept_slug_or_id': dept.slug})}]
1205
1206 self.assertEquals(expected, dept.crumbs())
1207
1208@@ -126,9 +117,9 @@
1209 dept = self.factory.make_department('Bar', parent=parent)
1210 expected = [{'name': 'Get Software', 'url': reverse('wc-index')},
1211 {'name': dept.parent.name, 'url': reverse('wc-department',
1212- args=[dept.parent.id])},
1213+ kwargs={'dept_slug_or_id': dept.parent.slug})},
1214 {'name': dept.name, 'url': reverse('wc-department',
1215- args=[dept.id])}]
1216+ kwargs={'dept_slug_or_id': dept.slug})}]
1217
1218 self.assertEquals(expected, dept.crumbs())
1219
1220@@ -137,9 +128,11 @@
1221 dept = self.factory.make_department('Bar', parent=parent)
1222 expected = [{'name': 'Get Software', 'url': reverse('wc-index')},
1223 {'name': dept.parent.name, 'url': reverse('wc-department',
1224- args=['frobbly', dept.parent.id])},
1225+ kwargs={'distro': 'frobbly',
1226+ 'dept_slug_or_id': dept.parent.slug})},
1227 {'name': dept.name, 'url': reverse('wc-department',
1228- args=['frobbly', dept.id])}]
1229+ kwargs={'distro': 'frobbly',
1230+ 'dept_slug_or_id': dept.slug})}]
1231
1232 self.assertEquals(expected, dept.crumbs(distro='frobbly'))
1233
1234
1235=== modified file 'src/webcatalog/tests/test_views.py'
1236--- src/webcatalog/tests/test_views.py 2012-03-12 09:12:56 +0000
1237+++ src/webcatalog/tests/test_views.py 2012-03-12 15:48:24 +0000
1238@@ -489,7 +489,7 @@
1239 response = self.client.get(reverse('wc-index'))
1240
1241 self.assertContains(response, reverse('wc-department',
1242- args=[dept.id]))
1243+ kwargs={'dept_slug_or_id': dept.slug}))
1244
1245 def test_exhibits_widget_doesnt_display_if_no_exhibits_published(self):
1246 self.factory.make_exhibit(published=False)
1247@@ -524,6 +524,15 @@
1248
1249 self.assertEqual([app], response.context[0]['featured_apps'])
1250
1251+ def test_featured_apps_doesnt_show_blank_slides(self):
1252+ # Make 4 apps to fill exactly one slide
1253+ apps = [self.factory.make_application() for x in range(4)]
1254+
1255+ with patch_settings(FEATURED_APPS=[app.package_name for app in apps]):
1256+ response = self.client.get(reverse('wc-index'))
1257+
1258+ self.assertContains(response, '<table class="apps">', 1)
1259+
1260 def test_link_to_dev_site(self):
1261 response = self.client.get(reverse('wc-index'))
1262
1263@@ -539,19 +548,19 @@
1264 subdept = self.factory.make_department('bar', parent=dept)
1265 distro = self.factory.make_distroseries(code_name='lucid')
1266
1267- response = self.client.get(reverse('wc-department', args=[
1268- 'lucid', dept.id]))
1269+ response = self.client.get(reverse('wc-department', kwargs={
1270+ 'distro': 'lucid', 'dept_slug_or_id': dept.id}))
1271
1272 self.assertContains(response, reverse('wc-department',
1273- args=[subdept.id]))
1274+ kwargs={'dept_slug_or_id': subdept.slug}))
1275
1276 def test_department_contains_links_to_apps(self):
1277 app = self.factory.make_application(package_name='foo')
1278 dept = self.factory.make_department('bar')
1279 app.departments.add(dept)
1280
1281- response = self.client.get(reverse('wc-department', args=[
1282- app.distroseries.code_name, dept.id]))
1283+ response = self.client.get(reverse('wc-department', kwargs={
1284+ 'distro': app.distroseries.code_name, 'dept_slug_or_id': dept.id}))
1285
1286 self.assertContains(response, reverse('wc-package-detail',
1287 args=[app.distroseries.code_name, app.package_name]), count=2)
1288@@ -560,8 +569,8 @@
1289 dept = self.factory.make_department('bar')
1290 distro = self.factory.make_distroseries(code_name='lucid')
1291
1292- response = self.client.get(reverse('wc-department', args=[
1293- 'lucid', dept.id]))
1294+ response = self.client.get(reverse('wc-department', kwargs={
1295+ 'distro': 'lucid', 'dept_slug_or_id': dept.id}))
1296
1297 self.assertNotContains(response, 'Subsections')
1298
1299@@ -572,7 +581,8 @@
1300 app = self.factory.make_application(distroseries=lucid)
1301 app.departments.add(dept)
1302
1303- url = reverse('wc-department', args=['lucid', dept.id])
1304+ url = reverse('wc-department',
1305+ kwargs={'distro': 'lucid', 'dept_slug_or_id': dept.id})
1306 with patch_settings(PAGE_BATCH_SIZE=2):
1307 response = self.client.get(url)
1308
1309@@ -589,7 +599,8 @@
1310 def test_invalid_page_doesnt_error(self):
1311 dept = self.factory.make_department('bar')
1312 distro = self.factory.make_distroseries(code_name='lucid')
1313- url = reverse('wc-department', args=['lucid', dept.id])
1314+ url = reverse('wc-department',
1315+ kwargs={'distro': 'lucid', 'dept_slug_or_id': dept.id})
1316 response = self.client.get(url, data={'page': 'aef8'})
1317 page = response.context['page']
1318 self.assertEqual(1, page.number)
1319@@ -611,11 +622,15 @@
1320 maverick = self.factory.make_distroseries(code_name='maverick',
1321 version='10.10')
1322
1323- response = self.client.get(reverse('wc-department', args=[
1324- settings.DEFAULT_DISTRO, dept.id]))
1325+ response = self.client.get(reverse('wc-department',
1326+ kwargs={
1327+ 'distro': settings.DEFAULT_DISTRO,
1328+ 'dept_slug_or_id': dept.id
1329+ }))
1330
1331 for ds in ['lucid', 'maverick']:
1332- url = reverse('wc-department', args=[ds, dept.id])
1333+ url = reverse('wc-department',
1334+ kwargs={'distro': ds, 'dept_slug_or_id': dept.slug})
1335 self.assertContains(response, '<a href="{0}">Ubuntu'.format(url))
1336
1337 def test_department_includes_rating_summary(self):
1338@@ -625,7 +640,8 @@
1339 ratings_average=Decimal('3.5'), ratings_total=23)
1340 app.departments.add(dept)
1341
1342- url = reverse('wc-department', args=['lucid', dept.id])
1343+ url = reverse('wc-department',
1344+ kwargs={'distro': 'lucid', 'dept_slug_or_id': dept.id})
1345 response = self.client.get(url)
1346
1347 self.assertContains(response, 'images/star-small-1.png', 3)
1348@@ -634,8 +650,8 @@
1349 def test_invalid_distroseries_returns_404(self):
1350 dept = self.factory.make_department('bar')
1351
1352- response = self.client.get(reverse('wc-department', args=[
1353- 'amnesiac', dept.id]))
1354+ response = self.client.get(reverse('wc-department',
1355+ kwargs={'distro': 'amnesiac', 'dept_slug_or_id': dept.id}))
1356
1357 self.assertEqual(404, response.status_code)
1358
1359@@ -648,15 +664,29 @@
1360 maverick = self.factory.make_distroseries(code_name='maverick',
1361 version='10.10')
1362
1363- response = self.client.get(reverse('wc-department', args=[
1364- settings.DEFAULT_DISTRO, dept.id]))
1365+ response = self.client.get(reverse('wc-department',
1366+ kwargs={
1367+ 'distro': settings.DEFAULT_DISTRO,
1368+ 'dept_slug_or_id': dept.id
1369+ }))
1370
1371- url = reverse('wc-department', args=['lucid', dept.id])
1372+ url = reverse('wc-department',
1373+ kwargs={'distro': 'lucid', 'dept_slug_or_id': dept.slug})
1374 lucid_pos = response.content.find('<a href="{0}">Ubuntu'.format(url))
1375- url = reverse('wc-department', args=['maverick', dept.id])
1376+ url = reverse('wc-department',
1377+ kwargs={'distro': 'maverick', 'dept_slug_or_id': dept.slug})
1378 maver_pos = response.content.find('<a href="{0}">Ubuntu'.format(url))
1379 self.assertTrue(lucid_pos > maver_pos)
1380
1381+ def test_get_department_by_slug(self):
1382+ dept = self.factory.make_department('bar')
1383+ self.factory.make_distroseries(code_name='lucid')
1384+
1385+ response = self.client.get(reverse('wc-department', kwargs={
1386+ 'distro': 'lucid', 'dept_slug_or_id': dept.slug}))
1387+
1388+ self.assertEqual(200, response.status_code)
1389+
1390
1391 class ApplicationReviewsTestCase(TestCaseWithFactory):
1392
1393
1394=== modified file 'src/webcatalog/urls.py'
1395--- src/webcatalog/urls.py 2012-03-07 21:23:46 +0000
1396+++ src/webcatalog/urls.py 2012-03-12 15:48:24 +0000
1397@@ -31,9 +31,9 @@
1398
1399 urlpatterns = patterns('webcatalog.views',
1400 url(r'^$', 'index', name='wc-index'),
1401- url(r'^department/(?P<distro>[-.+\w]+)/(?P<dept_id>\d+)/$',
1402+ url(r'^department/(?P<distro>[-.+\w]+)/(?P<dept_slug_or_id>[\w\d-]+)/$',
1403 'department_overview', name='wc-department'),
1404- url(r'^department/(?P<dept_id>\d+)/$', 'department_overview',
1405+ url(r'^department/(?P<dept_slug_or_id>[\w\d-]+)/$', 'department_overview',
1406 name='wc-department'),
1407 url(r'^applications/(?P<distro>[-.+\w]+)/(?P<package_name>[-.+\w]+)/$',
1408 'application_detail', name="wc-package-detail"),
1409
1410=== modified file 'src/webcatalog/views.py'
1411--- src/webcatalog/views.py 2012-03-08 22:20:32 +0000
1412+++ src/webcatalog/views.py 2012-03-12 15:48:24 +0000
1413@@ -34,7 +34,10 @@
1414 from django.core.paginator import Paginator
1415 from django.core.urlresolvers import reverse
1416 from django.db.models import Q
1417-from django.http import HttpResponseRedirect, HttpResponse
1418+from django.http import (
1419+ HttpResponseRedirect,
1420+ HttpResponse,
1421+ )
1422 from django.shortcuts import (
1423 get_object_or_404,
1424 render_to_response,
1425@@ -135,7 +138,7 @@
1426 context_instance=context)
1427
1428
1429-def department_overview(request, dept_id, distro=None):
1430+def department_overview(request, dept_slug_or_id=None, distro=None):
1431 if distro is None:
1432 useragent = UserAgentString(request.META.get('HTTP_USER_AGENT', ''))
1433 # Check for the distroseries in the useragent, if we have it,
1434@@ -144,13 +147,17 @@
1435 distro = useragent.distroseries
1436 else:
1437 distro = settings.DEFAULT_DISTRO
1438- return HttpResponseRedirect(
1439- reverse('wc-department', args=[distro, dept_id]))
1440+ url = reverse('wc-department',
1441+ kwargs={'distro': distro, 'dept_slug_or_id': dept_slug_or_id})
1442+ return HttpResponseRedirect(url)
1443
1444 # Attempt to retrieve the DistroSeries to ensure that it actually exists
1445 get_object_or_404(DistroSeries, code_name=distro)
1446
1447- dept = get_object_or_404(Department, pk=dept_id)
1448+ if dept_slug_or_id.isdigit():
1449+ dept = get_object_or_404(Department, pk=dept_slug_or_id)
1450+ else:
1451+ dept = get_object_or_404(Department, slug=dept_slug_or_id)
1452 subdepts = Department.objects.filter(parent=dept)
1453 subdepts = subdepts.order_by('name')
1454

Subscribers

People subscribed via source and target branches