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

Proposed by Anthony Lenton
Status: Merged
Approved by: Anthony Lenton
Approved revision: 39
Merged at revision: 31
Proposed branch: lp:~elachuni/ubuntu-webcatalog/xpmreader
Merge into: lp:ubuntu-webcatalog
Diff against target: 539 lines (+304/-52)
11 files modified
fabtasks/bootstrap.py (+39/-0)
src/webcatalog/management/commands/import_app_install_data.py (+54/-46)
src/webcatalog/tests/__init__.py (+1/-0)
src/webcatalog/tests/test_commands.py (+2/-6)
src/webcatalog/tests/test_data/access.svg (+127/-0)
src/webcatalog/tests/test_data/junk.svg (+4/-0)
src/webcatalog/tests/test_data/junk.xpm (+4/-0)
src/webcatalog/tests/test_data/mini-gsmc.xpm (+23/-0)
src/webcatalog/tests/test_utilities.py (+35/-0)
src/webcatalog/utilities.py (+11/-0)
src/webcatalog/views.py (+4/-0)
To merge this branch: bzr merge lp:~elachuni/ubuntu-webcatalog/xpmreader
Reviewer Review Type Date Requested Status
Canonical ISD hackers Pending
Review via email: mp+66510@code.launchpad.net

Commit message

Use gtk to render xpm and svg icons

Description of the change

Overview
========
This branch adds an xpmreader utility that will convert xpm icons to png format for using within the webcatalog.

Details
=======
PIL only supports reading xpms with less than 256 colors, so a small utility was added instead to parse the relatively simple xpm file format.
Tests included.

To test
=======
Follow the instructions in the readme, but basically just run: fab bootstrap test

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

This has been tested against the whole set of xpm icons pulled in by both natty's and maverick's app-install-data.

Revision history for this message
ISD Branch Mangler (isd-branches-mangler) wrote :

There are additional revisions which have not been approved in review. Please seek review and approval of these new revisions.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'fabtasks/bootstrap.py'
2--- fabtasks/bootstrap.py 2011-06-18 23:17:11 +0000
3+++ fabtasks/bootstrap.py 2011-07-01 15:38:25 +0000
4@@ -31,6 +31,7 @@
5 local("virtualenv/bin/pip install -r requirements.txt", capture=False)
6 local("virtualenv/bin/python setup.py develop", capture=False)
7 symlink_python_apt()
8+ symlink_gtk2()
9
10
11 def symlink_python_apt():
12@@ -48,6 +49,44 @@
13 virtualenv_libdir + 'site-packages/apt_inst.so')
14
15
16+def symlink_gtk2():
17+ minor = sys.version_info[1]
18+
19+ def _symlink_libs(filenames):
20+ for filename in filenames:
21+ basename = os.path.basename(filename)
22+ _symlink(filename, 'virtualenv/lib/python2.%s/site-packages/%s' %
23+ (minor, basename))
24+
25+ _symlink_libs([
26+ '/usr/lib/pymodules/python2.%s/gtk-2.0' % minor,
27+ '/usr/lib/pymodules/python2.%s/cairo' % minor,
28+ ])
29+ pth_file_prenatty = '/usr/lib/pymodules/python2.%s/pygtk.pth' % minor
30+ pth_file_natty = '/usr/share/pyshared/pygtk.pth'
31+ if os.path.exists(pth_file_prenatty):
32+ _symlink_libs([
33+ pth_file_prenatty,
34+ '/usr/lib/pymodules/python2.%s/pygtk.py' % minor,
35+ ])
36+ elif os.path.exists(pth_file_natty):
37+ _symlink_libs([
38+ pth_file_natty,
39+ '/usr/lib/pymodules/python2.%s/cairo' % minor,
40+ '/usr/lib/python2.%s/dist-packages/glib' % minor,
41+ '/usr/lib/python2.%s/dist-packages/gobject' % minor,
42+ '/usr/lib/python2.%s/dist-packages/gtk-2.0' % minor,
43+ '/usr/lib/python2.%s/dist-packages/gtk-2.0-pysupport-compat.pth'
44+ % minor,
45+ '/usr/share/pyshared/pygtk.py',
46+ '/usr/lib/python2.%s/dist-packages/gtk-2.0/gio' % minor,
47+ '/usr/lib/python2.%s/dist-packages/gtk-2.0/LaunchpadIntegration'
48+ % minor,
49+ ])
50+ else:
51+ raise EnvironmentError("Unable to find system-wide gtk2 installation")
52+
53+
54 def _symlink(source, link_name):
55 if not os.path.exists(link_name):
56 os.symlink(os.path.abspath(source), link_name)
57
58=== modified file 'src/webcatalog/management/commands/import_app_install_data.py'
59--- src/webcatalog/management/commands/import_app_install_data.py 2011-06-24 07:54:02 +0000
60+++ src/webcatalog/management/commands/import_app_install_data.py 2011-07-01 15:38:25 +0000
61@@ -42,6 +42,7 @@
62 Application,
63 DistroSeries,
64 )
65+from webcatalog.utilities import create_png_from_file
66
67 __metaclass__ = type
68 __all__ = [
69@@ -110,20 +111,19 @@
70 help=('Use a local app-install-data deb package rather than '
71 'downloading from the archive.')),
72 )
73- verbosity = 0
74+ verbosity = 1
75
76 def handle_label(self, distroseries_name, **options):
77+ self.verbosity = int(options['verbosity'])
78+ data_dir = tempfile.mkdtemp()
79+ # First create the apps based on the current cache.
80+ distroseries, created = DistroSeries.objects.get_or_create(
81+ code_name=distroseries_name)
82+ if created:
83+ self.output(
84+ "Created a DistroSeries record called '{0}'.\n".format(
85+ distroseries_name), 1)
86 with Cache(distroseries_name) as cache:
87- self.verbosity = int(options['verbosity'])
88- data_dir = tempfile.mkdtemp()
89-
90- # First create the apps based on the current cache.
91- distroseries, created = DistroSeries.objects.get_or_create(
92- code_name=distroseries_name)
93- if created and self.verbosity > 0:
94- self.stdout.write(
95- "Created a DistroSeries record with codename '{0}'.".format(
96- distroseries_name))
97 self.update_from_cache(cache, distroseries_name)
98
99 # Download app install data when requested.
100@@ -135,40 +135,38 @@
101
102 # Extract and parse the deb archive.
103 deb_file = DebFile(deb_location)
104- if self.verbosity > 0:
105- self.stdout.write(
106- "Processing application data to update database...\n")
107+ self.output("Processing application data to update database...\n", 1)
108 deb_file.data.extractall(data_dir)
109 matcher = data_dir + '/usr/share/app-install/desktop/*.desktop'
110 icon_dir = data_dir + '/usr/share/app-install/icons/'
111
112 for desktop_file in iglob(matcher):
113- self.process_desktop_file(desktop_file, icon_dir, distroseries,
114- cache)
115+ if self.verbosity > 1:
116+ self.output("Processing {0}\n".format(desktop_file))
117+ elif self.verbosity > 0:
118+ self.output(".", flush=True)
119+ self.process_desktop_file(desktop_file, icon_dir, distroseries)
120
121 shutil.rmtree(data_dir)
122
123- def get_latest_app_data_for_series(self, distroseries, working_dir, cache=None):
124+ def get_latest_app_data_for_series(self, distroseries, working_dir,
125+ cache=None):
126 # Write a temporary sources.list, update the cache and access:
127 # cache['app-install-data-ubuntu'].candidate.uri
128- install_data_url = self.get_app_data_uri_for_series(distroseries, cache)
129+ install_data_url = self.get_app_data_uri_for_series(distroseries,
130+ cache)
131 rest, sep, deb_filename = install_data_url.rpartition('/')
132
133- if self.verbosity > 0:
134- self.stdout.write(
135- u"Downloading app-install-data-ubuntu from {0}...".format(
136- install_data_url))
137+ self.output(u"Downloading app-install-data from {0}...\n".format(
138+ install_data_url), 1)
139 deb_location = os.path.join(working_dir, deb_filename)
140 urllib.urlretrieve(install_data_url, deb_location)
141- if self.verbosity > 0:
142- self.stdout.write("Done.\n")
143+ self.output("Done.\n", 1)
144 return deb_location
145
146 def get_app_data_uri_for_series(self, distroseries, cache=None):
147- if self.verbosity > 0:
148- self.stdout.write(
149- "Determining URI for current version of "
150- "app-install-data in {0}.\n".format(distroseries))
151+ self.output("Determining URI for current version of "
152+ "app-install-data in {0}.\n".format(distroseries), 1)
153 uri = None
154 if cache == None:
155 with Cache(distroseries) as cache:
156@@ -177,8 +175,7 @@
157 uri = cache['app-install-data'].candidate.uri
158 return uri
159
160- def process_desktop_file(self, desktop_file_path, icon_dir, distroseries,
161- cache):
162+ def process_desktop_file(self, desktop_file_path, icon_dir, distroseries):
163 with open(desktop_file_path, 'r') as desktop_file:
164 data = desktop_file.read()
165 form = ApplicationForm.get_form_from_desktop_data(data, distroseries)
166@@ -189,15 +186,12 @@
167 app.save()
168 app.update_departments()
169 self.add_icon_to_app(app, icon_dir)
170- if self.verbosity > 0:
171- self.stdout.write(
172- u"{0} created.\n".format(app.name).encode('utf-8'))
173+ self.output(u"{0} created.\n".format(app.name).encode('utf-8'), 1)
174 else:
175- if self.verbosity > 0:
176- self.stdout.write(
177- u"Skipping {0} as input failed validation: {1}.\n".format(
178- form.cleaned_data['package_name'],
179- form.errors).encode('utf-8'))
180+ self.output(
181+ u"Skipping {0} as input failed validation: {1}.\n".format(
182+ form.cleaned_data['package_name'],
183+ form.errors).encode('utf-8'), 1)
184
185 def add_icon_to_app(self, app, icon_dir):
186 # If the desktop file specifies an icon, we check for it in the
187@@ -223,27 +217,32 @@
188 break
189
190 if icon_path:
191+ if icon_path.endswith('.xpm') or icon_path.endswith('.svg'):
192+ target_path = icon_path[:-4] + '.png'
193+ if create_png_from_file(target_path, icon_path, 32, 32):
194+ icon_path = target_path
195 with open(icon_path, "r") as icon_file:
196 app.icon = ImageFile(icon_file)
197 app.save()
198 else:
199- if self.verbosity > 0:
200- self.stdout.write(
201- u"No icon found for {0} at {1}.({2}).\n".format(
202+ self.output(u"No icon found for {0} at {1}.({2}).\n".format(
203 app.name, icon_path_generic,
204- '|'.join(supported_extensions)).encode('utf-8'))
205+ '|'.join(supported_extensions)).encode('utf-8'), 1)
206
207 def update_from_cache(self, cache, distroseries_name):
208 distroseries, created = DistroSeries.objects.get_or_create(
209 code_name=distroseries_name)
210- if created and self.verbosity > 0:
211- self.stdout.write(
212- "Created a DistroSeries record with codename '{0}'.".format(
213- distroseries_name))
214+ if created:
215+ self.output("Created a DistroSeries record called '{0}'.\n".format(
216+ distroseries_name), 1)
217
218 # For each package in the apt-cache, we update (or possibly
219 # create) a matching db entry.
220 for package in cache:
221+ if self.verbosity > 1:
222+ self.output("Updating {0}\n".format(package.name))
223+ elif self.verbosity > 0:
224+ self.output(".", flush=True)
225 candidate = package.candidate
226 # USC uses the first line of the summary as the
227 # application name and the rest as the comment..
228@@ -272,4 +271,13 @@
229 name=app_name,
230 comment=app_comment,
231 )
232+ if self.verbosity == 1:
233+ self.output("\n")
234 return distroseries
235+
236+ def output(self, message, level=None, flush=False):
237+ if hasattr(self, 'stdout'):
238+ if level is None or self.verbosity >= level:
239+ self.stdout.write(message)
240+ if flush:
241+ self.stdout.flush()
242
243=== modified file 'src/webcatalog/tests/__init__.py'
244--- src/webcatalog/tests/__init__.py 2011-06-29 11:59:28 +0000
245+++ src/webcatalog/tests/__init__.py 2011-07-01 15:38:25 +0000
246@@ -21,4 +21,5 @@
247 from .test_forms import *
248 from .test_models import *
249 from .test_templatetags import *
250+from .test_utilities import *
251 from .test_views import *
252
253=== modified file 'src/webcatalog/tests/test_commands.py'
254--- src/webcatalog/tests/test_commands.py 2011-06-24 07:54:02 +0000
255+++ src/webcatalog/tests/test_commands.py 2011-07-01 15:38:25 +0000
256@@ -247,12 +247,8 @@
257 self.assertEqual(4, Application.objects.count())
258 scribus = Application.objects.get(package_name='scribus')
259 firefox = Application.objects.get(package_name='firefox')
260- self.assertEqual(
261- os.path.getsize(self.factory.get_test_path('firefox.xpm')),
262- firefox.icon.size)
263- self.assertEqual(
264- os.path.getsize(self.factory.get_test_path('scribus.xpm')),
265- scribus.icon.size)
266+ self.assertTrue(firefox.icon.size > 1)
267+ self.assertTrue(scribus.icon.size > 1)
268
269 def test_get_app_data_uri_for_series(self):
270 uri = Command().get_app_data_uri_for_series('natty')
271
272=== added file 'src/webcatalog/tests/test_data/access.svg'
273--- src/webcatalog/tests/test_data/access.svg 1970-01-01 00:00:00 +0000
274+++ src/webcatalog/tests/test_data/access.svg 2011-07-01 15:38:25 +0000
275@@ -0,0 +1,127 @@
276+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
277+<!-- Created with Inkscape (http://www.inkscape.org/) -->
278+<svg
279+ xmlns:svg="http://www.w3.org/2000/svg"
280+ xmlns="http://www.w3.org/2000/svg"
281+ xmlns:xlink="http://www.w3.org/1999/xlink"
282+ version="1.0"
283+ width="32"
284+ height="32"
285+ id="svg2522">
286+ <defs
287+ id="defs2524">
288+ <linearGradient
289+ id="linearGradient8838">
290+ <stop
291+ id="stop8840"
292+ style="stop-color:#000000;stop-opacity:1"
293+ offset="0" />
294+ <stop
295+ id="stop8842"
296+ style="stop-color:#000000;stop-opacity:0"
297+ offset="1" />
298+ </linearGradient>
299+ <radialGradient
300+ cx="62.625"
301+ cy="4.625"
302+ r="10.625"
303+ fx="62.625"
304+ fy="4.625"
305+ id="radialGradient2502"
306+ xlink:href="#linearGradient8838"
307+ gradientUnits="userSpaceOnUse"
308+ gradientTransform="matrix(1.5058824,0,0,0.37647,-78.305888,26.258824)" />
309+ <linearGradient
310+ id="linearGradient2490-182-124">
311+ <stop
312+ id="stop2788"
313+ style="stop-color:#1f4b6a;stop-opacity:1"
314+ offset="0" />
315+ <stop
316+ id="stop2790"
317+ style="stop-color:#4083c2;stop-opacity:1"
318+ offset="1" />
319+ </linearGradient>
320+ <linearGradient
321+ x1="18.379412"
322+ y1="44.980297"
323+ x2="18.379412"
324+ y2="3.0816143"
325+ id="linearGradient2499"
326+ xlink:href="#linearGradient2490-182-124"
327+ gradientUnits="userSpaceOnUse"
328+ gradientTransform="matrix(0.7126612,0,0,0.7126613,-1.1038706,-1.1038704)" />
329+ <linearGradient
330+ id="linearGradient3242-187-536">
331+ <stop
332+ id="stop2778"
333+ style="stop-color:#8badea;stop-opacity:1"
334+ offset="0" />
335+ <stop
336+ id="stop2780"
337+ style="stop-color:#6396cd;stop-opacity:1"
338+ offset="0.26238" />
339+ <stop
340+ id="stop2782"
341+ style="stop-color:#3b7caf;stop-opacity:1"
342+ offset="0.66093999" />
343+ <stop
344+ id="stop2784"
345+ style="stop-color:#194c70;stop-opacity:1"
346+ offset="1" />
347+ </linearGradient>
348+ <radialGradient
349+ cx="23.895569"
350+ cy="3.9900031"
351+ r="20.397499"
352+ fx="23.895569"
353+ fy="3.9900031"
354+ id="radialGradient2497"
355+ xlink:href="#linearGradient3242-187-536"
356+ gradientUnits="userSpaceOnUse"
357+ gradientTransform="matrix(0,1.7008781,-2.245129,0,24.958071,-40.236051)" />
358+ <linearGradient
359+ id="linearGradient4873">
360+ <stop
361+ id="stop4875"
362+ style="stop-color:#ffffff;stop-opacity:1"
363+ offset="0" />
364+ <stop
365+ id="stop4877"
366+ style="stop-color:#ffffff;stop-opacity:0"
367+ offset="1" />
368+ </linearGradient>
369+ <linearGradient
370+ x1="63.397362"
371+ y1="-12.489107"
372+ x2="63.397362"
373+ y2="5.4675598"
374+ id="linearGradient2494"
375+ xlink:href="#linearGradient4873"
376+ gradientUnits="userSpaceOnUse"
377+ gradientTransform="matrix(1.5436509,0,0,1.5436158,-80.015712,21.419381)" />
378+ </defs>
379+ <g
380+ id="layer1">
381+ <path
382+ d="M 31.999998,28 C 31.999998,30.209139 24.836553,32 15.999997,32 C 7.1634417,32 -3.2e-06,30.209139 -3.2e-06,28 C -3.2e-06,25.790861 7.1634417,24 15.999997,24 C 24.836553,24 31.999998,25.790861 31.999998,28 L 31.999998,28 z"
383+ id="path8836"
384+ style="opacity:0.3;fill:url(#radialGradient2502);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.99999988;stroke-linecap:butt;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
385+ <path
386+ d="M 16.000001,1.5017975 C 8.0003938,1.5017975 1.5017978,8.0003912 1.5017978,15.999999 C 1.5017978,23.999608 8.0003938,30.498205 16.000001,30.498202 C 23.999606,30.498202 30.49821,23.999608 30.498202,15.999999 C 30.498202,8.0003912 23.999606,1.5017975 16.000001,1.5017975 z"
387+ id="path2555"
388+ style="fill:url(#radialGradient2497);fill-opacity:1;stroke:url(#linearGradient2499);stroke-width:1.00365412;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
389+ <path
390+ d="M 29.5,15.999522 C 29.5,23.455607 23.455399,29.5 16.000172,29.5 C 8.5442598,29.5 2.5,23.455539 2.5,15.999522 C 2.5,8.5437822 8.5442598,2.5000011 16.000172,2.5000011 C 23.455399,2.5000011 29.5,8.5437822 29.5,15.999522 L 29.5,15.999522 z"
391+ id="path8655"
392+ style="opacity:0.4;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:url(#linearGradient2494);stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
393+ <path
394+ d="M 18.299799,8.1756486 C 18.299799,9.3766078 17.316035,10.351299 16.103898,10.351299 C 14.891759,10.351299 13.907996,9.3766078 13.907996,8.1756486 C 13.907996,6.9746909 14.891759,5.9999997 16.103898,5.9999997 C 17.316035,5.9999997 18.299799,6.9746909 18.299799,8.1756486 z M 23.780465,10.489071 C 23.678901,10.505375 23.582932,10.545064 23.50068,10.604851 L 15.999771,11.763753 L 8.5392604,10.588868 C 8.3464065,10.450283 8.0752623,10.408784 7.8597702,10.511673 C 7.6442771,10.614562 7.5030077,10.858427 7.5000365,11.090481 C 7.4973158,11.322567 7.646957,11.522717 7.8597702,11.630709 L 13.705808,13.468134 L 13.665824,17.686241 L 11.602993,24.003606 C 11.404317,24.291273 11.464871,24.660718 11.762875,24.852499 C 12.060892,25.044308 12.483506,24.985866 12.682202,24.698167 L 16.011195,17.879188 L 19.277349,24.686382 C 19.453959,24.984755 19.847645,25.088403 20.156699,24.917911 C 20.465753,24.747422 20.573126,24.367363 20.396522,24.068988 L 18.371982,17.724445 L 18.292054,13.753383 L 24.22012,11.608108 C 24.459637,11.445958 24.563691,11.138982 24.459951,10.874963 C 24.356202,10.610915 24.071092,10.457077 23.780445,10.489071 L 23.780465,10.489071 z"
395+ id="path2520"
396+ style="fill:#1f4b6b;fill-opacity:1;stroke:none" />
397+ <path
398+ d="M 18.299799,9.1756488 C 18.299799,10.376608 17.316035,11.351299 16.103898,11.351299 C 14.891759,11.351299 13.907996,10.376608 13.907996,9.1756488 C 13.907996,7.9746911 14.891759,6.9999999 16.103898,6.9999999 C 17.316035,6.9999999 18.299799,7.9746911 18.299799,9.1756488 z M 23.780465,11.489071 C 23.678901,11.505375 23.582932,11.545064 23.50068,11.604851 L 15.999771,12.763753 L 8.5392604,11.588868 C 8.3464065,11.450283 8.0752623,11.408784 7.8597702,11.511673 C 7.6442771,11.614562 7.5030077,11.858427 7.5000365,12.090481 C 7.4973158,12.322567 7.646957,12.522717 7.8597702,12.630709 L 13.705808,14.468134 L 13.665824,18.686241 L 11.602993,25.003606 C 11.404317,25.291273 11.464871,25.660718 11.762875,25.852499 C 12.060892,26.044308 12.483506,25.985866 12.682202,25.698167 L 16.011195,18.879188 L 19.277349,25.686382 C 19.453959,25.984755 19.847645,26.088403 20.156699,25.917911 C 20.465753,25.747422 20.573126,25.367363 20.396522,25.068988 L 18.371982,18.724445 L 18.292054,14.753383 L 24.22012,12.608108 C 24.459637,12.445958 24.563691,12.138982 24.459951,11.874963 C 24.356202,11.610915 24.071092,11.457077 23.780445,11.489071 L 23.780465,11.489071 z"
399+ id="path6668"
400+ style="fill:#ffffff;fill-opacity:1;stroke:none" />
401+ </g>
402+</svg>
403
404=== added file 'src/webcatalog/tests/test_data/empty.xpm'
405=== added file 'src/webcatalog/tests/test_data/junk.svg'
406--- src/webcatalog/tests/test_data/junk.svg 1970-01-01 00:00:00 +0000
407+++ src/webcatalog/tests/test_data/junk.svg 2011-07-01 15:38:25 +0000
408@@ -0,0 +1,4 @@
409+This is not an xpm file. {
410+"",
411+Really, it's not?
412+}
413\ No newline at end of file
414
415=== added file 'src/webcatalog/tests/test_data/junk.xpm'
416--- src/webcatalog/tests/test_data/junk.xpm 1970-01-01 00:00:00 +0000
417+++ src/webcatalog/tests/test_data/junk.xpm 2011-07-01 15:38:25 +0000
418@@ -0,0 +1,4 @@
419+This is not an xpm file. {
420+"",
421+Really, it's not?
422+}
423\ No newline at end of file
424
425=== added file 'src/webcatalog/tests/test_data/mini-gsmc.xpm'
426--- src/webcatalog/tests/test_data/mini-gsmc.xpm 1970-01-01 00:00:00 +0000
427+++ src/webcatalog/tests/test_data/mini-gsmc.xpm 2011-07-01 15:38:25 +0000
428@@ -0,0 +1,23 @@
429+/* XPM */
430+static char * mini-mix_xpm[] = {
431+"16 16 4 1",
432+" c None s None",
433+". c black",
434+"r c red",
435+"y c green",
436+" .... ",
437+" .. r .. ",
438+" . r . ",
439+" . r . ",
440+" . r ... . ",
441+" . .ryyy.. ",
442+". .yryyy..",
443+". .yyyrryy.",
444+".............rrr",
445+". .yyyrryy.",
446+" . .yryyy. ",
447+" . ryyy.. ",
448+" . r .... ",
449+" . r . ",
450+" .. r .. ",
451+" .... "};
452
453=== added file 'src/webcatalog/tests/test_utilities.py'
454--- src/webcatalog/tests/test_utilities.py 1970-01-01 00:00:00 +0000
455+++ src/webcatalog/tests/test_utilities.py 2011-07-01 15:38:25 +0000
456@@ -0,0 +1,35 @@
457+import os
458+
459+from webcatalog.tests.factory import TestCaseWithFactory
460+from webcatalog.utilities import create_png_from_file
461+
462+class CreatePNGFromFileTestCase(TestCaseWithFactory):
463+ def test_file_conversion(self):
464+ filenames = [
465+ self.factory.get_test_path('firefox.xpm'), # Regular xpm
466+ self.factory.get_test_path('mini-gsmc.xpm'), # named colors
467+ self.factory.get_test_path('access.svg'), # SVG file
468+ ]
469+ pngpath = 'test_file_DELETEME.png'
470+ for f in filenames:
471+ try:
472+ self.assertTrue(create_png_from_file(pngpath, f, 32, 32))
473+ self.assertTrue(os.path.exists(pngpath))
474+ finally:
475+ if os.path.exists(pngpath):
476+ os.unlink(pngpath)
477+
478+ def test_invalid_files(self):
479+ filenames = [
480+ self.factory.get_test_path('junk.xpm'),
481+ self.factory.get_test_path('empty.xpm'),
482+ self.factory.get_test_path('junk.svg'),
483+ ]
484+ pngpath = 'test_file_DELETEME.png'
485+ for f in filenames:
486+ try:
487+ self.assertFalse(create_png_from_file(pngpath, f, 32, 32))
488+ self.assertFalse(os.path.exists(pngpath))
489+ finally:
490+ if os.path.exists(pngpath):
491+ os.unlink(pngpath)
492
493=== modified file 'src/webcatalog/utilities.py'
494--- src/webcatalog/utilities.py 2011-06-29 14:52:33 +0000
495+++ src/webcatalog/utilities.py 2011-07-01 15:38:25 +0000
496@@ -184,3 +184,14 @@
497 return True
498
499 return False
500+
501+
502+def create_png_from_file(target_path, source_file, width, height):
503+ import gtk
504+ from glib import GError
505+ try:
506+ pix = gtk.gdk.pixbuf_new_from_file_at_size(source_file, width, height)
507+ pix.save(target_path, "png")
508+ return True
509+ except GError:
510+ return False
511
512=== modified file 'src/webcatalog/views.py'
513--- src/webcatalog/views.py 2011-06-29 14:27:13 +0000
514+++ src/webcatalog/views.py 2011-07-01 15:38:25 +0000
515@@ -68,6 +68,7 @@
516 ors = [Q(package_name__icontains=term) for term in terms]
517 ors += [Q(name__icontains=term) for term in terms]
518 apps = Application.objects.filter(reduce(operator.or_, ors))
519+ apps = apps.order_by('name')
520
521 crumbs = [{'name': 'Get Software', 'url': reverse('wc-index')},
522 {'name': 'Search results', 'url': reverse('wc-search') + '?' +
523@@ -88,6 +89,7 @@
524
525 def index(request):
526 depts = Department.objects.filter(parent=None).order_by('name')
527+ depts = depts.order_by('name')
528 context = RequestContext(request, dict={'depts': depts})
529 return render_to_response('webcatalog/index.html',
530 context_instance=context)
531@@ -96,7 +98,9 @@
532 def department_overview(request, dept_id):
533 dept = get_object_or_404(Department, pk=dept_id)
534 subdepts = Department.objects.filter(parent=dept)
535+ subdepts = subdepts.order_by('name')
536 apps = Application.objects.filter(departments=dept)
537+ apps = apps.order_by('name')
538 paginator = Paginator(apps, settings.PAGE_BATCH_SIZE)
539 page_num = _get_page_num_from_request(request, paginator)
540

Subscribers

People subscribed via source and target branches