Merge lp:~cjohnston/summit/quick-links into lp:summit
- quick-links
- Merge into trunk
Proposed by
Chris Johnston
Status: | Superseded |
---|---|
Proposed branch: | lp:~cjohnston/summit/quick-links |
Merge into: | lp:summit |
Diff against target: |
744 lines (+594/-12) 13 files modified
EXTERNALS (+7/-0) summit/common/widgets.py (+111/-0) summit/media/css/style.css (+44/-0) summit/media/js/events-ui.js (+11/-0) summit/schedule/forms.py (+40/-0) summit/schedule/templates/schedule/actions.html (+8/-3) summit/schedule/templates/schedule/form.html (+47/-0) summit/schedule/templates/schedule/summit.html (+3/-7) summit/schedule/tests/__init__.py (+1/-0) summit/schedule/tests/registration.py (+272/-0) summit/schedule/views.py (+44/-1) summit/settings.py (+1/-1) summit/urls.py (+5/-0) |
To merge this branch: | bzr merge lp:~cjohnston/summit/quick-links |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Summit Hackers | Pending | ||
Review via email: mp+157550@code.launchpad.net |
This proposal has been superseded by a proposal from 2013-04-07.
Commit message
Does some work on the actions area
Description of the change
To post a comment you must log in.
lp:~cjohnston/summit/quick-links
updated
- 521. By Chris Johnston
-
Update from other branches
Unmerged revisions
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'EXTERNALS' | |||
2 | --- EXTERNALS 2012-04-22 20:20:10 +0000 | |||
3 | +++ EXTERNALS 2013-04-07 16:28:22 +0000 | |||
4 | @@ -19,3 +19,10 @@ | |||
5 | 19 | 19 | ||
6 | 20 | - Twitter JS | 20 | - Twitter JS |
7 | 21 | http://twitter.com/javascripts/blogger.js | 21 | http://twitter.com/javascripts/blogger.js |
8 | 22 | |||
9 | 23 | - Date Picker | ||
10 | 24 | provided by the JQuery UI project. See http://jqueryui.com/ | ||
11 | 25 | |||
12 | 26 | - DateTimeWidget inspired by: | ||
13 | 27 | http://copiesofcopies.org/webl/2010/04/26/a-better-datetime-widget-for-django/ | ||
14 | 28 | |||
15 | 22 | 29 | ||
16 | === added file 'summit/common/widgets.py' | |||
17 | --- summit/common/widgets.py 1970-01-01 00:00:00 +0000 | |||
18 | +++ summit/common/widgets.py 2013-04-07 16:28:22 +0000 | |||
19 | @@ -0,0 +1,111 @@ | |||
20 | 1 | # -*- coding: utf-8 -*- | ||
21 | 2 | from datetime import time | ||
22 | 3 | from time import strptime, strftime | ||
23 | 4 | |||
24 | 5 | from django import forms | ||
25 | 6 | |||
26 | 7 | |||
27 | 8 | class DateWidget(forms.DateInput): | ||
28 | 9 | """ | ||
29 | 10 | A more-friendly date widget with a pop-up calendar. | ||
30 | 11 | """ | ||
31 | 12 | def __init__(self, attrs=None): | ||
32 | 13 | self.date_class = 'datepicker' | ||
33 | 14 | if not attrs: | ||
34 | 15 | attrs = {} | ||
35 | 16 | if 'date_class' in attrs: | ||
36 | 17 | self.date_class = attrs.pop('date_class') | ||
37 | 18 | if 'class' not in attrs: | ||
38 | 19 | attrs['class'] = 'date' | ||
39 | 20 | |||
40 | 21 | super(DateWidget, self).__init__(attrs=attrs) | ||
41 | 22 | |||
42 | 23 | def render(self, name, value, attrs=None): | ||
43 | 24 | return '<span class="%s">%s</span>' % ( | ||
44 | 25 | self.date_class, | ||
45 | 26 | super(DateWidget, self).render(name, value, attrs) | ||
46 | 27 | ) | ||
47 | 28 | |||
48 | 29 | |||
49 | 30 | class TimeWidget(forms.MultiWidget): | ||
50 | 31 | """ | ||
51 | 32 | A more-friendly time widget. | ||
52 | 33 | """ | ||
53 | 34 | def __init__(self, attrs=None): | ||
54 | 35 | self.time_class = 'timepicker' | ||
55 | 36 | if not attrs: | ||
56 | 37 | attrs = {} | ||
57 | 38 | if 'time_class' in attrs: | ||
58 | 39 | self.time_class = attrs.pop('time_class') | ||
59 | 40 | if 'class' not in attrs: | ||
60 | 41 | attrs['class'] = 'time' | ||
61 | 42 | |||
62 | 43 | widgets = ( | ||
63 | 44 | forms.Select( | ||
64 | 45 | attrs=attrs, | ||
65 | 46 | choices=[(i + 1, "%02d" % (i + 1)) for i in range(0, 12)], | ||
66 | 47 | ), | ||
67 | 48 | forms.Select( | ||
68 | 49 | attrs=attrs, | ||
69 | 50 | choices=[(i, "%02d" % i) for i in range(00, 60, 15)], | ||
70 | 51 | ), | ||
71 | 52 | forms.Select( | ||
72 | 53 | attrs=attrs, | ||
73 | 54 | choices=[('AM', 'AM'), ('PM', 'PM')], | ||
74 | 55 | ) | ||
75 | 56 | ) | ||
76 | 57 | |||
77 | 58 | super(TimeWidget, self).__init__(widgets, attrs) | ||
78 | 59 | |||
79 | 60 | def decompress(self, value): | ||
80 | 61 | if isinstance(value, time): | ||
81 | 62 | hour = int(value.strftime("%I")) | ||
82 | 63 | minute = int(value.strftime("%M")) | ||
83 | 64 | meridian = value.strftime("%p") | ||
84 | 65 | return (hour, minute, meridian) | ||
85 | 66 | return (None, None, None) | ||
86 | 67 | |||
87 | 68 | def value_from_datadict(self, data, files, name): | ||
88 | 69 | value = super(TimeWidget, self).value_from_datadict(data, files, name) | ||
89 | 70 | t = strptime( | ||
90 | 71 | "%02d:%02d %s" % ( | ||
91 | 72 | int(value[0]), | ||
92 | 73 | int(value[1]), | ||
93 | 74 | value[2] | ||
94 | 75 | ), | ||
95 | 76 | "%I:%M %p", | ||
96 | 77 | ) | ||
97 | 78 | return strftime("%H:%M:%S", t) | ||
98 | 79 | |||
99 | 80 | def format_output(self, rendered_widgets): | ||
100 | 81 | return '<span class="%s">%s%s%s</span>' % ( | ||
101 | 82 | self.time_class, | ||
102 | 83 | rendered_widgets[0], rendered_widgets[1], rendered_widgets[2] | ||
103 | 84 | ) | ||
104 | 85 | |||
105 | 86 | |||
106 | 87 | class DateTimeWidget(forms.SplitDateTimeWidget): | ||
107 | 88 | """ | ||
108 | 89 | A more-friendly date/time widget. | ||
109 | 90 | |||
110 | 91 | Inspired by: | ||
111 | 92 | |||
112 | 93 | http://copiesofcopies.org/webl/2010/04/26/a-better-datetime-widget-for-django/ | ||
113 | 94 | """ | ||
114 | 95 | def __init__(self, attrs=None, date_format=None, time_format=None): | ||
115 | 96 | super(DateTimeWidget, self).__init__(attrs, date_format, time_format) | ||
116 | 97 | self.widgets = ( | ||
117 | 98 | DateWidget(attrs=attrs), | ||
118 | 99 | TimeWidget(attrs=attrs), | ||
119 | 100 | ) | ||
120 | 101 | |||
121 | 102 | def decompress(self, value): | ||
122 | 103 | if value: | ||
123 | 104 | d = strftime("%Y-%m-%d", value.timetuple()) | ||
124 | 105 | t = strftime("%I:%M %p", value.timetuple()) | ||
125 | 106 | return (d, t) | ||
126 | 107 | else: | ||
127 | 108 | return (None, None) | ||
128 | 109 | |||
129 | 110 | def format_output(self, rendered_widgets): | ||
130 | 111 | return '%s%s' % (rendered_widgets[0], rendered_widgets[1]) | ||
131 | 0 | 112 | ||
132 | === modified file 'summit/media/css/style.css' | |||
133 | --- summit/media/css/style.css 2012-01-22 18:36:40 +0000 | |||
134 | +++ summit/media/css/style.css 2013-04-07 16:28:22 +0000 | |||
135 | @@ -66,3 +66,47 @@ | |||
136 | 66 | .summit-columns ul li h3 a:hover { text-decoration: underline; } | 66 | .summit-columns ul li h3 a:hover { text-decoration: underline; } |
137 | 67 | .summit-columns ul li img { padding: 10px 0 5px; } | 67 | .summit-columns ul li img { padding: 10px 0 5px; } |
138 | 68 | .summit-columns p { margin-bottom: 5px; } | 68 | .summit-columns p { margin-bottom: 5px; } |
139 | 69 | |||
140 | 70 | #id_start_utc_0, #id_start_utc_1, | ||
141 | 71 | #id_end_utc_0, #id_end_utc_1, | ||
142 | 72 | #id_start_utc_1_0, #id_start_utc_1_1, #id_start_utc_1_2, | ||
143 | 73 | #id_end_utc_1_0, #id_end_utc_1_1, #id_end_utc_1_2 { | ||
144 | 74 | width: 100px; | ||
145 | 75 | display: inline; | ||
146 | 76 | } | ||
147 | 77 | |||
148 | 78 | #id_start_utc_1_0, #id_start_utc_1_1, #id_start_utc_1_2, | ||
149 | 79 | #id_end_utc_1_0, #id_end_utc_1_1, #id_end_utc_1_2 { | ||
150 | 80 | width: 60px; | ||
151 | 81 | } | ||
152 | 82 | |||
153 | 83 | #id_name, #id_announce, #id_registration { | ||
154 | 84 | width: 350px; | ||
155 | 85 | } | ||
156 | 86 | |||
157 | 87 | .link-cta { | ||
158 | 88 | margin-top: 10px; | ||
159 | 89 | padding: 10px; | ||
160 | 90 | background: url('../img/background-cta.png') center 0 repeat-x #dd4814; | ||
161 | 91 | display: block; | ||
162 | 92 | float: left; | ||
163 | 93 | color: #fff !important; | ||
164 | 94 | -moz-border-radius: 4px; | ||
165 | 95 | -webkit-border-radius: 4px; | ||
166 | 96 | border-radius: 4px; | ||
167 | 97 | } | ||
168 | 98 | |||
169 | 99 | .link-cta + p { | ||
170 | 100 | margin: 18px 0 0 10px; | ||
171 | 101 | float: left; | ||
172 | 102 | } | ||
173 | 103 | |||
174 | 104 | .link-cta.disabled { | ||
175 | 105 | background-image: none; | ||
176 | 106 | background-color: #aea79f !important; | ||
177 | 107 | cursor: not-allowed; | ||
178 | 108 | } | ||
179 | 109 | |||
180 | 110 | .link-arrow:after { | ||
181 | 111 | content: " ›"; | ||
182 | 112 | } | ||
183 | 69 | 113 | ||
184 | === added file 'summit/media/js/events-ui.js' | |||
185 | --- summit/media/js/events-ui.js 1970-01-01 00:00:00 +0000 | |||
186 | +++ summit/media/js/events-ui.js 2013-04-07 16:28:22 +0000 | |||
187 | @@ -0,0 +1,11 @@ | |||
188 | 1 | $(document).ready(function(){ | ||
189 | 2 | |||
190 | 3 | $.datepicker.setDefaults({ | ||
191 | 4 | showOn: 'focus', | ||
192 | 5 | dateFormat: 'yy-mm-dd', | ||
193 | 6 | }); | ||
194 | 7 | |||
195 | 8 | $("#id_start_utc_0").datepicker(); | ||
196 | 9 | $("#id_end_utc_0").datepicker(); | ||
197 | 10 | |||
198 | 11 | }); | ||
199 | 0 | 12 | ||
200 | === modified file 'summit/schedule/forms.py' | |||
201 | --- summit/schedule/forms.py 2013-03-09 05:14:19 +0000 | |||
202 | +++ summit/schedule/forms.py 2013-04-07 16:28:22 +0000 | |||
203 | @@ -14,6 +14,7 @@ | |||
204 | 14 | # You should have received a copy of the GNU Affero General Public License | 14 | # You should have received a copy of the GNU Affero General Public License |
205 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
206 | 16 | 16 | ||
207 | 17 | from django.conf import settings | ||
208 | 17 | from django import forms | 18 | from django import forms |
209 | 18 | 19 | ||
210 | 19 | from summit.schedule.models.meetingmodel import Meeting | 20 | from summit.schedule.models.meetingmodel import Meeting |
211 | @@ -21,6 +22,7 @@ | |||
212 | 21 | from summit.schedule.models.participantmodel import Participant | 22 | from summit.schedule.models.participantmodel import Participant |
213 | 22 | 23 | ||
214 | 23 | from common.forms import RenderableMixin | 24 | from common.forms import RenderableMixin |
215 | 25 | from common.widgets import DateTimeWidget | ||
216 | 24 | 26 | ||
217 | 25 | 27 | ||
218 | 26 | class MultipleAttendeeField(forms.ModelMultipleChoiceField): | 28 | class MultipleAttendeeField(forms.ModelMultipleChoiceField): |
219 | @@ -226,3 +228,41 @@ | |||
220 | 226 | fields = ('hangout_url', 'broadcast_url') | 228 | fields = ('hangout_url', 'broadcast_url') |
221 | 227 | 229 | ||
222 | 228 | broadcast_url = YouTubeEmbedURL(label='Broadcast URL') | 230 | broadcast_url = YouTubeEmbedURL(label='Broadcast URL') |
223 | 231 | |||
224 | 232 | |||
225 | 233 | class Registration(forms.ModelForm, RenderableMixin): | ||
226 | 234 | class Media: | ||
227 | 235 | css = {'all': ( | ||
228 | 236 | '//code.jquery.com/ui/1.10.2/themes/smoothness/jquery-ui.css', | ||
229 | 237 | )} | ||
230 | 238 | js = ( | ||
231 | 239 | '//code.jquery.com/ui/1.10.2/jquery-ui.js', | ||
232 | 240 | settings.MEDIA_URL + 'js/events-ui.js', | ||
233 | 241 | ) | ||
234 | 242 | |||
235 | 243 | class Meta: | ||
236 | 244 | model = Attendee | ||
237 | 245 | fields = ( | ||
238 | 246 | 'start_utc', | ||
239 | 247 | 'end_utc', | ||
240 | 248 | 'crew', | ||
241 | 249 | ) | ||
242 | 250 | |||
243 | 251 | def __init__(self, *args, **kargs): | ||
244 | 252 | super(Registration, self).__init__(*args, **kargs) | ||
245 | 253 | self.fields['start_utc'].widget = DateTimeWidget() | ||
246 | 254 | self.fields['end_utc'].widget = DateTimeWidget() | ||
247 | 255 | |||
248 | 256 | def clean(self): | ||
249 | 257 | begin = self.cleaned_data.get('start_utc') | ||
250 | 258 | end = self.cleaned_data.get('end_utc') | ||
251 | 259 | if begin and end and begin > end: | ||
252 | 260 | raise forms.ValidationError( | ||
253 | 261 | "Availability can not end before it starts." | ||
254 | 262 | ) | ||
255 | 263 | return self.cleaned_data | ||
256 | 264 | |||
257 | 265 | def save(self, commit=True): | ||
258 | 266 | instance = super(Registration, self).save(commit) | ||
259 | 267 | instance.from_launchpad=False | ||
260 | 268 | instance.save() | ||
261 | 229 | 269 | ||
262 | === modified file 'summit/schedule/templates/schedule/actions.html' | |||
263 | --- summit/schedule/templates/schedule/actions.html 2012-08-08 15:10:25 +0000 | |||
264 | +++ summit/schedule/templates/schedule/actions.html 2013-04-07 16:28:22 +0000 | |||
265 | @@ -1,9 +1,10 @@ | |||
266 | 1 | {% load schedule_perms %} | 1 | {% load schedule_perms %} |
267 | 2 | 2 | ||
268 | 3 | {% ifnotequal summit.state "public" %} | 3 | {% ifnotequal summit.state "public" %} |
272 | 4 | <div class="action-links"> | 4 | <div class="box box-padded"> |
273 | 5 | <p style="font-size: 16px; font-weight: bold;">Actions</p> | 5 | <h3>Quick Links</h3> |
274 | 6 | <ul> | 6 | <div> |
275 | 7 | <ul class="list"> | ||
276 | 7 | 8 | ||
277 | 8 | {% ifchangeschedule summit attendee %} | 9 | {% ifchangeschedule summit attendee %} |
278 | 9 | {% if schedule.date %} | 10 | {% if schedule.date %} |
279 | @@ -43,7 +44,11 @@ | |||
280 | 43 | {% endif %} | 44 | {% endif %} |
281 | 44 | {% endifequal %} | 45 | {% endifequal %} |
282 | 45 | 46 | ||
283 | 47 | {% if attendee %} | ||
284 | 48 | <li><a href="{% url registration summit.name %}">Update registration</a></li> | ||
285 | 49 | {% endif %} | ||
286 | 46 | </ul> | 50 | </ul> |
287 | 47 | </div> | 51 | </div> |
288 | 52 | </div> | ||
289 | 48 | 53 | ||
290 | 49 | {% endifnotequal %} | 54 | {% endifnotequal %} |
291 | 50 | 55 | ||
292 | === added file 'summit/schedule/templates/schedule/form.html' | |||
293 | --- summit/schedule/templates/schedule/form.html 1970-01-01 00:00:00 +0000 | |||
294 | +++ summit/schedule/templates/schedule/form.html 2013-04-07 16:28:22 +0000 | |||
295 | @@ -0,0 +1,47 @@ | |||
296 | 1 | {% extends "base.html" %} | ||
297 | 2 | |||
298 | 3 | {% block page_name %}{{ form_title }} - {{ summit.title }}{%endblock %} | ||
299 | 4 | {% block sub_nav %}{% endblock %} | ||
300 | 5 | |||
301 | 6 | {% block extrahead %}{{ block.super }} | ||
302 | 7 | {{ form.media }} | ||
303 | 8 | <script type="text/javascript" src="{{MEDIA_URL}}js/colortip-1.0-jquery.js"></script> | ||
304 | 9 | <link rel="stylesheet" type="text/css" href="{{MEDIA_URL}}css/colortip-1.0-jquery.css"/> | ||
305 | 10 | {% endblock %} | ||
306 | 11 | |||
307 | 12 | {% block closure %} | ||
308 | 13 | <script type="text/javascript"><!-- | ||
309 | 14 | $(document).ready(function(){ | ||
310 | 15 | $('span[rel*=help]').colorTip({color:'orange'}); | ||
311 | 16 | }); | ||
312 | 17 | --></script> | ||
313 | 18 | <style> | ||
314 | 19 | form ul { | ||
315 | 20 | height: 12em; | ||
316 | 21 | overflow-y: scroll; | ||
317 | 22 | overflow-x: hidden; | ||
318 | 23 | } | ||
319 | 24 | </style> | ||
320 | 25 | {% endblock %} | ||
321 | 26 | |||
322 | 27 | |||
323 | 28 | {% block content %} | ||
324 | 29 | <div class="row"> | ||
325 | 30 | <article id="form" class="span-8"> | ||
326 | 31 | {% if form.errors %} | ||
327 | 32 | <p style="color: red;"> | ||
328 | 33 | Please correct the error{{ form.errors|pluralize }} below. | ||
329 | 34 | </p> | ||
330 | 35 | {% endif %} | ||
331 | 36 | |||
332 | 37 | <form action="{{ request.path_info }}" method="POST">{% csrf_token %} | ||
333 | 38 | <fieldset> | ||
334 | 39 | <h3>{{ form_title }}</h3> | ||
335 | 40 | {{ form.as_template }} | ||
336 | 41 | {% if is_popup %}<input type="hidden" name="_popup" value="1">{% endif %} | ||
337 | 42 | <input type="submit" name="submit" value="Create" class="submit-button" /> | ||
338 | 43 | </fieldset> | ||
339 | 44 | </form> | ||
340 | 45 | </article> | ||
341 | 46 | </div> | ||
342 | 47 | {% endblock %} | ||
343 | 0 | 48 | ||
344 | === modified file 'summit/schedule/templates/schedule/summit.html' | |||
345 | --- summit/schedule/templates/schedule/summit.html 2013-03-20 02:00:45 +0000 | |||
346 | +++ summit/schedule/templates/schedule/summit.html 2013-04-07 16:28:22 +0000 | |||
347 | @@ -38,19 +38,15 @@ | |||
348 | 38 | <div class="row"> | 38 | <div class="row"> |
349 | 39 | <section class="span-8"> | 39 | <section class="span-8"> |
350 | 40 | {% if attendee %} | 40 | {% if attendee %} |
353 | 41 | <p>You are attending, you can update the days and times of your attendance <ins></ins> | 41 | <p>You are registered as attending {{ summit.title }}. <a href="{% url registration summit %}">Update registration</a>. |
352 | 42 | <a class="launchpad" href="http://launchpad.net/sprints/{{ summit.name }}/+attend"><img src="/media/img/gem-sm.png" /> Launchpad</a>. | ||
354 | 43 | </p> | 42 | </p> |
355 | 44 | <p>Download your | 43 | <p>Download your |
356 | 45 | <a href="/{{ summit.name }}/participant/my_schedule_{{ attendee.secret_key }}.ical">Participation Schedule</a> to import into your Calendar. | 44 | <a href="/{{ summit.name }}/participant/my_schedule_{{ attendee.secret_key }}.ical">Participation Schedule</a> to import into your Calendar. |
357 | 46 | </p> | 45 | </p> |
358 | 47 | {% else %} | 46 | {% else %} |
359 | 48 | {% if request.user.is_authenticated %} | 47 | {% if request.user.is_authenticated %} |
365 | 49 | <p><strong>You are not registered as attending.</strong></p> | 48 | <p><strong>You have not registered in Summit.</strong></p> |
366 | 50 | <p>You can register your attendance in | 49 | <p><a class="link-cta" href="{% url registration summit %}">Register in Summit</a></p> |
362 | 51 | <a class="launchpad" href="http://launchpad.net/sprints/{{ summit.name }}/+attend"><img src="/media/img/gem-sm.png" /> Launchpad</a>. | ||
363 | 52 | If you have recently done so, wait a few minutes and reload this page. | ||
364 | 53 | </p> | ||
367 | 54 | {% else %} | 50 | {% else %} |
368 | 55 | <p><strong>You are not logged in.</strong></p> | 51 | <p><strong>You are not logged in.</strong></p> |
369 | 56 | <p><a href="/openid/login?next={{login_next}}">Log in now</a></p> | 52 | <p><a href="/openid/login?next={{login_next}}">Log in now</a></p> |
370 | 57 | 53 | ||
371 | === modified file 'summit/schedule/tests/__init__.py' | |||
372 | --- summit/schedule/tests/__init__.py 2013-03-14 20:52:12 +0000 | |||
373 | +++ summit/schedule/tests/__init__.py 2013-04-07 16:28:22 +0000 | |||
374 | @@ -31,3 +31,4 @@ | |||
375 | 31 | from schedule import * | 31 | from schedule import * |
376 | 32 | from summit_model import * | 32 | from summit_model import * |
377 | 33 | from propose_meeting import * | 33 | from propose_meeting import * |
378 | 34 | from registration import RegistrationTestCase | ||
379 | 34 | 35 | ||
380 | === added file 'summit/schedule/tests/registration.py' | |||
381 | --- summit/schedule/tests/registration.py 1970-01-01 00:00:00 +0000 | |||
382 | +++ summit/schedule/tests/registration.py 2013-04-07 16:28:22 +0000 | |||
383 | @@ -0,0 +1,272 @@ | |||
384 | 1 | # The Summit Scheduler web application | ||
385 | 2 | # Copyright (C) 2008 - 2013 Ubuntu Community, Canonical Ltd | ||
386 | 3 | # | ||
387 | 4 | # This program is free software: you can redistribute it and/or modify | ||
388 | 5 | # it under the terms of the GNU Affero General Public License as | ||
389 | 6 | # published by the Free Software Foundation, either version 3 of the | ||
390 | 7 | # License, or (at your option) any later version. | ||
391 | 8 | # | ||
392 | 9 | # This program is distributed in the hope that it will be useful, | ||
393 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
394 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
395 | 12 | # GNU Affero General Public License for more details. | ||
396 | 13 | # | ||
397 | 14 | # You should have received a copy of the GNU Affero General Public License | ||
398 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
399 | 16 | |||
400 | 17 | import datetime | ||
401 | 18 | |||
402 | 19 | from django import test as djangotest | ||
403 | 20 | from django.core.urlresolvers import reverse | ||
404 | 21 | |||
405 | 22 | from django.contrib.auth.models import User | ||
406 | 23 | from django.test.client import Client | ||
407 | 24 | |||
408 | 25 | from model_mommy import mommy as factory | ||
409 | 26 | |||
410 | 27 | from summit.schedule.models import ( | ||
411 | 28 | Summit, | ||
412 | 29 | Attendee, | ||
413 | 30 | ) | ||
414 | 31 | |||
415 | 32 | |||
416 | 33 | class RegistrationTestCase(djangotest.TestCase): | ||
417 | 34 | """ | ||
418 | 35 | Tests for registering to attend a Summit | ||
419 | 36 | """ | ||
420 | 37 | c = Client() | ||
421 | 38 | |||
422 | 39 | def setUp(self): | ||
423 | 40 | self.now = datetime.datetime.utcnow() | ||
424 | 41 | self.one_hour = datetime.timedelta(0, 3600) | ||
425 | 42 | self.one_day = datetime.timedelta(days=1) | ||
426 | 43 | self.week = datetime.timedelta(days=5) | ||
427 | 44 | self.end_summit = self.now + self.week | ||
428 | 45 | self.summit = factory.make_one( | ||
429 | 46 | Summit, | ||
430 | 47 | name='test-summit', | ||
431 | 48 | title='Test Summit', | ||
432 | 49 | virtual_summit=True, | ||
433 | 50 | date_start=self.now, | ||
434 | 51 | date_end=self.now + self.week, | ||
435 | 52 | timezone='UTC', | ||
436 | 53 | ) | ||
437 | 54 | |||
438 | 55 | self.user1 = factory.make_one( | ||
439 | 56 | User, | ||
440 | 57 | username='testuser', | ||
441 | 58 | first_name='Test', | ||
442 | 59 | last_name='User', | ||
443 | 60 | is_active=True, | ||
444 | 61 | is_superuser=False, | ||
445 | 62 | ) | ||
446 | 63 | self.user1.set_password('password') | ||
447 | 64 | self.user1.save() | ||
448 | 65 | |||
449 | 66 | def create_attendee(self): | ||
450 | 67 | self.attendee1 = factory.make_one( | ||
451 | 68 | Attendee, | ||
452 | 69 | summit=self.summit, | ||
453 | 70 | user=self.user1, | ||
454 | 71 | start_utc=self.now, | ||
455 | 72 | end_utc=self.now+self.week | ||
456 | 73 | ) | ||
457 | 74 | |||
458 | 75 | def tearDown(self): | ||
459 | 76 | self.client.logout() | ||
460 | 77 | |||
461 | 78 | def login(self): | ||
462 | 79 | logged_in = self.c.login( | ||
463 | 80 | username='testuser', | ||
464 | 81 | password='password') | ||
465 | 82 | self.assertTrue(logged_in) | ||
466 | 83 | |||
467 | 84 | def get_attendee(self): | ||
468 | 85 | attendee = Attendee.objects.get(user=self.user1) | ||
469 | 86 | return attendee | ||
470 | 87 | |||
471 | 88 | def view_summit_page(self): | ||
472 | 89 | rev_args = [self.summit.name, ] | ||
473 | 90 | response = self.c.get( | ||
474 | 91 | reverse( | ||
475 | 92 | 'summit.schedule.views.summit', | ||
476 | 93 | args=rev_args | ||
477 | 94 | ) | ||
478 | 95 | ) | ||
479 | 96 | self.assertEqual(response.status_code, 200) | ||
480 | 97 | self.assertTemplateUsed(response, 'schedule/summit.html') | ||
481 | 98 | |||
482 | 99 | return response | ||
483 | 100 | |||
484 | 101 | def with_summit_registration_times(self): | ||
485 | 102 | """ | ||
486 | 103 | Using the start and end of the summit as the registration times | ||
487 | 104 | """ | ||
488 | 105 | self.start_date = self.now.strftime("%Y-%m-%d") | ||
489 | 106 | self.start_hour = self.now.strftime("%I") | ||
490 | 107 | self.start_minute = self.now.strftime("%M") | ||
491 | 108 | self.start_period = self.now.strftime("%p") | ||
492 | 109 | self.end_hour = self.end_summit.strftime("%I") | ||
493 | 110 | self.end_minute = self.end_summit.strftime("%M") | ||
494 | 111 | self.end_period = self.end_summit.strftime("%p") | ||
495 | 112 | self.end_date = self.end_summit.strftime("%Y-%m-%d") | ||
496 | 113 | self.edit_registration_form() | ||
497 | 114 | attendee = self.get_attendee() | ||
498 | 115 | self.assertEqual( | ||
499 | 116 | attendee.start_utc.replace(second=0), | ||
500 | 117 | self.now.replace(second=0, microsecond=0), | ||
501 | 118 | ) | ||
502 | 119 | self.assertEqual( | ||
503 | 120 | attendee.end_utc.replace(second=0, microsecond=0), | ||
504 | 121 | self.end_summit.replace(second=0, microsecond=0), | ||
505 | 122 | ) | ||
506 | 123 | |||
507 | 124 | def with_custom_registration_times(self): | ||
508 | 125 | """ | ||
509 | 126 | Using custom start and end registration times | ||
510 | 127 | """ | ||
511 | 128 | start = self.now + self.one_day | ||
512 | 129 | self.start_date = start.strftime("%Y-%m-%d") | ||
513 | 130 | self.start_hour = self.now.strftime("%I") | ||
514 | 131 | self.start_minute = self.now.strftime("%M") | ||
515 | 132 | self.start_period = self.now.strftime("%p") | ||
516 | 133 | self.end_hour = self.end_summit.strftime("%I") | ||
517 | 134 | self.end_minute = self.end_summit.strftime("%M") | ||
518 | 135 | self.end_period = self.end_summit.strftime("%p") | ||
519 | 136 | self.end_date = self.end_summit.strftime("%Y-%m-%d") | ||
520 | 137 | self.edit_registration_form() | ||
521 | 138 | attendee = self.get_attendee() | ||
522 | 139 | self.assertEqual( | ||
523 | 140 | attendee.start_utc.replace(second=0), | ||
524 | 141 | start.replace(second=0, microsecond=0), | ||
525 | 142 | ) | ||
526 | 143 | self.assertEqual( | ||
527 | 144 | attendee.end_utc.replace(second=0, microsecond=0), | ||
528 | 145 | self.end_summit.replace(second=0, microsecond=0), | ||
529 | 146 | ) | ||
530 | 147 | |||
531 | 148 | def edit_registration_form(self): | ||
532 | 149 | """ | ||
533 | 150 | Tests that a user can register for a Summit | ||
534 | 151 | """ | ||
535 | 152 | # Define data to fill our the form | ||
536 | 153 | |||
537 | 154 | # Post the form | ||
538 | 155 | post = self.c.post( | ||
539 | 156 | reverse( | ||
540 | 157 | 'registration', | ||
541 | 158 | args=(self.summit.name,) | ||
542 | 159 | ), | ||
543 | 160 | data={ | ||
544 | 161 | 'end_utc_0': self.end_date, | ||
545 | 162 | 'end_utc_1_0': self.end_hour, | ||
546 | 163 | 'end_utc_1_1': self.end_minute, | ||
547 | 164 | 'end_utc_1_2': self.end_period, | ||
548 | 165 | 'start_utc_1_0': self.start_hour, | ||
549 | 166 | 'start_utc_1_1': self.start_minute, | ||
550 | 167 | 'start_utc_1_2': self.start_period, | ||
551 | 168 | 'start_utc_0': self.start_date, | ||
552 | 169 | } | ||
553 | 170 | ) | ||
554 | 171 | |||
555 | 172 | # A successful post should redirect to the summit page | ||
556 | 173 | response = reverse( | ||
557 | 174 | 'summit.schedule.views.summit', | ||
558 | 175 | args=(self.summit.name,) | ||
559 | 176 | ) | ||
560 | 177 | self.assertEqual(post.status_code, 302) | ||
561 | 178 | self.assertRedirects(post, response) | ||
562 | 179 | attendee = self.get_attendee() | ||
563 | 180 | self.assertEqual(attendee.user, self.user1) | ||
564 | 181 | self.assertEqual(attendee.from_launchpad, False) | ||
565 | 182 | |||
566 | 183 | def test_non_attendee_registers(self): | ||
567 | 184 | self.login() | ||
568 | 185 | self.assertRaises(Attendee.DoesNotExist, lambda: self.get_attendee()) | ||
569 | 186 | rev_args = [self.summit.name, ] | ||
570 | 187 | response = self.c.get(reverse('registration', args=rev_args)) | ||
571 | 188 | self.assertEqual(response.status_code, 200) | ||
572 | 189 | self.assertTemplateUsed(response, 'schedule/form.html') | ||
573 | 190 | self.assertIn('Register for ' + self.summit.title, response.content) | ||
574 | 191 | self.with_summit_registration_times() | ||
575 | 192 | |||
576 | 193 | def test_attendee_updates_registration(self): | ||
577 | 194 | self.create_attendee() | ||
578 | 195 | self.login() | ||
579 | 196 | self.assertEquals( | ||
580 | 197 | self.user1.username, | ||
581 | 198 | self.get_attendee().user.username, | ||
582 | 199 | ) | ||
583 | 200 | rev_args = [self.summit.name, ] | ||
584 | 201 | response = self.c.get(reverse('registration', args=rev_args)) | ||
585 | 202 | self.assertEqual(response.status_code, 200) | ||
586 | 203 | self.assertTemplateUsed(response, 'schedule/form.html') | ||
587 | 204 | self.assertIn( | ||
588 | 205 | 'Update registration for ' + self.summit.title, | ||
589 | 206 | response.content | ||
590 | 207 | ) | ||
591 | 208 | self.with_custom_registration_times() | ||
592 | 209 | |||
593 | 210 | def test_update_registration_from_launchpad_true(self): | ||
594 | 211 | """ | ||
595 | 212 | update resgistration with from_launchpad=True | ||
596 | 213 | to save from_launchpad=False | ||
597 | 214 | """ | ||
598 | 215 | self.create_attendee() | ||
599 | 216 | self.attendee1.from_launchpad = True | ||
600 | 217 | self.attendee1.save() | ||
601 | 218 | self.login() | ||
602 | 219 | self.assertEquals( | ||
603 | 220 | self.user1.username, | ||
604 | 221 | self.get_attendee().user.username, | ||
605 | 222 | ) | ||
606 | 223 | self.assertEquals( | ||
607 | 224 | True, | ||
608 | 225 | self.get_attendee().from_launchpad, | ||
609 | 226 | ) | ||
610 | 227 | rev_args = [self.summit.name, ] | ||
611 | 228 | response = self.c.get(reverse('registration', args=rev_args)) | ||
612 | 229 | self.assertEqual(response.status_code, 200) | ||
613 | 230 | self.assertTemplateUsed(response, 'schedule/form.html') | ||
614 | 231 | self.assertIn( | ||
615 | 232 | 'Update registration for ' + self.summit.title, | ||
616 | 233 | response.content | ||
617 | 234 | ) | ||
618 | 235 | self.with_custom_registration_times() | ||
619 | 236 | |||
620 | 237 | def test_registered_view(self): | ||
621 | 238 | """ | ||
622 | 239 | Test that when a user is already registered for the summit | ||
623 | 240 | that they will see a link to update their registration | ||
624 | 241 | """ | ||
625 | 242 | self.create_attendee() | ||
626 | 243 | self.attendee1.from_launchpad = True | ||
627 | 244 | self.attendee1.save() | ||
628 | 245 | self.login() | ||
629 | 246 | rev_args = [self.summit.name, ] | ||
630 | 247 | response = self.view_summit_page() | ||
631 | 248 | self.assertIn( | ||
632 | 249 | reverse( | ||
633 | 250 | 'registration', | ||
634 | 251 | args=rev_args | ||
635 | 252 | ), | ||
636 | 253 | response.content | ||
637 | 254 | ) | ||
638 | 255 | self.assertIn('Update registration', response.content) | ||
639 | 256 | |||
640 | 257 | def test_unregistered_view(self): | ||
641 | 258 | """ | ||
642 | 259 | Test that when a user is not registered for the summit | ||
643 | 260 | that they will see a button to register | ||
644 | 261 | """ | ||
645 | 262 | self.login() | ||
646 | 263 | rev_args = [self.summit.name, ] | ||
647 | 264 | response = self.view_summit_page() | ||
648 | 265 | self.assertIn( | ||
649 | 266 | reverse( | ||
650 | 267 | 'registration', | ||
651 | 268 | args=rev_args | ||
652 | 269 | ), | ||
653 | 270 | response.content | ||
654 | 271 | ) | ||
655 | 272 | self.assertIn('Register in Summit', response.content) | ||
656 | 0 | 273 | ||
657 | === modified file 'summit/schedule/views.py' | |||
658 | --- summit/schedule/views.py 2013-03-15 01:06:10 +0000 | |||
659 | +++ summit/schedule/views.py 2013-04-07 16:28:22 +0000 | |||
660 | @@ -52,7 +52,8 @@ | |||
661 | 52 | MeetingReview, | 52 | MeetingReview, |
662 | 53 | AttendMeeting, | 53 | AttendMeeting, |
663 | 54 | OrganizerChangeAttend, | 54 | OrganizerChangeAttend, |
665 | 55 | EditMeetingHangout | 55 | EditMeetingHangout, |
666 | 56 | Registration, | ||
667 | 56 | ) | 57 | ) |
668 | 57 | 58 | ||
669 | 58 | __all__ = ( | 59 | __all__ = ( |
670 | @@ -1173,3 +1174,45 @@ | |||
671 | 1173 | context, | 1174 | context, |
672 | 1174 | RequestContext(request) | 1175 | RequestContext(request) |
673 | 1175 | ) | 1176 | ) |
674 | 1177 | |||
675 | 1178 | |||
676 | 1179 | @login_required | ||
677 | 1180 | @summit_required | ||
678 | 1181 | def registration_form(request, summit, attendee): | ||
679 | 1182 | registration_args = dict() | ||
680 | 1183 | |||
681 | 1184 | if attendee is None: | ||
682 | 1185 | attendee = Attendee( | ||
683 | 1186 | user=request.user, | ||
684 | 1187 | summit=summit, | ||
685 | 1188 | from_launchpad=False, | ||
686 | 1189 | ) | ||
687 | 1190 | registration_args['instance'] = attendee | ||
688 | 1191 | form_title="Register for %s" % summit.title | ||
689 | 1192 | else: | ||
690 | 1193 | registration_args['instance'] = attendee | ||
691 | 1194 | form_title="Update registration for %s" % summit.title | ||
692 | 1195 | |||
693 | 1196 | if request.method == 'POST': | ||
694 | 1197 | form = Registration(data=request.POST, **registration_args) | ||
695 | 1198 | if form.is_valid(): | ||
696 | 1199 | form.save() | ||
697 | 1200 | return HttpResponseRedirect( | ||
698 | 1201 | reverse( | ||
699 | 1202 | 'summit.schedule.views.summit', | ||
700 | 1203 | args=(summit.name,) | ||
701 | 1204 | ) | ||
702 | 1205 | ) | ||
703 | 1206 | else: | ||
704 | 1207 | form = Registration(**registration_args) | ||
705 | 1208 | |||
706 | 1209 | context = { | ||
707 | 1210 | 'summit': summit, | ||
708 | 1211 | 'form': form, | ||
709 | 1212 | 'form_title': form_title, | ||
710 | 1213 | } | ||
711 | 1214 | return render_to_response( | ||
712 | 1215 | 'schedule/form.html', | ||
713 | 1216 | context, | ||
714 | 1217 | RequestContext(request) | ||
715 | 1218 | ) | ||
716 | 1176 | 1219 | ||
717 | === modified file 'summit/settings.py' | |||
718 | --- summit/settings.py 2013-04-04 02:13:03 +0000 | |||
719 | +++ summit/settings.py 2013-04-07 16:28:22 +0000 | |||
720 | @@ -167,7 +167,7 @@ | |||
721 | 167 | 'bzr_apps': ('http://bazaar.launchpad.net/~django-foundations-dev/ubuntu-django-foundations/bzr_apps', '7'), | 167 | 'bzr_apps': ('http://bazaar.launchpad.net/~django-foundations-dev/ubuntu-django-foundations/bzr_apps', '7'), |
722 | 168 | 168 | ||
723 | 169 | ## ubuntu-website supplied templates and styles | 169 | ## ubuntu-website supplied templates and styles |
725 | 170 | 'ubuntu_website': ('http://bazaar.launchpad.net/~ubuntu-community-webthemes/ubuntu-community-webthemes/light-django-theme', '55'), | 170 | 'ubuntu_website': ('http://bazaar.launchpad.net/~ubuntu-community-webthemes/ubuntu-community-webthemes/light-django-theme', '61'), |
726 | 171 | 171 | ||
727 | 172 | ## linaro-website supplied templates and styles | 172 | ## linaro-website supplied templates and styles |
728 | 173 | 'linaro_website': ('http://bazaar.launchpad.net/~linaro-connect-theme-devs/ubuntu-community-webthemes/light-django-linaro-theme', '52'), | 173 | 'linaro_website': ('http://bazaar.launchpad.net/~linaro-connect-theme-devs/ubuntu-community-webthemes/light-django-linaro-theme', '52'), |
729 | 174 | 174 | ||
730 | === modified file 'summit/urls.py' | |||
731 | --- summit/urls.py 2013-02-26 19:31:21 +0000 | |||
732 | +++ summit/urls.py 2013-04-07 16:28:22 +0000 | |||
733 | @@ -68,6 +68,11 @@ | |||
734 | 68 | (r'^(?P<summit_name>[\w-]+)/$', 'summit'), | 68 | (r'^(?P<summit_name>[\w-]+)/$', 'summit'), |
735 | 69 | (r'^(?P<summit_name>[\w-]+)/mobile/$', 'mobile'), | 69 | (r'^(?P<summit_name>[\w-]+)/mobile/$', 'mobile'), |
736 | 70 | (r'^(?P<summit_name>[\w-]+)/search/$', 'search'), | 70 | (r'^(?P<summit_name>[\w-]+)/search/$', 'search'), |
737 | 71 | url( | ||
738 | 72 | r'^(?P<summit_name>[\w-]+)/registration/$', | ||
739 | 73 | 'registration_form', | ||
740 | 74 | name='registration', | ||
741 | 75 | ), | ||
742 | 71 | (r'^(?P<summit_name>[\w-]+)/propose_meeting/$', 'propose_meeting'), | 76 | (r'^(?P<summit_name>[\w-]+)/propose_meeting/$', 'propose_meeting'), |
743 | 72 | (r'^(?P<summit_name>[\w-]+)/edit_meeting/(?P<meeting_id>\d+)/(?P<meeting_slug>[%+\.\w-]+)/$', | 77 | (r'^(?P<summit_name>[\w-]+)/edit_meeting/(?P<meeting_id>\d+)/(?P<meeting_slug>[%+\.\w-]+)/$', |
744 | 73 | 'edit_meeting'), | 78 | 'edit_meeting'), |