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

Proposed by Anthony Lenton
Status: Merged
Approved by: Anthony Lenton
Approved revision: 25
Merged at revision: 24
Proposed branch: lp:~elachuni/ubuntu-webcatalog/breadcrumbs
Merge into: lp:ubuntu-webcatalog
Diff against target: 375 lines (+183/-27)
11 files modified
src/webcatalog/models.py (+20/-0)
src/webcatalog/static/css/webcatalog.css (+55/-0)
src/webcatalog/templates/webcatalog/application_detail.html (+5/-2)
src/webcatalog/templates/webcatalog/application_overview_snippet.html (+11/-0)
src/webcatalog/templates/webcatalog/breadcrumbs_snippet.html (+6/-0)
src/webcatalog/templates/webcatalog/department_overview.html (+3/-11)
src/webcatalog/templates/webcatalog/index.html (+1/-0)
src/webcatalog/templates/webcatalog/search_results.html (+2/-11)
src/webcatalog/tests/test_models.py (+57/-0)
src/webcatalog/urls.py (+2/-3)
src/webcatalog/views.py (+21/-0)
To merge this branch: bzr merge lp:~elachuni/ubuntu-webcatalog/breadcrumbs
Reviewer Review Type Date Requested Status
Matthew Nuzum (community) Approve
Review via email: mp+65682@code.launchpad.net

Commit message

Add a small breadcrumbs display at the top of the page, similar to software-center's

Description of the change

Overview
========
Add a small breadcrumbs display at the top of the page, similar to software-center's

Details
=======
The breadcrumbs shown at the top of the pages are currently different to the ones displayed within USC. They don't display the path you've walked through the application, but a canonical path for each page. For example, if after searching for "Foo" I click on "bygfoot", the breadcrumbs won't display "Get Software > Search results > bygfoot". They'll instead display "Get software > Games > bygfoot", as bygfoot is in the Games department.

We *could* switch to the former behaviour by sticking the breadcrumbs in the session, and keeping track of the pages the user has visited. That would be tricky (what happens if the user starts visiting arbitrary urls? the breadcrumb machine needs to realize what's a "valid path" within the site...) That's not what this branch does :)

To post a comment you must log in.
Revision history for this message
Matthew Nuzum (newz) wrote :

Looks good. You did implement this the right way in my opinion. There is a back button to get people back to the search results, however this lets them go up a level to see related products.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/webcatalog/models.py'
2--- src/webcatalog/models.py 2011-06-17 16:21:59 +0000
3+++ src/webcatalog/models.py 2011-06-23 15:03:25 +0000
4@@ -25,6 +25,7 @@
5 import logging
6 import re
7
8+from django.core.urlresolvers import reverse
9 from django.db import models
10
11 from webcatalog.department_filters import department_filters
12@@ -119,6 +120,16 @@
13 self.departments.add(dept)
14 break
15
16+ def crumbs(self):
17+ depts = self.departments.order_by('-parent')
18+ if depts.count():
19+ crumbs = depts[0].crumbs()
20+ else:
21+ crumbs = [{'name': 'Get Software', 'url': reverse('wc-index')}]
22+ crumbs.append({'name': self.name, 'url': reverse('wc-package-detail',
23+ args=[self.package_name])})
24+ return crumbs
25+
26 class Department(models.Model):
27 parent = models.ForeignKey('self', blank=True, null=True)
28 name = models.CharField(max_length=64)
29@@ -129,3 +140,12 @@
30 @property
31 def normalized_name(self):
32 return re.sub(r'[-&.,;" \']', '', self.name)
33+
34+ def crumbs(self):
35+ if self.parent:
36+ crumbs = self.parent.crumbs()
37+ else:
38+ crumbs = [{'name': 'Get Software', 'url': reverse('wc-index')}]
39+ crumbs.append({'name': self.name, 'url': reverse('wc-department',
40+ args=[self.id])})
41+ return crumbs
42
43=== modified file 'src/webcatalog/static/css/webcatalog.css'
44--- src/webcatalog/static/css/webcatalog.css 2011-06-07 20:53:56 +0000
45+++ src/webcatalog/static/css/webcatalog.css 2011-06-23 15:03:25 +0000
46@@ -145,3 +145,58 @@
47 width:56px;
48 height:48px;
49 }
50+
51+.icon64 {
52+ width: 64px;
53+ height: 64px;
54+}
55+
56+
57+/* Breadcrumbs styles */
58+#breadcrumbs {
59+ height: 25px;
60+ margin: 16px 0;
61+}
62+
63+#breadcrumbs li {
64+ list-style: none;
65+ float: left;
66+ line-height: 25px;
67+ padding: 0 20px 0 7px;
68+ background: transparent url("/assets/images/breadcrumbs_bg.png") no-repeat top right;
69+ border-style: solid;
70+ border-color: #928d8a;
71+ border-width: 1px 0;
72+}
73+
74+#breadcrumbs li a {
75+ color: #444;
76+}
77+
78+#breadcrumbs li.first {
79+ -moz-border-radius: 4px 0 0 4px;
80+ -webkit-border-radius: 4px 0 0 4px;
81+ -khtml-border-radius: 4px 0 0 4px;
82+ border-radius: 4px 0 0 4px;
83+ border-width: 1px 0 1px 1px;
84+}
85+
86+#breadcrumbs li.last {
87+ -moz-border-radius: 0 4px 4px 0;
88+ -webkit-border-radius: 0 4px 4px 0;
89+ -khtml-border-radius: 0 4px 4px 0;
90+ border-radius:0 4px 4px 0 ;
91+ border-width: 1px 1px 1px 0;
92+ background-position: top left;
93+ padding-right: 7px;
94+}
95+
96+#breadcrumbs li.first.last {
97+ -moz-border-radius: 4px;
98+ -webkit-border-radius: 4px;
99+ -khtml-border-radius: 4px;
100+ border-radius: 4px;
101+ border-width: 1px;
102+}
103+
104+/* End breadcrumbs style */
105
106=== added file 'src/webcatalog/static/images/breadcrumbs_bg.png'
107Binary files src/webcatalog/static/images/breadcrumbs_bg.png 1970-01-01 00:00:00 +0000 and src/webcatalog/static/images/breadcrumbs_bg.png 2011-06-23 15:03:25 +0000 differ
108=== modified file 'src/webcatalog/templates/webcatalog/application_detail.html'
109--- src/webcatalog/templates/webcatalog/application_detail.html 2011-06-17 16:21:59 +0000
110+++ src/webcatalog/templates/webcatalog/application_detail.html 2011-06-23 15:03:25 +0000
111@@ -2,14 +2,17 @@
112 {% load i18n %}
113
114 {% block title %}Application {{ application.name }}{% endblock %}
115+{% block header %}Details for {{ application.name }}{% endblock %}
116
117 {% block content %}
118+ {% include "webcatalog/breadcrumbs_snippet.html" %}
119+
120 <div id="sc-mockup">
121 <div class="header">
122 {% if application.icon %}
123- <img src="{{ application.icon.url }}"/>
124+ <img class="icon64" src="{{ application.icon.url }}"/>
125 {% else %}
126- <img src="{{ STATIC_URL }}images/noicon_32.png"/>
127+ <img class="icon64" src="{{ STATIC_URL }}images/noicon_32.png"/>
128 {% endif %}
129
130 <h2>{{ application.name }}</h2>
131
132=== added file 'src/webcatalog/templates/webcatalog/application_overview_snippet.html'
133--- src/webcatalog/templates/webcatalog/application_overview_snippet.html 1970-01-01 00:00:00 +0000
134+++ src/webcatalog/templates/webcatalog/application_overview_snippet.html 2011-06-23 15:03:25 +0000
135@@ -0,0 +1,11 @@
136+<div class="app-overview-row">
137+ {% if app.icon %}
138+ <img src="{{ app.icon.url }}" width="32" height="32" />
139+ {% else %}
140+ <img src="{{ STATIC_URL }}images/noicon_32.png"/>
141+ {% endif %}
142+ <h3>
143+ <a href="{% url wc-package-detail app.package_name %}">{{ app.name }}</a>
144+ </h3>
145+ <p>{{ app.comment }}</p>
146+</div>
147
148=== added file 'src/webcatalog/templates/webcatalog/breadcrumbs_snippet.html'
149--- src/webcatalog/templates/webcatalog/breadcrumbs_snippet.html 1970-01-01 00:00:00 +0000
150+++ src/webcatalog/templates/webcatalog/breadcrumbs_snippet.html 2011-06-23 15:03:25 +0000
151@@ -0,0 +1,6 @@
152+<ul id="breadcrumbs">
153+{% for crumb in breadcrumbs %}
154+ <li class="crumb {% if forloop.first %} first{% endif %}{% if forloop.last %} last{% endif %}">
155+ <a href="{{ crumb.url }}">{{ crumb.name }}</a></li>
156+{% endfor %}
157+</ul>
158\ No newline at end of file
159
160=== modified file 'src/webcatalog/templates/webcatalog/department_overview.html'
161--- src/webcatalog/templates/webcatalog/department_overview.html 2011-04-19 19:25:48 +0000
162+++ src/webcatalog/templates/webcatalog/department_overview.html 2011-06-23 15:03:25 +0000
163@@ -5,6 +5,8 @@
164 {% block header %}{% trans "Ubuntu web catalog" %} &mdash; {{ dept.name }}{% endblock %}
165
166 {% block content %}
167+ {% include "webcatalog/breadcrumbs_snippet.html" %}
168+
169 {% if subdepts %}
170 <h3>{% trans "Subsections" %}:</h3>
171
172@@ -18,17 +20,7 @@
173 {% if apps %}
174 <p>{% blocktrans with napps=apps|length %}Showing {{napps}} applications.{% endblocktrans %}</p>
175 {% for app in apps %}
176-<div class="app-overview-row">
177- {% if app.icon %}
178- <img src="{{ app.icon.url }}" width="32" height="32" />
179- {% else %}
180- <img src="{{ STATIC_URL }}images/noicon_32.png"/>
181- {% endif %}
182- <h3>
183- <a href="{% url wc-package-detail app.package_name %}">{{ app.name }}</a>
184- </h3>
185- <p>{{ app.comment }}</p>
186-</div>
187+ {% include "webcatalog/application_overview_snippet.html" %}
188 {% endfor %}
189 {% else %}
190 <p>{% trans "No applications found." %}</p>
191
192=== modified file 'src/webcatalog/templates/webcatalog/index.html'
193--- src/webcatalog/templates/webcatalog/index.html 2011-04-19 18:46:21 +0000
194+++ src/webcatalog/templates/webcatalog/index.html 2011-06-23 15:03:25 +0000
195@@ -6,6 +6,7 @@
196 {% block search %}{% endblock %}
197
198 {% block content %}
199+
200 <h3>{% trans "Browse application departments" %}:</h3>
201
202 {% for dept in depts %}
203
204=== modified file 'src/webcatalog/templates/webcatalog/search_results.html'
205--- src/webcatalog/templates/webcatalog/search_results.html 2011-04-13 15:00:07 +0000
206+++ src/webcatalog/templates/webcatalog/search_results.html 2011-06-23 15:03:25 +0000
207@@ -5,23 +5,14 @@
208
209 {% block header %}{% trans "Search results" %}{% endblock %}
210 {% block content %}
211+ {% include "webcatalog/breadcrumbs_snippet.html" %}
212
213 {% if query %}
214 <h3>{% blocktrans %}Searched for "{{query}}"{% endblocktrans %}</h3>
215 {% if apps %}
216 <p>{% blocktrans with napps=apps|length %}Found {{napps}} applications.{% endblocktrans %}</p>
217 {% for app in apps %}
218-<div class="app-overview-row">
219- {% if app.icon %}
220- <img src="{{ app.icon.url }}"/>
221- {% else %}
222- <img src="{{ STATIC_URL }}images/noicon_32.png"/>
223- {% endif %}
224- <h3>
225- <a href="{% url wc-package-detail app.package_name %}">{{ app.name }}</a>
226- </h3>
227- <p>{{ app.comment }}</p>
228-</div>
229+ {% include "webcatalog/application_overview_snippet.html" %}
230 {% endfor %}
231 {% else %}
232 <p>{% trans "No applications found." %}</p>
233
234=== modified file 'src/webcatalog/tests/test_models.py'
235--- src/webcatalog/tests/test_models.py 2011-04-19 19:25:48 +0000
236+++ src/webcatalog/tests/test_models.py 2011-06-23 15:03:25 +0000
237@@ -22,6 +22,7 @@
238 with_statement,
239 )
240
241+from django.core.urlresolvers import reverse
242
243 from webcatalog.tests.factory import TestCaseWithFactory
244 from webcatalog.models import Application
245@@ -59,6 +60,43 @@
246 self.assertEqual(1, app.departments.count())
247 self.assertEqual('Games', app.departments.get().name)
248
249+ def test_crumbs_no_department(self):
250+ app = self.factory.make_application()
251+ self.assertEquals([], list(app.departments.all()))
252+ expected = [{'name': 'Get Software', 'url': reverse('wc-index')},
253+ {'name': app.name, 'url': reverse('wc-package-detail',
254+ args=[app.package_name])}]
255+
256+ self.assertEquals(expected, app.crumbs())
257+
258+ def test_crumbs_department_without_parent(self):
259+ app = self.factory.make_application()
260+ app.categories = 'Game;'
261+ app.update_departments()
262+ dept = app.departments.get()
263+ expected = [{'name': 'Get Software', 'url': reverse('wc-index')},
264+ {'name': dept.name, 'url': reverse('wc-department',
265+ args=[dept.id])},
266+ {'name': app.name, 'url': reverse('wc-package-detail',
267+ args=[app.package_name])}]
268+
269+ self.assertEquals(expected, app.crumbs())
270+
271+ def test_crumbs_department_with_parent(self):
272+ app = self.factory.make_application()
273+ app.categories = 'RolePlaying;'
274+ app.update_departments()
275+ dept = app.departments.get()
276+ expected = [{'name': 'Get Software', 'url': reverse('wc-index')},
277+ {'name': dept.parent.name, 'url': reverse('wc-department',
278+ args=[dept.parent.id])},
279+ {'name': dept.name, 'url': reverse('wc-department',
280+ args=[dept.id])},
281+ {'name': app.name, 'url': reverse('wc-package-detail',
282+ args=[app.package_name])}]
283+
284+ self.assertEquals(expected, app.crumbs())
285+
286
287 def DepartmentTestCse(TestCaseWithFactory):
288 def test_normalized_name(self):
289@@ -72,3 +110,22 @@
290 for case, expected in cases.items():
291 dept = self.factory.make_department(case)
292 self.assertEquals(dept.normalized_name, expected)
293+
294+ def test_crumbs_no_parent(self):
295+ dept = self.factory.make_department('Foo')
296+ expected = [{'name': 'Get Software', 'url': reverse('wc-index')},
297+ {'name': dept.name, 'url': reverse('wc-department',
298+ args=[dept.id])}]
299+
300+ self.assertEquals(expected, dept.crumbs())
301+
302+ def test_crumbs_with_parent(self):
303+ parent = self.factory.make_department('Foo')
304+ dept = self.factory.make_department('Bar', parent=parent)
305+ expected = [{'name': 'Get Software', 'url': reverse('wc-index')},
306+ {'name': dept.parent.name, 'url': reverse('wc-department',
307+ args=[dept.parent.id])},
308+ {'name': dept.name, 'url': reverse('wc-department',
309+ args=[dept.id])}]
310+
311+ self.assertEquals(expected, dept.crumbs())
312
313=== modified file 'src/webcatalog/urls.py'
314--- src/webcatalog/urls.py 2011-06-17 16:21:59 +0000
315+++ src/webcatalog/urls.py 2011-06-23 15:03:25 +0000
316@@ -36,8 +36,7 @@
317 url(r'^$', 'index', name='wc-index'),
318 url(r'^department/(?P<dept_id>\d+)/$', 'department_overview',
319 name='wc-department'),
320- url(r'^applications/(?P<slug>[-.+\w]+)/$', DetailView.as_view(
321- model=Application, slug_field='package_name',
322- context_object_name='application'), name="wc-package-detail"),
323+ url(r'^applications/(?P<package_name>[-.+\w]+)/$', 'application_detail',
324+ name="wc-package-detail"),
325 url(r'^search/$', 'search', name="wc-search"),
326 )
327
328=== modified file 'src/webcatalog/views.py'
329--- src/webcatalog/views.py 2011-06-17 16:21:59 +0000
330+++ src/webcatalog/views.py 2011-06-23 15:03:25 +0000
331@@ -23,6 +23,9 @@
332 )
333
334 import operator
335+from urllib import urlencode
336+
337+from django.core.urlresolvers import reverse
338 from django.db.models import Q
339 from django.template import RequestContext
340 from django.shortcuts import (
341@@ -52,9 +55,15 @@
342 ors += [Q(name__icontains=term) for term in terms]
343 apps = Application.objects.filter(reduce(operator.or_, ors))
344
345+ crumbs = [{'name': 'Get Software', 'url': reverse('wc-index')},
346+ {'name': 'Search results', 'url': reverse('wc-search') + '?' +
347+ urlencode({'q': query})},
348+ ]
349+
350 context = RequestContext(request, dict={
351 'query': query,
352 'apps': apps,
353+ 'breadcrumbs': crumbs,
354 })
355 return render_to_response('webcatalog/search_results.html',
356 context_instance=context)
357@@ -72,6 +81,18 @@
358 context = RequestContext(request, dict={'dept': dept,
359 'subdepts': subdepts,
360 'apps': apps,
361+ 'breadcrumbs': dept.crumbs(),
362 })
363 return render_to_response('webcatalog/department_overview.html',
364 context_instance=context)
365+
366+
367+def application_detail(request, package_name):
368+ app = get_object_or_404(Application, package_name=package_name)
369+
370+ context = RequestContext(request, {
371+ 'application': app,
372+ 'breadcrumbs': app.crumbs(),
373+ })
374+ return render_to_response('webcatalog/application_detail.html',
375+ context_instance=context)

Subscribers

People subscribed via source and target branches