Merge lp:~mhall119/summit/meeting-search into lp:summit

Proposed by Michael Hall on 2012-10-10
Status: Merged
Approved by: Chris Johnston on 2012-10-10
Approved revision: 461
Merged at revision: 457
Proposed branch: lp:~mhall119/summit/meeting-search
Merge into: lp:summit
Diff against target: 402 lines (+303/-5)
8 files modified
summit/common/templates/base.html (+18/-3)
summit/media/css/site.css (+28/-0)
summit/schedule/templates/schedule/daily.html (+10/-1)
summit/schedule/templates/schedule/search.html (+143/-0)
summit/schedule/templates/schedule/summit.html (+4/-0)
summit/schedule/tests.py (+83/-0)
summit/schedule/views.py (+16/-1)
summit/urls.py (+1/-0)
To merge this branch: bzr merge lp:~mhall119/summit/meeting-search
Reviewer Review Type Date Requested Status
Chris Johnston 2012-10-10 Approve on 2012-10-10
Review via email: mp+128986@code.launchpad.net

Commit Message

Adds meeting search functionality

Description of the Change

Adds meeting search functionality, search box in subnav, and adjustments to make it look alright in mobile view.

To post a comment you must log in.
Chris Johnston (cjohnston) wrote :

Seems as thought the search image is missing.

review: Needs Fixing
Chris Johnston (cjohnston) wrote :

The search page template is broken in some way. The area between the footer and the main content is wrong.

review: Needs Fixing
Chris Johnston (cjohnston) wrote :

LGTM

review: Approve
Tarmac WebDev (tarmac-webdev) wrote :

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

Chris Johnston (cjohnston) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'summit/common/templates/base.html'
2--- summit/common/templates/base.html 2012-05-03 01:57:41 +0000
3+++ summit/common/templates/base.html 2012-10-10 18:35:23 +0000
4@@ -47,12 +47,27 @@
5 </ul>
6 {% endblock %}
7
8-{% block search %}{% endblock %}
9+{% block sub_nav %}
10+<div id="sub-nav-container" >
11+ <nav role="navigation" class="nav-secondary">
12+ <ul class="clearfix">
13+ {% block sub_nav_links %}
14+ {% endblock %}
15+ </ul>
16+ </nav>
17+ {% if summit %}
18+ <section id="searchbox-container">
19+
20+ <form id="site_search_form" action="{% url summit.schedule.views.search summit.name %}" method="get">
21+ <input id="id_q" type="text" name="q" maxlength="100" value="{{ query }}" />
22+ <input id="id_button" type="image" src="/media/img/search.png" title="Search" alt="Search" />
23+ </form>
24
25-{% block sub_nav_links %}
26+ </section>
27+ {% endif %}
28+</div>
29 {% endblock %}
30
31-
32 {% block messages %}
33 {% if messages %}
34 {% for message in messages %}
35
36=== modified file 'summit/media/css/site.css'
37--- summit/media/css/site.css 2012-10-09 21:00:53 +0000
38+++ summit/media/css/site.css 2012-10-10 18:35:23 +0000
39@@ -176,3 +176,31 @@
40 display: inline-block;
41 margin-right: 2px;
42 }
43+
44+/* Subnav search form */
45+#sub-nav-container {
46+ height: 40px;
47+ display: block;
48+ margin-bottom: 20px;
49+}
50+
51+#searchbox-container {
52+ position: absolute;
53+ top: 10px;
54+ right: 30px;
55+}
56+#site_search_form {
57+ display: inline;
58+}
59+
60+#site_search_form #id_q {
61+ width: 200px;
62+ padding: 0px;
63+ margin: 0px;
64+ display: inline;
65+}
66+
67+#site_search_form #id_button {
68+ width: 16px;
69+ display: inline;
70+}
71
72=== added file 'summit/media/img/search.png'
73Binary files summit/media/img/search.png 1970-01-01 00:00:00 +0000 and summit/media/img/search.png 2012-10-10 18:35:23 +0000 differ
74=== modified file 'summit/schedule/templates/schedule/daily.html'
75--- summit/schedule/templates/schedule/daily.html 2012-10-09 21:00:53 +0000
76+++ summit/schedule/templates/schedule/daily.html 2012-10-10 18:35:23 +0000
77@@ -157,7 +157,7 @@
78 }
79 div.schedule-head .schedule-date {
80 font-weight: normal;
81- font-size: 36px;
82+ font-size: 24px;
83 margin: 0;
84 display: block;
85 text-align: center;
86@@ -169,6 +169,15 @@
87 width: 25%;
88 text-align: center;
89 }
90+
91+ div.schedule-head .qrcode {
92+ display: none;
93+ }
94+
95+ #sub-nav-container {
96+ margin-bottom: 0px;
97+ }
98+
99 }
100 </style>
101
102
103=== added file 'summit/schedule/templates/schedule/search.html'
104--- summit/schedule/templates/schedule/search.html 1970-01-01 00:00:00 +0000
105+++ summit/schedule/templates/schedule/search.html 2012-10-10 18:35:23 +0000
106@@ -0,0 +1,143 @@
107+{% extends "base.html" %}
108+{% load datetime %}
109+
110+{% block page_name %}
111+Meeting Search - {{ summit.title }}
112+{% endblock %}
113+
114+{% block sub_nav_links %}
115+ <li><a class="sub-nav-item" href="/{{ summit.name }}/" title="Summit">Back to Summit</a></li>
116+{% endblock %}
117+
118+{% block content %}
119+<div class="row">
120+<article class="span-8">
121+{% if meetings %}
122+<h2>Results for: {{query}}</h2>
123+{% else %}
124+<h3>No Meetings found matching query</h3>
125+{% endif %}
126+
127+<ul>
128+{% for meeting in meetings %}
129+ <li><a href="{{meeting.get_meeting_page_url}}">{{meeting.title}}</a>{% if meeting.agenda_set.count %} -
130+ {% for agenda in meeting.agenda_set.all %}{{agenda.slot}}{% endfor %}
131+ {% endif %}</li>
132+{% endfor %}
133+</ul>
134+</article>
135+</div>
136+{% block closure %}
137+<style>
138+/* Mobile-friendly styles */
139+@media screen and (max-width: 960px) {
140+ .span-4, footer, #main-nav, footer .logo-ubuntu, .nav-secondary, .schedule-qrcode { display: none; }
141+ #page-header { text-align: center; }
142+ #page-header a { height: 24px; line-height: 1; float: none; display: inline; }
143+ .span-8, .span-9, .span-3, .span-12 { width: 100%; }
144+ table.schedule { width: 100%; margin: 0; padding: 0; border-width: 0; }
145+ table.column { width: 100%; margin: 0; padding: 0; border-width: 0; }
146+ table.column td { display: block; width: 100%; margin: 0; padding: 0; border-width: 0; }
147+ table.column td ul {
148+ width: 100%;
149+ margin: 0;
150+ padding: 0;
151+ }
152+ table.column td ul li {
153+ background: #efefef;
154+ border-bottom: 1px solid black;
155+ list-style: none;
156+ text-align: center;
157+ font-size: 24px;
158+ line-height: 24px;
159+ min-height: 24px;
160+ width: 100%;
161+ display: block;
162+ padding: 4px 0;
163+ margin: 0;
164+ }
165+ #fb-root * { width: auto; }
166+ .schedule-head > a { display: none; } /* qr code */
167+ div.schedule-head .schedule-date a:nth-child(1) {
168+ float: left;
169+ }
170+ div.schedule-head .schedule-date {
171+ font-weight: normal;
172+ font-size: 24px;
173+ margin: 0;
174+ display: block;
175+ text-align: center;
176+ }
177+ div.schedule-head .schedule-date a {
178+ display: block;
179+ float: right;
180+ background: #eee;
181+ width: 25%;
182+ text-align: center;
183+ }
184+
185+ #sub-nav-container {
186+ margin-bottom: 0px;
187+ }
188+}
189+</style>
190+
191+<script language="JavaScript">
192+
193+function show_agenda_details(agenda_id, index_id) {
194+ var elem_id = 'agenda-'+agenda_id+'-'+index_id+'-details'
195+ var details = document.getElementById(elem_id)
196+ details.style.display='block';
197+}
198+
199+function hide_agenda_details(agenda_id, index_id) {
200+ var elem_id = 'agenda-'+agenda_id+'-'+index_id+'-details'
201+ var details = document.getElementById(elem_id)
202+ details.style.display='none';
203+}
204+
205+</script>
206+<script>
207+/* Mobile handler for clicking a link */
208+/* put at the bottom of the page so that the relevant elements exist */
209+document.querySelector("article.main-content").addEventListener("click", function(e) {
210+ /* Only handle clicking on the main links in the table */
211+ if (e.target.nodeName.toLowerCase() == "a" && e.target.className == "main-agenda-item-name") {
212+ /* only override links if we're in mobile mode */
213+ if (document.documentElement.clientWidth > 481) return;
214+ e.stopPropagation();
215+ e.preventDefault();
216+ /* toggle display of the details div */
217+ var details_div = e.target.parentNode.getElementsByTagName("div")[0];
218+ details_div.style.display = details_div.style.display == "block" ? "none" : "block";
219+ /* and add a link to the specific page to the top of the details div, if
220+ we haven't already */
221+ var links = details_div.getElementsByClassName("real-link");
222+ if (links.length == 0) {
223+ var a = document.createElement("a");
224+ a.href = e.target.href;
225+ a.appendChild(document.createTextNode("View details of this session"));
226+ a.className = "real-link";
227+ details_div.insertBefore(a, details_div.firstChild);
228+ }
229+ }
230+});
231+
232+/* override the mouseover/mouseout functions in mobile mode */
233+var old_show_agenda_details = show_agenda_details;
234+var old_hide_agenda_details = hide_agenda_details;
235+show_agenda_details = function(agenda_id, slot_id) {
236+ if (document.documentElement.clientWidth > 481) {
237+ return old_show_agenda_details(agenda_id, slot_id);
238+ }
239+};
240+hide_agenda_details = function(agenda_id, slot_id) {
241+ if (document.documentElement.clientWidth > 481) {
242+ return old_hide_agenda_details(agenda_id, slot_id);
243+ }
244+};
245+
246+</script>
247+{% endblock %}
248+
249+{% endblock %}
250
251=== modified file 'summit/schedule/templates/schedule/summit.html'
252--- summit/schedule/templates/schedule/summit.html 2012-05-17 12:56:36 +0000
253+++ summit/schedule/templates/schedule/summit.html 2012-10-10 18:35:23 +0000
254@@ -120,6 +120,10 @@
255 margin: 0;
256 }
257 #fb-root * { width: auto; }
258+
259+ #sub-nav-container {
260+ margin-bottom: 0px;
261+ }
262 }
263 </style>
264 {% endblock %}
265
266=== modified file 'summit/schedule/tests.py'
267--- summit/schedule/tests.py 2012-10-09 18:55:55 +0000
268+++ summit/schedule/tests.py 2012-10-10 18:35:23 +0000
269@@ -1967,3 +1967,86 @@
270 self.login()
271 response = self.client.get(reverse('summit.schedule.views.delete_meeting', args=(self.summit, self.meeting.id, self.meeting.name)))
272 self.assertRedirects(response, reverse('summit.schedule.views.delete_confirmed', args=(self.summit.name,)), status_code=302, target_status_code=200)
273+
274+
275+class MeetingSearchTestCase(djangotest.TestCase):
276+ """
277+ This will test different options for deleting a meeting from
278+ the front end UI.
279+ """
280+
281+ form_html = '<form id="site_search_form"'
282+
283+ def setUp(self):
284+ now = datetime.datetime.utcnow()
285+ one_hour = datetime.timedelta(0, 3600)
286+ self.summit = factory.make_one(Summit, name='uds-test', date_start=now, date_end=now+one_hour)
287+ self.slot = factory.make_one(
288+ Slot,
289+ summit=self.summit,
290+ start_utc=now,
291+ end_utc=now+one_hour)
292+ self.room = factory.make_one(Room)
293+
294+ def test_searchform_exists_on_summit_view(self):
295+ ''' Search form should appear on the summit view page '''
296+ response = self.client.get(reverse('summit.schedule.views.summit', args=[self.summit.name]))
297+ self.assertContains(response, self.form_html, 1)
298+
299+ def test_searchform_exists_on_daily_view(self):
300+ ''' Search form should appear on the list view page '''
301+ response = self.client.get(reverse('summit.schedule.views.daily_schedule', args=[self.summit.name, '2012-10-10']))
302+ self.assertContains(response, self.form_html, 1)
303+
304+ def test_searchform_exists_on_today_view(self):
305+ ''' Search form should appear on the today view page '''
306+ response = self.client.get(reverse('summit.schedule.views.today_view', args=[self.summit.name]))
307+ self.assertContains(response, self.form_html, 1)
308+
309+ def test_searchform_exists_on_meeting_view(self):
310+ ''' Search form should appear on the meeting view page '''
311+ meeting = factory.make_one(Meeting, summit=self.summit, requires_dial_in=False, private=False)
312+
313+ response = self.client.get(reverse('summit.schedule.views.meeting', args=[self.summit.name, meeting.id, meeting.name]))
314+ self.assertContains(response, self.form_html, 1)
315+
316+ def test_searchform_not_exists_on_index(self):
317+ ''' Search form should NOT appear on the index page because there isn't a summit selected '''
318+ response = self.client.get(reverse('summit.common.views.index'))
319+ self.assertContains(response, self.form_html, 0)
320+
321+ def test_meeting_in_search_results(self):
322+ ''' Meeting should appear in search results page if the query matches the name or title '''
323+ meeting = factory.make_one(Meeting, summit=self.summit, name='test-name', title='Test Title', requires_dial_in=False, private=False)
324+
325+ # Attempt to match 'name' against 'test-name' in Meeting.name
326+ response = self.client.get(reverse('summit.schedule.views.search', args=[self.summit.name]), {'q': 'name'})
327+ self.assertContains(response, self.form_html, 1)
328+ self.assertContains(response, '<a href="%s">Test Title</a>' % meeting.get_meeting_page_url(), 1)
329+
330+ # Attempt to match 'title' against 'Test Title' in Meeting.title
331+ response = self.client.get(reverse('summit.schedule.views.search', args=[self.summit.name]), {'q': 'title'})
332+ self.assertContains(response, self.form_html, 1)
333+ self.assertContains(response, '<a href="%s">Test Title</a>' % meeting.get_meeting_page_url(), 1)
334+
335+ def test_meeting_not_in_search_results(self):
336+ ''' Meetings for one summit should NOT appear when searching a different summit '''
337+ now = datetime.datetime.utcnow()
338+ one_hour = datetime.timedelta(0, 3600)
339+
340+ # Create a second summit
341+ other_summit = factory.make_one(Summit, name='uds-other', date_start=now, date_end=now+one_hour)
342+
343+ meeting = factory.make_one(Meeting, summit=other_summit, name='test-name', title='Test Title', requires_dial_in=False, private=False)
344+
345+ # Attempt to match 'test' against 'test-name' or 'Test Title', should return 0 results
346+ # because the the meeting is in other_summit, not self.summit
347+ response = self.client.get(reverse('summit.schedule.views.search', args=[self.summit.name]), {'q': 'test'})
348+ self.assertContains(response, self.form_html, 1)
349+ self.assertContains(response, '<a href="%s">Test Title</a>' % meeting.get_meeting_page_url(), 0)
350+
351+ # Same test, but searching the correct other_summit should return 1 result
352+ response = self.client.get(reverse('summit.schedule.views.search', args=[other_summit.name]), {'q': 'test'})
353+ self.assertContains(response, self.form_html, 1)
354+ self.assertContains(response, '<a href="%s">Test Title</a>' % meeting.get_meeting_page_url(), 1)
355+
356
357=== modified file 'summit/schedule/views.py'
358--- summit/schedule/views.py 2012-10-09 18:56:16 +0000
359+++ summit/schedule/views.py 2012-10-10 18:35:23 +0000
360@@ -59,7 +59,7 @@
361 'csv',
362 'today_view',
363 )
364-
365+
366 @summit_required
367 def summit(request, summit, attendee):
368 edit = False
369@@ -81,6 +81,21 @@
370 return render_to_response("schedule/summit.html", context,
371 context_instance=RequestContext(request))
372
373+@summit_only_required
374+def search(request, summit):
375+ query = request.GET.get('q', None)
376+ if query:
377+ meetings = summit.meeting_set.filter(Q(name__icontains=query) | Q(title__icontains=query))
378+ else:
379+ meetings = []
380+
381+ context = {
382+ 'summit': summit,
383+ 'query': query,
384+ 'meetings': meetings,
385+ }
386+ return render_to_response("schedule/search.html", context,
387+ context_instance=RequestContext(request))
388 @summit_required
389 def daily_schedule(request, summit, attendee, date):
390 viewdate = summit.as_localtime(datetime.datetime.strptime(date, "%Y-%m-%d"))
391
392=== modified file 'summit/urls.py'
393--- summit/urls.py 2012-10-06 16:02:36 +0000
394+++ summit/urls.py 2012-10-10 18:35:23 +0000
395@@ -67,6 +67,7 @@
396 url(r'^logout$', 'logout_view', name='logout'),
397 (r'^(?P<summit_name>[\w-]+)/$', 'summit'),
398 (r'^(?P<summit_name>[\w-]+)/mobile/$', 'mobile'),
399+ (r'^(?P<summit_name>[\w-]+)/search/$', 'search'),
400 (r'^(?P<summit_name>[\w-]+)/propose_meeting/$', 'propose_meeting'),
401 (r'^(?P<summit_name>[\w-]+)/edit_meeting/(?P<meeting_id>\d+)/(?P<meeting_slug>[%+\.\w-]+)/$', 'edit_meeting'),
402 (r'^(?P<summit_name>[\w-]+)/review/$', 'review_pending'),

Subscribers

People subscribed via source and target branches