Merge lp:~cjohnston/summit/participation-changes into lp:summit

Proposed by Chris Johnston
Status: Merged
Merged at revision: 425
Proposed branch: lp:~cjohnston/summit/participation-changes
Merge into: lp:summit
Diff against target: 927 lines (+601/-36)
13 files modified
summit/schedule/admin/attendeeadmin.py (+1/-1)
summit/schedule/forms.py (+13/-2)
summit/schedule/migrations/0024_change_required_to_participation.py (+212/-0)
summit/schedule/models/meetingmodel.py (+28/-10)
summit/schedule/models/participantmodel.py (+10/-3)
summit/schedule/models/summitmodel.py (+5/-1)
summit/schedule/templates/schedule/attend.html (+2/-2)
summit/schedule/templates/schedule/attendees.html (+96/-0)
summit/schedule/templates/schedule/meeting.html (+10/-2)
summit/schedule/templates/schedule/org_attend.html (+39/-0)
summit/schedule/tests.py (+129/-13)
summit/schedule/views.py (+54/-2)
summit/urls.py (+2/-0)
To merge this branch: bzr merge lp:~cjohnston/summit/participation-changes
Reviewer Review Type Date Requested Status
Summit Hackers Pending
Review via email: mp+114998@code.launchpad.net
To post a comment you must log in.
419. By Chris Johnston

Sponsorship application and suggestion CSS error (bug # 1030187) fix. Props: ~joseeantonior

420. By Chris Johnston

Removes code in summitmodel from lpupdate which deletes meetings that have a spec_url which isn't a launchpad blueprint

421. By Chris Johnston

Adds admin info to the meeting page

422. By Chris Johnston

Release 1.1.8

423. By Chris Johnston

Allows drafters and organizers to modify the attending level of attendees

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'summit/schedule/admin/attendeeadmin.py'
2--- summit/schedule/admin/attendeeadmin.py 2012-04-27 19:40:23 +0000
3+++ summit/schedule/admin/attendeeadmin.py 2012-08-05 16:34:23 +0000
4@@ -37,7 +37,7 @@
5
6
7 class ParticipantAdmin(admin.ModelAdmin):
8- list_display = ('meeting', 'attendee', 'required')
9+ list_display = ('meeting', 'attendee', 'participation')
10 list_display_links = ('attendee', )
11 search_fields = ('attendee__user__username', 'attendee__user__first_name', 'attendee__user__last_name',
12 'attendee__user__email', 'meeting__name', 'meeting__title')
13
14=== modified file 'summit/schedule/forms.py'
15--- summit/schedule/forms.py 2012-05-06 01:16:00 +0000
16+++ summit/schedule/forms.py 2012-08-05 16:34:23 +0000
17@@ -88,7 +88,7 @@
18 instance.participants.clear()
19 for participant in self.cleaned_data['participants']:
20 record = Participant(meeting=instance, attendee=participant,
21- required=True)
22+ participation='INTERESTED')
23 record.save()
24 self.save_m2m = save_m2m
25
26@@ -131,4 +131,15 @@
27 class AttendMeeting(forms.ModelForm, RenderableMixin):
28 class Meta:
29 model = Participant
30- fields = ('required',)
31+ fields = ('participation',)
32+ def __init__(self, *args, **kwargs):
33+ super(AttendMeeting, self).__init__(*args, **kwargs)
34+ self.fields['participation'].choices = ((u'ATTENDING', u'Attending'), (u'INTERESTED', u'Very interested in attending'))
35+
36+class OrganizerChangeAttend(forms.ModelForm, RenderableMixin):
37+ class Meta:
38+ model = Participant
39+ fields = ('participation',)
40+ def __init__(self, *args, **kwargs):
41+ super(OrganizerChangeAttend, self).__init__(*args, **kwargs)
42+ self.fields['participation'].choices = ((u'INTERESTED', u'Should Attend'), (u'REQUIRED', u'Required to Attend'))
43
44=== added file 'summit/schedule/migrations/0024_change_required_to_participation.py'
45--- summit/schedule/migrations/0024_change_required_to_participation.py 1970-01-01 00:00:00 +0000
46+++ summit/schedule/migrations/0024_change_required_to_participation.py 2012-08-05 16:34:23 +0000
47@@ -0,0 +1,212 @@
48+# encoding: utf-8
49+import datetime
50+from south.db import db
51+from south.v2 import SchemaMigration
52+from django.db import models
53+
54+class Migration(SchemaMigration):
55+
56+ def forwards(self, orm):
57+
58+ # Deleting field 'Participant.required'
59+ db.delete_column('schedule_participant', 'required')
60+
61+ # Adding field 'Participant.participation'
62+ db.add_column('schedule_participant', 'participation', self.gf('django.db.models.fields.CharField')(default='ATTENDING', max_length=32, null=True), keep_default=False)
63+
64+
65+ def backwards(self, orm):
66+
67+ # Adding field 'Participant.required'
68+ db.add_column('schedule_participant', 'required', self.gf('django.db.models.fields.BooleanField')(default=False), keep_default=False)
69+
70+ # Deleting field 'Participant.participation'
71+ db.delete_column('schedule_participant', 'participation')
72+
73+
74+ models = {
75+ 'auth.group': {
76+ 'Meta': {'object_name': 'Group'},
77+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
78+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
79+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
80+ },
81+ 'auth.permission': {
82+ 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
83+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
84+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
85+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
86+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
87+ },
88+ 'auth.user': {
89+ 'Meta': {'ordering': "['username']", 'object_name': 'User'},
90+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
91+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
92+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
93+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
94+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
95+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
96+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
97+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
98+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
99+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
100+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
101+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
102+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
103+ },
104+ 'contenttypes.contenttype': {
105+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
106+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
107+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
108+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
109+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
110+ },
111+ 'schedule.agenda': {
112+ 'Meta': {'ordering': "('slot', 'room')", 'unique_together': "(('slot', 'room'),)", 'object_name': 'Agenda'},
113+ 'auto': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
114+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
115+ 'meeting': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['schedule.Meeting']"}),
116+ 'room': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['schedule.Room']"}),
117+ 'slot': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['schedule.Slot']"})
118+ },
119+ 'schedule.attendee': {
120+ 'Meta': {'ordering': "('user__username', 'summit')", 'object_name': 'Attendee'},
121+ 'crew': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_column': "'crew'"}),
122+ 'end_utc': ('django.db.models.fields.DateTimeField', [], {'db_column': "'end'"}),
123+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
124+ 'secret_key_id': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
125+ 'start_utc': ('django.db.models.fields.DateTimeField', [], {'db_column': "'start'"}),
126+ 'summit': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['schedule.Summit']"}),
127+ 'tracks': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['schedule.Track']", 'symmetrical': 'False', 'blank': 'True'}),
128+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
129+ },
130+ 'schedule.attendeebusy': {
131+ 'Meta': {'ordering': "('attendee', 'start_utc', 'end_utc')", 'object_name': 'AttendeeBusy'},
132+ 'attendee': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'busy_set'", 'to': "orm['schedule.Attendee']"}),
133+ 'end_utc': ('django.db.models.fields.DateTimeField', [], {'db_column': "'end'"}),
134+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
135+ 'start_utc': ('django.db.models.fields.DateTimeField', [], {'db_column': "'start'"})
136+ },
137+ 'schedule.crew': {
138+ 'Meta': {'ordering': "('date_utc', 'attendee')", 'object_name': 'Crew'},
139+ 'attendee': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'crew_schedule'", 'to': "orm['schedule.Attendee']"}),
140+ 'date_utc': ('django.db.models.fields.DateField', [], {'db_column': "'date'"}),
141+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
142+ },
143+ 'schedule.lead': {
144+ 'Meta': {'ordering': "('summit', 'track')", 'object_name': 'Lead'},
145+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
146+ 'lead': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'lead'", 'to': "orm['schedule.Attendee']"}),
147+ 'summit': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['schedule.Summit']"}),
148+ 'track': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['schedule.Track']"})
149+ },
150+ 'schedule.meeting': {
151+ 'Meta': {'object_name': 'Meeting'},
152+ 'approved': ('django.db.models.fields.CharField', [], {'default': "'PENDING'", 'max_length': '10', 'null': 'True'}),
153+ 'approver': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'approver_set'", 'null': 'True', 'to': "orm['schedule.Attendee']"}),
154+ 'assignee': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'assignee_set'", 'null': 'True', 'to': "orm['schedule.Attendee']"}),
155+ 'description': ('django.db.models.fields.TextField', [], {'max_length': '2047', 'blank': 'True'}),
156+ 'drafter': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'drafter_set'", 'null': 'True', 'to': "orm['schedule.Attendee']"}),
157+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
158+ 'name': ('summit.schedule.fields.NameField', [], {'max_length': '100'}),
159+ 'override_break': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
160+ 'pad_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
161+ 'participants': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['schedule.Attendee']", 'symmetrical': 'False', 'through': "orm['schedule.Participant']", 'blank': 'True'}),
162+ 'priority': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
163+ 'private': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
164+ 'private_key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
165+ 'requires_dial_in': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
166+ 'scribe': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'scribe_set'", 'null': 'True', 'to': "orm['schedule.Attendee']"}),
167+ 'slots': ('django.db.models.fields.IntegerField', [], {'default': '1'}),
168+ 'spec_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
169+ 'status': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}),
170+ 'summit': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['schedule.Summit']"}),
171+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
172+ 'tracks': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['schedule.Track']", 'symmetrical': 'False', 'blank': 'True'}),
173+ 'type': ('django.db.models.fields.CharField', [], {'default': "u'discussion'", 'max_length': '15'}),
174+ 'video': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
175+ 'wiki_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'})
176+ },
177+ 'schedule.participant': {
178+ 'Meta': {'ordering': "('meeting', 'attendee', 'participation')", 'object_name': 'Participant'},
179+ 'attendee': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['schedule.Attendee']"}),
180+ 'from_launchpad': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
181+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
182+ 'meeting': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['schedule.Meeting']"}),
183+ 'participation': ('django.db.models.fields.CharField', [], {'default': "'ATTENDING'", 'max_length': '32', 'null': 'True'})
184+ },
185+ 'schedule.room': {
186+ 'Meta': {'ordering': "('summit', 'name')", 'object_name': 'Room'},
187+ 'end_utc': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_column': "'end'", 'blank': 'True'}),
188+ 'has_dial_in': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
189+ 'icecast_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
190+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
191+ 'irc_channel': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
192+ 'name': ('summit.schedule.fields.NameField', [], {'max_length': '50'}),
193+ 'size': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
194+ 'start_utc': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_column': "'start'", 'blank': 'True'}),
195+ 'summit': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['schedule.Summit']"}),
196+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
197+ 'tracks': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['schedule.Track']", 'symmetrical': 'False', 'blank': 'True'}),
198+ 'type': ('django.db.models.fields.CharField', [], {'default': "u'open'", 'max_length': '7'})
199+ },
200+ 'schedule.roombusy': {
201+ 'Meta': {'ordering': "('room', 'start_utc', 'end_utc')", 'object_name': 'RoomBusy'},
202+ 'end_utc': ('django.db.models.fields.DateTimeField', [], {'db_column': "'end'"}),
203+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
204+ 'room': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'busy_set'", 'to': "orm['schedule.Room']"}),
205+ 'start_utc': ('django.db.models.fields.DateTimeField', [], {'db_column': "'start'"})
206+ },
207+ 'schedule.slot': {
208+ 'Meta': {'ordering': "('summit', 'start_utc', 'end_utc')", 'object_name': 'Slot'},
209+ 'end_utc': ('django.db.models.fields.DateTimeField', [], {'db_column': "'end'"}),
210+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
211+ 'start_utc': ('django.db.models.fields.DateTimeField', [], {'db_column': "'start'"}),
212+ 'summit': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['schedule.Summit']"}),
213+ 'type': ('django.db.models.fields.CharField', [], {'default': "u'open'", 'max_length': '7'})
214+ },
215+ 'schedule.summit': {
216+ 'Meta': {'ordering': "('name',)", 'object_name': 'Summit'},
217+ 'date_end': ('django.db.models.fields.DateField', [], {'null': 'True'}),
218+ 'date_start': ('django.db.models.fields.DateField', [], {'null': 'True'}),
219+ 'description': ('django.db.models.fields.TextField', [], {'max_length': '2047', 'blank': 'True'}),
220+ 'etherpad': ('django.db.models.fields.URLField', [], {'default': "'http://pad.ubuntu.com/'", 'max_length': '75'}),
221+ 'hashtag': ('django.db.models.fields.CharField', [], {'max_length': '25', 'blank': 'True'}),
222+ 'help_text': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
223+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
224+ 'last_update': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
225+ 'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
226+ 'managers': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'managers'", 'blank': 'True', 'to': "orm['auth.User']"}),
227+ 'name': ('summit.schedule.fields.NameField', [], {'max_length': '50'}),
228+ 'qr': ('django.db.models.fields.URLField', [], {'default': "''", 'max_length': '100', 'blank': 'True'}),
229+ 'schedulers': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'schedulers'", 'blank': 'True', 'to': "orm['auth.User']"}),
230+ 'sites': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['sites.Site']", 'symmetrical': 'False'}),
231+ 'state': ('django.db.models.fields.CharField', [], {'default': "u'sponsor'", 'max_length': '10'}),
232+ 'timezone': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
233+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '100'})
234+ },
235+ 'schedule.summitsprint': {
236+ 'Meta': {'ordering': "('summit', 'import_url')", 'object_name': 'SummitSprint'},
237+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
238+ 'import_url': ('django.db.models.fields.URLField', [], {'max_length': '200'}),
239+ 'summit': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'sprint_set'", 'to': "orm['schedule.Summit']"})
240+ },
241+ 'schedule.track': {
242+ 'Meta': {'ordering': "('summit', 'title', 'slug')", 'object_name': 'Track'},
243+ 'allow_adjacent_sessions': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
244+ 'color': ('django.db.models.fields.CharField', [], {'default': "'FFFFFF'", 'max_length': '6'}),
245+ 'description': ('django.db.models.fields.TextField', [], {'max_length': '1000', 'null': 'True'}),
246+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
247+ 'slug': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}),
248+ 'summit': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['schedule.Summit']"}),
249+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '100'})
250+ },
251+ 'sites.site': {
252+ 'Meta': {'ordering': "('domain',)", 'object_name': 'Site', 'db_table': "'django_site'"},
253+ 'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
254+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
255+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
256+ }
257+ }
258+
259+ complete_apps = ['schedule']
260
261=== modified file 'summit/schedule/models/meetingmodel.py'
262--- summit/schedule/models/meetingmodel.py 2012-05-18 00:31:38 +0000
263+++ summit/schedule/models/meetingmodel.py 2012-08-05 16:34:23 +0000
264@@ -279,11 +279,18 @@
265 # attendees.add(self.approver)
266
267 for participant in self.participant_set.all():
268- if participant.required:
269+ if participant.participation == 'REQUIRED':
270 attendees.add(participant.attendee)
271
272 return attendees
273
274+ def get_participants_by_level(self, level):
275+ attendees = set()
276+ for participant in self.participant_set.all():
277+ if participant.participation == level:
278+ attendees.add(participant.attendee)
279+ return attendees
280+
281 def update_from_launchpad(self, elem):
282 """Update from Launchpad data."""
283
284@@ -382,7 +389,10 @@
285
286
287 participant.from_launchpad = True
288- participant.required = required
289+ if required:
290+ participant.participation = 'INTERESTED'
291+ else:
292+ participant.participation = 'ATTENDING'
293 participant.save()
294 except ObjectDoesNotExist:
295 pass
296@@ -391,7 +401,7 @@
297
298 self.save()
299
300- def check_schedule(self, slot, room):
301+ def check_schedule(self, slot, room, with_interested = False):
302 """Check whether we can schedule this meeting in the given slot
303 and room."""
304 missing = set()
305@@ -455,7 +465,8 @@
306 for agenda in this_slot.agenda_set.all():
307 if agenda.meeting != self:
308 busy.update([a.pk for a in agenda.meeting.required_attendees])
309-
310+ if with_interested:
311+ busy.update([a.pk for a in agenda.meeting.get_participants_by_level('INTERESTED')])
312 slot_num = all_slots.index(this_slot)
313 for i in range(slot_num, 0, -1):
314 distance = 2 + slot_num - i
315@@ -467,13 +478,20 @@
316 if agenda.meeting != self \
317 and agenda.meeting.slots >= distance:
318 busy.update([a.pk for a in agenda.meeting.required_attendees])
319-
320+ if with_interested:
321+ busy.update([a.pk for a in agenda.meeting.get_participants_by_level('INTERESTED')])
322 # Check that all required attendees are free in this slot
323 for attendee in self.required_attendees:
324 if not attendee.available(this_slot):
325 missing.add(attendee)
326 if attendee.pk in busy:
327 missing.add(attendee)
328+ if with_interested:
329+ for attendee in self.get_participants_by_level('INTERESTED'):
330+ if not attendee.available(this_slot):
331+ missing.add(attendee)
332+ if attendee.pk in busy:
333+ missing.add(attendee)
334
335 # Check that next/previous slots are not from the same track
336 def check_not_same_track(other_slot, relative_position):
337@@ -494,7 +512,7 @@
338
339 return missing
340
341- def try_schedule(self):
342+ def try_schedule(self, with_interested = False):
343 """Try to schedule this meeting in a suitable room."""
344 if self.private:
345 print "Not scheduling private meeting: %s" % self
346@@ -507,23 +525,23 @@
347 tracks = self.tracks.all()
348 if tracks:
349 rooms = open_rooms.filter(tracks__in=tracks)
350- if self.try_schedule_into(rooms):
351+ if self.try_schedule_into(rooms, with_interested):
352 return
353
354 rooms = open_rooms.filter(tracks__isnull=True)
355 if not self.try_schedule_into(rooms):
356 print "Gave up scheduling %s" % self
357
358- def try_schedule_into(self, rooms):
359+ def try_schedule_into(self, rooms, with_interested = False):
360 """Try to schedule this meeting in one of the given rooms."""
361 today = datetime.now()
362 for room in rooms:
363 for slot in self.summit.slot_set.filter(type__exact='open',start_utc__gt=today):
364 try:
365 room.agenda_set.get(slot=slot)
366- except ObjectDoesNotExist:
367+ except room.agenda_set.model.DoesNotExist:
368 try:
369- missing = self.check_schedule(slot, room)
370+ missing = self.check_schedule(slot, room, with_interested)
371 if len(missing):
372 raise Meeting.SchedulingError("Required people not available: %s"
373 % ', '.join(m.user.username for m in missing))
374
375=== modified file 'summit/schedule/models/participantmodel.py'
376--- summit/schedule/models/participantmodel.py 2012-01-23 01:18:55 +0000
377+++ summit/schedule/models/participantmodel.py 2012-08-05 16:34:23 +0000
378@@ -42,17 +42,24 @@
379
380
381 class Participant(models.Model):
382+
383+ PARTICIPATION_CHOICES = (
384+ (u'ATTENDING', u'Attending'),
385+ (u'INTERESTED', u'Very interested in attending'),
386+ (u'REQUIRED', u'Required'),
387+ )
388+
389 meeting = models.ForeignKey(Meeting)
390 attendee = models.ForeignKey(Attendee,
391 help_text=HELP_TEXT["attendee"])
392- required = models.BooleanField(default=False,
393- help_text=HELP_TEXT["required"])
394+ participation = models.CharField(max_length=32, choices=PARTICIPATION_CHOICES,
395+ null=True, blank=False, default='ATTENDING')
396 from_launchpad = models.BooleanField(default=False,
397 help_text=HELP_TEXT["from_launchpad"])
398
399 class Meta:
400 app_label = 'schedule'
401- ordering = ('meeting', 'attendee', 'required')
402+ ordering = ('meeting', 'attendee', 'participation')
403
404 def __unicode__(self):
405 return self.attendee.user.username
406
407=== modified file 'summit/schedule/models/summitmodel.py'
408--- summit/schedule/models/summitmodel.py 2012-06-11 15:00:59 +0000
409+++ summit/schedule/models/summitmodel.py 2012-08-05 16:34:23 +0000
410@@ -326,9 +326,13 @@
411 This is a pretty simple best-fit/first-come-first-served scheduler,
412 but it suffices.
413 """
414- for meeting in self.meeting_set.filter(approved='APPROVED', agenda__isnull=True):
415+ for meeting in self.meeting_set.filter(approved='APPROVED', agenda__isnull=True).order_by('id'):
416+ meeting.try_schedule(with_interested = True)
417+
418+ for meeting in self.meeting_set.filter(approved='APPROVED', agenda__isnull=True).order_by('id'):
419 meeting.try_schedule()
420
421+
422 def check_schedule(self):
423 """Check the schedule for existant errors."""
424 for meeting in self.meeting_set.all():
425
426=== modified file 'summit/schedule/templates/schedule/attend.html'
427--- summit/schedule/templates/schedule/attend.html 2012-05-06 00:02:48 +0000
428+++ summit/schedule/templates/schedule/attend.html 2012-08-05 16:34:23 +0000
429@@ -28,9 +28,9 @@
430
431 <form action="{{ request.path_info }}" method="POST">
432 <fieldset>
433- <h3>Attend "{{ meeting }}"</h3>
434+ <h3>Attend "{{ meeting.title }}"</h3>
435 {{ form.as_template }}
436- <p id="twitter_update_list" style="color: black;">Check 'Required' if your participation in this topic is essential. This will cause the meeting scheduler to try to ensure that you are able to attend this meeting. If your participation is not required, just click Save.</p>
437+ <p id="twitter_update_list" style="color: black;">If you feel that your participation is required for this topic, please contact {{ meeting.drafter.name }} or {{ meeting.approver.name }}, otherwise, please select one of the options above.</p>
438 </fieldset>
439 {% if is_popup %}<input type="hidden" name="_popup" value="1">{% endif %}
440 <input type="submit" name="submit" value="Save" class="submit-button" />
441
442=== added file 'summit/schedule/templates/schedule/attendees.html'
443--- summit/schedule/templates/schedule/attendees.html 1970-01-01 00:00:00 +0000
444+++ summit/schedule/templates/schedule/attendees.html 2012-08-05 16:34:23 +0000
445@@ -0,0 +1,96 @@
446+{% extends "base.html" %}
447+
448+{% block page_name %}
449+Review Attendees - {{ summit.title }}
450+{% endblock %}
451+
452+{% block extrahead %}
453+{{ block.super }}
454+<style>
455+TABLE img.icon {
456+ vertical-align: bottom;
457+ width: 16px;
458+ height: 16px;
459+}
460+
461+table.basic span.main-agenda-item-tracks {
462+ font-size: 0.8em;
463+}
464+
465+div.agenda-details {
466+ display: none;
467+ padding: 5px;
468+ position: absolute;
469+ margin-left: 20px;
470+ font-size: 0.9em;
471+ color: #FFFFFF;
472+ background-color: #101010;
473+ border: #000000 1px solid;
474+ border-radius: 5px;
475+ min-width: 200px;
476+ max-width: 500px;
477+ z-index: 100;
478+}
479+
480+div.meeting-description {
481+ white-space: pre-wrap;
482+}
483+
484+div.agenda-details LI {
485+ font-size: 1.0em;
486+}
487+
488+div.schedule-head {
489+ position: relative;
490+ overflow: visible;
491+ z-index: 10;
492+}
493+
494+div.schedule-head .schedule-qrcode {
495+ height: 63px;
496+ vertical-align: top;
497+ margin-left: -3px;
498+ margin-top: -3px;
499+}
500+
501+div.schedule-head .schedule-date {
502+ font-weight: normal;
503+ font-size: 36px;
504+ margin-left: 5px;
505+}
506+
507+div.schedule-head .schedule-crew, .last-updated {
508+ font-size: 0.7pc;
509+}
510+
511+</style>
512+{% endblock %}
513+
514+{% block sub_nav_links %}
515+<li><a class="sub-nav-item" href="{% url summit.schedule.views.meeting summit.name, meeting.id, meeting.name|default:'-' %}">Meeting</a></li>
516+{% endblock %}
517+
518+{% block content %}
519+<div class="row">
520+<article class="span-12">
521+
522+<div class="schedule-head">
523+ <h2>{{ meeting.title }} Attendees</h2>
524+</div>
525+<table class="basic">
526+{% for participant in participants %}
527+<tr>
528+<td width="66%">
529+ <a href="http://launchpad.net/~{{ participant.attendee.user.username }}">
530+ {{ participant.attendee.name }}
531+ </a>
532+</td>
533+<td width="34%">
534+<a href="{% url summit.schedule.views.organizer_edit_attendees meeting.summit.name meeting.id participant.attendee.user.username %}">Status: {{ participant.get_participation_display }}</a>
535+</td>
536+</tr>
537+{% endfor %}
538+</table>
539+</article>
540+</div>
541+{% endblock %}
542
543=== modified file 'summit/schedule/templates/schedule/meeting.html'
544--- summit/schedule/templates/schedule/meeting.html 2012-06-10 20:34:50 +0000
545+++ summit/schedule/templates/schedule/meeting.html 2012-08-05 16:34:23 +0000
546@@ -37,10 +37,18 @@
547 {% else %}
548 {% if drafter %}
549 <li><a class="sub-nav-item" href="{% url summit.schedule.views.edit_meeting summit.name, meeting.id, meeting.name|default:'-' %}">Edit meeting</a></li>
550+ <li><a class="sub-nav-item" href="{% url summit.schedule.views.attendee_review summit.name, meeting.id, meeting.name|default:'-' %}">Review attendees</a></li>
551 {% endif %}
552 {% endif %}
553 {% endif %}
554-
555+ {% if summit_organizer %}
556+ <li><a class="sub-nav-item" href="{% url summit.schedule.views.attendee_review summit.name, meeting.id, meeting.name|default:'-' %}">Review attendees</a></li>
557+ {% else %}
558+ {% if drafter %}
559+ <li><a class="sub-nav-item" href="{% url summit.schedule.views.attendee_review summit.name, meeting.id, meeting.name|default:'-' %}">Review attendees</a></li>
560+ {% endif %}
561+ {% endif %}
562+
563 {% if user_is_attending %}
564 {% if user_is_participating %}
565 <li><a class="sub-nav-item" href="{% url summit.schedule.views.unregister meeting.summit.name, meeting.id, meeting.name|default:'-' %}">Skip this meeting</a></li>
566@@ -110,7 +118,7 @@
567 <div id="Attendees" class="span-5">
568 <h3>Attendees</h3>
569 {% for attendee in attendees %}
570- <a href="http://launchpad.net/~{{ attendee.user.username }}">{% if attendee.required %}<strong>{% endif %}{{ attendee.name }}{% if attendee.required %}</strong>{% endif %}</a>{% if not forloop.last %}, {% endif %}
571+ <a href="http://launchpad.net/~{{ attendee.user.username }}">{% if attendee.participation = 'REQUIRED' %}<strong>{% endif %}{{ attendee.name }}{% if attendee.required %}</strong>{% endif %}</a>{% if not forloop.last %}, {% endif %}
572 {% endfor %}
573 </div>
574 {% endifnotequal %}
575
576=== added file 'summit/schedule/templates/schedule/org_attend.html'
577--- summit/schedule/templates/schedule/org_attend.html 1970-01-01 00:00:00 +0000
578+++ summit/schedule/templates/schedule/org_attend.html 2012-08-05 16:34:23 +0000
579@@ -0,0 +1,39 @@
580+{% extends "base.html" %}
581+
582+{% block page_name %}Change {{ attendee.name }}'s attendance to {{ meeting }} - {{ summit.title }}{%endblock %}
583+{% block sub_nav %}{% endblock %}
584+
585+{% block extrahead %}{{ block.super }}
586+ <script type="text/javascript" src="{{MEDIA_URL}}js/colortip-1.0-jquery.js"></script>
587+ <link rel="stylesheet" type="text/css" href="{{MEDIA_URL}}css/colortip-1.0-jquery.css"/>
588+{% endblock %}
589+
590+{% block closure %}
591+<script type="text/javascript"><!--
592+$(document).ready(function(){
593+ $('span[rel*=help]').colorTip({color:'orange'});
594+});
595+--></script>
596+{% endblock %}
597+
598+
599+{% block content %}
600+<div class="row">
601+ <article id="form" class="span-8">
602+ {% if form.errors %}
603+ <p style="color: red;">
604+ Please correct the error{{ form.errors|pluralize }} below.
605+ </p>
606+ {% endif %}
607+
608+ <form action="{{ request.path_info }}" method="POST">
609+ <fieldset>
610+ <h3>Change {{ attendee.name }}'s attendance to "{{ meeting.title }}"</h3>
611+ {{ form.as_template }}
612+ </fieldset>
613+ {% if is_popup %}<input type="hidden" name="_popup" value="1">{% endif %}
614+ <input type="submit" name="submit" value="Save" class="submit-button" />
615+ </form>
616+ </article>
617+</div>
618+{% endblock %}
619
620=== modified file 'summit/schedule/tests.py'
621--- summit/schedule/tests.py 2012-07-31 23:30:11 +0000
622+++ summit/schedule/tests.py 2012-08-05 16:34:23 +0000
623@@ -136,8 +136,8 @@
624 meeting2 = factory.make_one(Meeting, summit=slot.summit, requires_dial_in=False, private=False)
625
626 attendee = factory.make_one(Attendee)
627- factory.make_one(Participant, meeting=meeting1, attendee=attendee, required=True)
628- factory.make_one(Participant, meeting=meeting2, attendee=attendee, required=True)
629+ factory.make_one(Participant, meeting=meeting1, attendee=attendee, participation = 'REQUIRED')
630+ factory.make_one(Participant, meeting=meeting2, attendee=attendee, participation = 'REQUIRED')
631
632 factory.make_one(Agenda, meeting=meeting1, slot=slot, room=room1)
633 agenda = factory.make_one(Agenda, meeting=meeting2, slot=slot, room=room2)
634@@ -387,7 +387,7 @@
635 meeting.update_from_launchpad(elem)
636 participant = meeting.participant_set.get()
637 self.assertEqual(attendee, participant.attendee)
638- self.assertEqual(False, participant.required)
639+ self.assertEqual('ATTENDING', participant.participation)
640
641 def test_update_from_launchpad_sets_participant_essential(self):
642 summit, attendee = self.make_summit_and_attendee()
643@@ -395,7 +395,7 @@
644 elem = self.get_person_node(attendee.user.username, required=True)
645 meeting.update_from_launchpad(elem)
646 participant = meeting.participant_set.get()
647- self.assertEqual(True, participant.required)
648+ self.assertEqual('INTERESTED', participant.participation)
649
650 def test_update_from_launchpad_sets_from_launchpad(self):
651 summit, attendee = self.make_summit_and_attendee()
652@@ -605,7 +605,7 @@
653 self.assertContains(response, 'Skip this meeting', 0)
654
655 def test_skip_link(self):
656- self.meeting.participant_set.create(attendee=self.attendee, required=False, from_launchpad=False)
657+ self.meeting.participant_set.create(attendee=self.attendee, participation = 'ATTENDING', from_launchpad=False)
658 self.assertEquals(1, Participant.objects.filter(attendee=self.attendee).count())
659 self.login()
660 response = self.client.get(reverse('summit.schedule.views.meeting', args=('uds-test', self.meeting.id, 'meeting1')))
661@@ -620,7 +620,7 @@
662 self.assertEquals(1, Participant.objects.filter(attendee=self.attendee).count())
663
664 def test_delete_participation(self):
665- self.meeting.participant_set.create(attendee=self.attendee, required=False, from_launchpad=False)
666+ self.meeting.participant_set.create(attendee=self.attendee, participation = 'ATTENDING', from_launchpad=False)
667 self.assertEquals(1, Participant.objects.filter(attendee=self.attendee).count())
668 self.login()
669 response = self.client.get(reverse('summit.schedule.views.unregister', args=('uds-test', self.meeting.id, 'meeting1')))
670@@ -856,7 +856,7 @@
671 participant1 = Participant.objects.create(
672 meeting=self.meeting1,
673 attendee=self.attendee,
674- required=True
675+ participation = 'REQUIRED'
676 )
677 missing = self.meeting1.check_schedule(self.slot, self.room1)
678 self.assertEquals(len(missing), 0)
679@@ -864,11 +864,127 @@
680 participant1 = Participant.objects.create(
681 meeting=self.meeting2,
682 attendee=self.attendee,
683- required=True
684+ participation = 'REQUIRED'
685 )
686 missing = self.meeting2.check_schedule(self.slot, self.room2)
687 self.assertEquals(len(missing), 1)
688
689+class AutoSchedulerTestCase(djangotest.TestCase):
690+ def setUp(self):
691+ now = datetime.datetime.utcnow()
692+ one_hour = datetime.timedelta(0, 3600)
693+ week = datetime.timedelta(days=5)
694+ self.summit = factory.make_one(Summit, name='uds-test')
695+ self.slot = factory.make_one(
696+ Slot,
697+ start_utc=now+week,
698+ end_utc=now+week+one_hour,
699+ type='open',
700+ summit=self.summit)
701+
702+ self.room1 = factory.make_one(Room, summit=self.summit, type='open')
703+ self.meeting1 = factory.make_one(Meeting, summit=self.summit, name='meeting1', requires_dial_in=False, approved='APPROVED', private=False)
704+ self.agenda1 = factory.make_one(Agenda, slot=self.slot, meeting=self.meeting1, room=self.room1)
705+
706+ self.room2 = factory.make_one(Room, summit=self.summit, type='open')
707+ self.meeting2 = factory.make_one(Meeting, summit=self.summit, name='meeting2', requires_dial_in=False, approved='APPROVED', private=False)
708+
709+ self.user = factory.make_one(User, username='testuser', first_name='Test', last_name='User')
710+ self.attendee = factory.make_one(Attendee, summit=self.summit, user=self.user, start_utc=now, end_utc=now+week)
711+
712+ def tearDown(self):
713+ pass
714+
715+ def run_autoschedule(self):
716+ from django.core.management import execute_from_command_line
717+ execute_from_command_line(argv=['manage.py', 'autoschedule', 'uds-test', '-v', '2'])
718+
719+ def run_reschedule(self):
720+ from django.core.management import execute_from_command_line
721+ execute_from_command_line(argv=['manage.py', 'reschedule', 'uds-test', '-v', '2'])
722+
723+ def test_required_conflict(self):
724+ participant1 = Participant.objects.create(
725+ meeting=self.meeting1,
726+ attendee=self.attendee,
727+ participation = 'REQUIRED'
728+ )
729+
730+ participant2 = Participant.objects.create(
731+ meeting=self.meeting2,
732+ attendee=self.attendee,
733+ participation = 'REQUIRED'
734+ )
735+
736+ self.run_autoschedule()
737+
738+ self.assertEquals(1, self.meeting1.agenda_set.all().count())
739+ self.assertEquals(0, self.meeting2.agenda_set.all().count())
740+
741+ def test_required_and_interested(self):
742+ participant1 = Participant.objects.create(
743+ meeting=self.meeting1,
744+ attendee=self.attendee,
745+ participation = 'REQUIRED'
746+ )
747+
748+ participant2 = Participant.objects.create(
749+ meeting=self.meeting2,
750+ attendee=self.attendee,
751+ participation = 'INTERESTED'
752+ )
753+
754+ self.run_autoschedule()
755+
756+ self.assertEquals(1, self.meeting1.agenda_set.all().count())
757+ self.assertEquals(1, self.meeting2.agenda_set.all().count())
758+
759+ def test_interested_and_required(self):
760+ participant1 = Participant.objects.create(
761+ meeting=self.meeting1,
762+ attendee=self.attendee,
763+ participation = 'REQUIRED'
764+ )
765+
766+ participant2 = Participant.objects.create(
767+ meeting=self.meeting2,
768+ attendee=self.attendee,
769+ participation = 'INTERESTED'
770+ )
771+
772+ self.run_autoschedule()
773+
774+ self.assertEquals(1, self.meeting1.agenda_set.all().count())
775+ self.assertEquals(1, self.meeting2.agenda_set.all().count())
776+
777+ participant2.participation = 'REQUIRED'
778+ participant2.save()
779+
780+ self.run_reschedule()
781+
782+ self.assertEquals(1, self.meeting1.agenda_set.all().count())
783+ self.assertEquals(0, self.meeting2.agenda_set.all().count())
784+
785+ def test_get_participants_by_level(self):
786+ participant1 = Participant.objects.create(
787+ meeting=self.meeting1,
788+ attendee=self.attendee,
789+ participation = 'REQUIRED'
790+ )
791+
792+ participant2 = Participant.objects.create(
793+ meeting=self.meeting2,
794+ attendee=self.attendee,
795+ participation = 'INTERESTED'
796+ )
797+
798+ self.assertEquals(1, len(self.meeting1.get_participants_by_level('REQUIRED')))
799+ self.assertEquals(0, len(self.meeting1.get_participants_by_level('INTERESTED')))
800+ self.assertEquals(0, len(self.meeting1.get_participants_by_level('ATTENDING')))
801+
802+ self.assertEquals(0, len(self.meeting2.get_participants_by_level('REQUIRED')))
803+ self.assertEquals(1, len(self.meeting2.get_participants_by_level('INTERESTED')))
804+ self.assertEquals(0, len(self.meeting2.get_participants_by_level('ATTENDING')))
805
806 class PrivateSchedulingTestCase(djangotest.TestCase):
807
808@@ -953,8 +1069,8 @@
809 def test_required_participant_in_private_meeting(self):
810 '''Make sure meetings aren't scheduled when someone is in a private meeting.'''
811 # Make the same person required for both meetings
812- self.public_meeting.participant_set.create(attendee=self.attendee, required=True)
813- self.private_meeting.participant_set.create(attendee=self.attendee, required=True)
814+ self.public_meeting.participant_set.create(attendee=self.attendee, participation = 'REQUIRED')
815+ self.private_meeting.participant_set.create(attendee=self.attendee, participation = 'REQUIRED')
816 # Schedule the private meeting in the one available slot
817 self.private_room.agenda_set.create(slot=self.slot, meeting=self.private_meeting)
818
819@@ -1668,8 +1784,8 @@
820 slot = self.make_one_future_slot(summit=summit)
821 attendee = factory.make_one(Attendee, summit=summit,
822 start_utc=slot.start_utc, end_utc=slot.end_utc)
823- meeting1.participant_set.create(attendee=attendee, required=True)
824- meeting2.participant_set.create(attendee=attendee, required=True)
825+ meeting1.participant_set.create(attendee=attendee, participation = 'REQUIRED')
826+ meeting2.participant_set.create(attendee=attendee, participation = 'REQUIRED')
827 factory.make_one(Agenda, meeting=meeting1, room=room1, slot=slot,
828 auto=True)
829 factory.make_one(Agenda, meeting=meeting2, room=room2, slot=slot,
830@@ -1686,7 +1802,7 @@
831 attendee = factory.make_one(Attendee, summit=summit,
832 start_utc=slot.end_utc,
833 end_utc=slot.end_utc+datetime.timedelta(hours=1))
834- meeting.participant_set.create(attendee=attendee, required=True)
835+ meeting.participant_set.create(attendee=attendee, participation = 'REQUIRED')
836 factory.make_one(Agenda, meeting=meeting, room=room, slot=slot,
837 auto=True)
838 summit.reschedule()
839
840=== modified file 'summit/schedule/views.py'
841--- summit/schedule/views.py 2012-05-18 00:31:53 +0000
842+++ summit/schedule/views.py 2012-08-05 16:34:23 +0000
843@@ -375,7 +375,7 @@
844 meeting = get_object_or_404(summit.meeting_set, id=meeting_id)
845 user_is_attending = attendee is not None and attendee in meeting.attendees
846 if not user_is_attending:
847- meeting.participant_set.create(attendee=attendee, required=False, from_launchpad=False)
848+ meeting.participant_set.create(attendee=attendee, participation = 'ATTENDING', from_launchpad=False)
849
850 return HttpResponseRedirect(reverse('summit.schedule.views.meeting', args=(summit.name, meeting.id, meeting_slug)))
851
852@@ -677,7 +677,59 @@
853 context = {
854 'summit': summit,
855 'form': form,
856- 'meeting': meeting.title,
857+ 'meeting.title': meeting.title,
858+ 'meeting': meeting,
859 }
860 return render_to_response('schedule/attend.html',
861 context, RequestContext(request))
862+
863+@summit_required
864+def attendee_review(request, summit, attendee, meeting_slug, meeting_id):
865+ meeting = get_object_or_404(summit.meeting_set, id=meeting_id)
866+
867+ if not summit.is_organizer(attendee) and attendee != meeting.drafter:
868+ return HttpResponseRedirect(reverse('summit.schedule.views.summit', args=(summit.name,)))
869+ else:
870+ participants=meeting.participant_set.all()
871+
872+ context = {
873+ 'summit': summit,
874+ 'meeting':meeting,
875+ 'participants':participants,
876+ 'summit_organizer': summit.is_organizer(attendee),
877+
878+ }
879+ return render_to_response("schedule/attendees.html", context,
880+ context_instance=RequestContext(request))
881+
882+@summit_attendee_required
883+def organizer_edit_attendees(request, summit, logged_in_user, username, meeting_id):
884+ meeting = get_object_or_404(summit.meeting_set, id=meeting_id)
885+ attendee = get_object_or_404(Attendee, summit=summit, user__username=username)
886+ meeting_slug = get_object_or_404(Meeting, summit=summit, id=meeting_id)
887+
888+ if not summit.is_organizer(logged_in_user) and logged_in_user != meeting.drafter:
889+ return HttpResponseRedirect(reverse('summit.schedule.views.summit', args=(summit.name,)))
890+ else:
891+ try:
892+ participant = Participant.objects.get(attendee=attendee, meeting=meeting)
893+ except Participant.DoesNotExist:
894+ participant = Participant(attendee=attendee, meeting=meeting)
895+
896+ if request.method == 'POST':
897+ form = OrganizerChangeAttend(data=request.POST, instance=participant)
898+ if form.is_valid():
899+ form.save()
900+ return HttpResponseRedirect(reverse('summit.schedule.views.attendee_review', args=(summit.name, meeting.id, meeting.name)))
901+ else:
902+ form = OrganizerChangeAttend(instance=participant)
903+
904+ context = {
905+ 'summit': summit,
906+ 'form': form,
907+ 'meeting.title': meeting.title,
908+ 'meeting': meeting,
909+ 'attendee': attendee,
910+ }
911+ return render_to_response('schedule/org_attend.html',
912+ context, RequestContext(request))
913
914=== modified file 'summit/urls.py'
915--- summit/urls.py 2012-05-06 00:02:48 +0000
916+++ summit/urls.py 2012-08-05 16:34:23 +0000
917@@ -72,8 +72,10 @@
918 (r'^(?P<summit_name>[\w-]+)/review/$', 'review_pending'),
919 (r'^(?P<summit_name>[\w-]+)/review_meeting/(?P<meeting_id>\d+)/$', 'meeting_review'),
920 (r'^(?P<summit_name>[\w-]+)/attend_meeting/(?P<meeting_id>\d+)/$', 'attend_meeting'),
921+ (r'^(?P<summit_name>[\w-]+)/edit_attendee/(?P<meeting_id>\d+)/(?P<username>[%+\.\w-]+)/$', 'organizer_edit_attendees'),
922 (r'^(?P<summit_name>[\w-]+)/create_meeting/$', 'create_meeting'),
923 (r'^(?P<summit_name>[\w-]+)/edit_mtg/(?P<meeting_id>\d+)/(?P<meeting_slug>[%+\.\w-]+)/$', 'organizer_edit_meeting'),
924+ (r'^(?P<summit_name>[\w-]+)/attendee_review/(?P<meeting_id>\d+)/(?P<meeting_slug>[%+\.\w-]+)/$', 'attendee_review'),
925 (r'^(?P<summit_name>[\w-]+)/tracks$', 'tracks'),
926 (r'^(?P<summit_name>[\w-]+)/next$', 'next_session'),
927 (r'^(?P<summit_name>[\w-]+)/(?P<username>[%+\.\w-]+)/meetings$', 'created_meetings'),

Subscribers

People subscribed via source and target branches