Merge lp:~cjohnston/summit/participation-changes into lp:summit
- participation-changes
- Merge into trunk
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Summit Hackers | Pending | ||
Review via email: mp+114998@code.launchpad.net |
Commit message
Description of the change
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'), |