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

Proposed by Anthony Lenton
Status: Merged
Merged at revision: 7
Proposed branch: lp:~elachuni/ubuntu-webcatalog/search
Merge into: lp:ubuntu-webcatalog
Diff against target: 199 lines (+144/-1)
5 files modified
src/webcatalog/static/css/webcatalog.css (+13/-0)
src/webcatalog/templates/webcatalog/search_results.html (+32/-0)
src/webcatalog/tests/test_views.py (+70/-0)
src/webcatalog/urls.py (+2/-1)
src/webcatalog/views.py (+27/-0)
To merge this branch: bzr merge lp:~elachuni/ubuntu-webcatalog/search
Reviewer Review Type Date Requested Status
Māris Fogels (community) Approve
Review via email: mp+57369@code.launchpad.net

Description of the change

Overview
========
Adds basic search functionality

Details
=======
Currently searches package_name and name, case-insentively.
The app-overview-row class should be useful when we add a couple of general browsing views, coming up next.

To post a comment you must log in.
Revision history for this message
Māris Fogels (mars) wrote :

Hi Anthony,

This branch looks good, r=mars. I can only think of two things:

 * Consider a helper method in the test that checks if a given app (or set of apps) is in the set of applications returned by a page: assertAppInResults('appname') in place of self.assertEquals(expected[q], set(response.context['apps'])). It might increase the test's readability.

 * Does the search input need to be protected in order to protect application performance? Can the search time out, and do you present a nice user page or a 500 error?

Maris

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/webcatalog/static/css/webcatalog.css'
2--- src/webcatalog/static/css/webcatalog.css 2011-04-07 16:02:34 +0000
3+++ src/webcatalog/static/css/webcatalog.css 2011-04-12 17:33:22 +0000
4@@ -67,5 +67,18 @@
5 #sc-mockup .license td {
6 border: 0;
7 }
8+
9 /* End software centre mockup */
10
11+.app-overview-row {
12+ height: 36px;
13+ padding: 2px;
14+}
15+.app-overview-row img {
16+ float: left;
17+ margin-right: 10px;
18+}
19+.app-overview-row h3 {
20+ padding: 0;
21+ margin: 0;
22+}
23\ No newline at end of file
24
25=== added file 'src/webcatalog/templates/webcatalog/search_results.html'
26--- src/webcatalog/templates/webcatalog/search_results.html 1970-01-01 00:00:00 +0000
27+++ src/webcatalog/templates/webcatalog/search_results.html 2011-04-12 17:33:22 +0000
28@@ -0,0 +1,32 @@
29+{% extends "webcatalog/base.html" %}
30+{% load i18n %}
31+
32+{% block title %}Search results{% endblock %}
33+
34+{% block header %}{% trans "Search results" %}{% endblock %}
35+{% block content %}
36+
37+{% if query %}
38+<h3>{% blocktrans %}Searched for "{{query}}"{% endblocktrans %}</h3>
39+{% if apps %}
40+<p>{% blocktrans with napps=apps|length %}Found {{napps}} applications.{% endblocktrans %}</p>
41+{% for app in apps %}
42+<div class="app-overview-row">
43+ {% if app.icon %}
44+ <img src="{{ app.icon.url }}"/>
45+ {% else %}
46+ <img src="{{ STATIC_URL }}images/noicon_32.png"/>
47+ {% endif %}
48+ <h3>
49+ <a href="{% url wc-package-detail app.package_name %}">{{ app.name }}</a>
50+ </h3>
51+ <p>{{ app.comment }}</p>
52+</div>
53+{% endfor %}
54+{% else %}
55+<p>{% trans "No applications found." %}</p>
56+{% endif %}
57+{% else %}
58+<h3>{% trans "You didn't search for anything!" %}</h3>
59+{% endif %}
60+{% endblock %}
61
62=== modified file 'src/webcatalog/tests/test_views.py'
63--- src/webcatalog/tests/test_views.py 2011-04-08 12:25:25 +0000
64+++ src/webcatalog/tests/test_views.py 2011-04-12 17:33:22 +0000
65@@ -30,6 +30,7 @@
66 __metaclass__ = type
67 __all__ = [
68 'PackageDetailTestCase',
69+ 'SearchTestCase',
70 ]
71
72
73@@ -65,3 +66,72 @@
74 self.assertContains(
75 response, '<img src="http://screenshots.ubuntu.com/'
76 'thumbnail-with-version/pkgfoo/ignored"')
77+
78+class SearchTestCase(TestCaseWithFactory):
79+ def test_no_search_retrieves_no_apps(self):
80+ # Ensure there's some data in the DB
81+ self.factory.make_application()
82+ url = reverse('wc-search')
83+ for data in [{'q': ''}, {}]:
84+
85+ response = self.client.get(url, data=data)
86+
87+ self.assertEquals(response.context['apps'], [])
88+ self.assertContains(response, "You didn't search for anything")
89+
90+ def test_search_searches_package_name(self):
91+ app1 = self.factory.make_application(package_name='foo')
92+ app2 = self.factory.make_application(package_name='bar')
93+ app3 = self.factory.make_application(package_name='foobar')
94+ url = reverse('wc-search')
95+ expected = {'foo': set([app1, app3]), 'bar': set([app2, app3])}
96+ for q in expected:
97+
98+ response = self.client.get(url, data={'q': q})
99+
100+ self.assertEquals(expected[q], set(response.context['apps']))
101+
102+ def test_search_searches_name(self):
103+ app1 = self.factory.make_application(name='foo')
104+ app2 = self.factory.make_application(name='bar')
105+ app3 = self.factory.make_application(name='foobar')
106+ url = reverse('wc-search')
107+ expected = {'foo': set([app1, app3]), 'bar': set([app2, app3])}
108+ for q in expected:
109+
110+ response = self.client.get(url, data={'q': q})
111+
112+ self.assertEquals(expected[q], set(response.context['apps']))
113+
114+ def test_search_no_apps_found_says_so(self):
115+ url = reverse('wc-search')
116+
117+ response = self.client.get(url, data={'q': 'sarasa'})
118+
119+ self.assertContains(response, "No applications found")
120+
121+ def test_response_shows_query_if_no_apps_found(self):
122+ query = 'someimprobablequery'
123+ url = reverse('wc-search')
124+
125+ response = self.client.get(url, data={'q': query})
126+
127+ self.assertContains(response, query)
128+
129+ def test_response_shows_query_if_some_apps_found(self):
130+ query = 'someimprobablequery'
131+ self.factory.make_application(package_name=query)
132+ url = reverse('wc-search')
133+
134+ response = self.client.get(url, data={'q': query})
135+
136+ self.assertContains(response, query)
137+
138+ def test_response_shows_number_of_apps_found(self):
139+ app1 = self.factory.make_application(package_name='foo')
140+ app2 = self.factory.make_application(package_name='foobar')
141+ url = reverse('wc-search')
142+
143+ response = self.client.get(url, data={'q': 'foo'})
144+
145+ self.assertContains(response, "Found 2 applications")
146
147=== modified file 'src/webcatalog/urls.py'
148--- src/webcatalog/urls.py 2011-04-07 11:20:48 +0000
149+++ src/webcatalog/urls.py 2011-04-12 17:33:22 +0000
150@@ -33,7 +33,8 @@
151
152
153 urlpatterns = patterns('webcatalog.views',
154- url(r'^applications/(?P<slug>[-\w]+)/$', DetailView.as_view(
155+ url(r'^applications/(?P<slug>[-.\w]+)/$', DetailView.as_view(
156 model=Application, slug_field='package_name',
157 context_object_name='application'), name="wc-package-detail"),
158+ url(r'^search$', 'search', name="wc-search"),
159 )
160
161=== modified file 'src/webcatalog/views.py'
162--- src/webcatalog/views.py 2011-04-07 10:52:50 +0000
163+++ src/webcatalog/views.py 2011-04-12 17:33:22 +0000
164@@ -21,8 +21,35 @@
165 absolute_import,
166 with_statement,
167 )
168+
169+import operator
170+from django.db.models import Q
171 from django.http import HttpResponse
172+from django.template import RequestContext
173+from django.shortcuts import render_to_response
174+
175+from webcatalog.models import Application
176
177 __metaclass__ = type
178 __all__ = [
179+ 'search',
180 ]
181+
182+
183+def search(request):
184+ apps = []
185+ query = ''
186+ if 'q' in request.GET:
187+ query = request.GET.get('q', '')
188+ terms = query.split()
189+ if terms:
190+ ors = [Q(package_name__icontains=term) for term in terms]
191+ ors += [Q(name__icontains=term) for term in terms]
192+ apps = Application.objects.filter(reduce(operator.or_, ors))
193+
194+ context = RequestContext(request, dict={
195+ 'query': query,
196+ 'apps': apps,
197+ })
198+ return render_to_response('webcatalog/search_results.html',
199+ context_instance=context)

Subscribers

People subscribed via source and target branches