Merge lp:~elachuni/ubuntu-webcatalog/departments into lp:ubuntu-webcatalog

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
Reviewer Review Type Date Requested Status
Michael Nelson (community) Approve
Review via email: mp+57424@code.launchpad.net

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_app_install_data.py to avoid UnicodeEncodeErrors
 - Changed Application.package_name from a SlugField to a CharField, as there are package names with periods in them
 - Extended max_length for Application.mimetype
 - Allowed blank Application.app_type and Application.categories

To post a comment you must log in.
Revision history for this message
Michael Nelson (michael.nelson) wrote :

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.

review: Approve

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

Subscribers

People subscribed via source and target branches