Merge lp:~cjohnston/summit/new-meeting-stuff into lp:summit
- new-meeting-stuff
- Merge into trunk
Status: | Superseded |
---|---|
Proposed branch: | lp:~cjohnston/summit/new-meeting-stuff |
Merge into: | lp:summit |
Diff against target: |
810 lines (+644/-7) 12 files modified
summit/schedule/admin/meetingadmin.py (+1/-1) summit/schedule/autoslug.py (+79/-0) summit/schedule/forms.py (+76/-3) summit/schedule/migrations/0017_add_review_status.py (+204/-0) summit/schedule/models/meetingmodel.py (+15/-1) summit/schedule/templates/schedule/edit_meeting.html (+45/-0) summit/schedule/templates/schedule/meeting.html (+2/-0) summit/schedule/templates/schedule/propose_meeting.html (+45/-0) summit/schedule/templates/schedule/review.html (+117/-0) summit/schedule/templates/schedule/summit.html (+1/-0) summit/schedule/views.py (+56/-2) summit/urls.py (+3/-0) |
To merge this branch: | bzr merge lp:~cjohnston/summit/new-meeting-stuff |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Summit Hackers | Pending | ||
Review via email: mp+95455@code.launchpad.net |
This proposal has been superseded by a proposal from 2012-03-06.
Commit message
Automagically creates meeting.name based upon meeting.title, sets the meeting to approved so that it doesn't appear in the needs approved category, and allows assigning tracks when creating the meeting.
Description of the change
- 297. By Chris Johnston
-
[r=mhall119] Begins adding docs on using summit
- 298. By Chris Johnston
-
[r=mhall119] Fixes tests for etherpad changes
- 299. By Chris Johnston
-
[r=mhall119] Updates import_live_data to import users and attendees from production
- 300. By Chris Johnston
-
[r=mhall119] Removes QR img if summit.qr does not exist. Removes QR code on mobile devices
- 301. By Chris Johnston
-
[r=mhall119] Adds IRC Channel field to roommodel, adds chat icon to agenda page when there is an IRC Channel defined.
- 302. By Chris Johnston
-
[r=mhall119] Changes from room.name to room.title on the meeting page for better readability.
- 303. By Chris Johnston
-
Fix conflict from production
- 304. By Chris Johnston
-
[r=mhall119] Adds django sites to summit to allow for multiple different unique sites
- 305. By Chris Johnston
-
Version 1.0.6 Release
- 306. By Chris Johnston
-
[r=mhall119] Reverses the order of the past summits so that newest appears first.
- 307. By Chris Johnston
-
[r=mhall119] This adds theme support to Summit to allow LC and UDS to have separate themes, as well as add a menu app that will allow each site to have it's own menu without having to mess with templates.
- 308. By Chris Johnston
-
Version 1.0.7 release
- 309. By Chris Johnston
-
[r=mhall119] Fixes errors created in a previous merge that I failed to catch before pushing.
- 310. By Chris Johnston
-
Updating docs to reflect sites
- 311. By Chris Johnston
-
Fixes most theme issues
- 312. By Chris Johnston
-
Resolve conflicts
Unmerged revisions
Preview Diff
1 | === modified file 'summit/schedule/admin/meetingadmin.py' |
2 | --- summit/schedule/admin/meetingadmin.py 2012-02-06 04:57:02 +0000 |
3 | +++ summit/schedule/admin/meetingadmin.py 2012-03-02 03:37:19 +0000 |
4 | @@ -140,7 +140,7 @@ |
5 | |
6 | fieldsets = ( |
7 | (None, { |
8 | - 'fields': ('summit', 'name', 'title', 'description', |
9 | + 'fields': ('summit', 'name', 'title', 'description', 'approved', |
10 | 'type', 'tracks', 'topics', 'requires_dial_in', 'video'), |
11 | }), |
12 | ("References", { |
13 | |
14 | === added file 'summit/schedule/autoslug.py' |
15 | --- summit/schedule/autoslug.py 1970-01-01 00:00:00 +0000 |
16 | +++ summit/schedule/autoslug.py 2012-03-02 03:37:19 +0000 |
17 | @@ -0,0 +1,79 @@ |
18 | +from django.template.defaultfilters import slugify |
19 | + |
20 | +class AutoSlugMixin(object): |
21 | + """ |
22 | + Automatically set slug to slugified version of the name if left empty. |
23 | + Use this as follows:: |
24 | + |
25 | + class MyModel(AutoSlugMixin, models.Model): |
26 | + def save(self): |
27 | + super(MyModel, self).save() |
28 | + |
29 | + self.update_slug() |
30 | + |
31 | + The name of the slug field and the field to populate from can be set |
32 | + using the `_slug_from` and `_slug_field` properties. |
33 | + |
34 | + The big advantage of this method of setting slugs over others |
35 | + (ie. django-autoslug) is that we can set the value of slugs |
36 | + automatically based on the value of a field of an a field with a foreign |
37 | + key relation. For example:: |
38 | + |
39 | + class MyModel(AutoSlugMixin, models.Model): |
40 | + slug = models.SlugField() |
41 | + |
42 | + def generate_slug(self): |
43 | + qs = self.mymodeltranslation_set.all()[:1] |
44 | + if qs.exists(): |
45 | + return qs[0].name |
46 | + else: |
47 | + return '' |
48 | + |
49 | + class MyModelTranslation(models.Model): |
50 | + parent = models.ForeignKey(MyModel) |
51 | + name = models.CharField() |
52 | + |
53 | + def save(self): |
54 | + super(MyModel, self).save() |
55 | + |
56 | + self.parent.update_slug() |
57 | + |
58 | + (The code above is untested and _might_ be buggy.) |
59 | + |
60 | + """ |
61 | + _slug_from = 'title' |
62 | + _slug_field = 'name' |
63 | + |
64 | + def slugify(self, name): |
65 | + return slugify(name) |
66 | + |
67 | + def generate_slug(self): |
68 | + name = getattr(self, self._slug_from) |
69 | + return self.slugify(name) |
70 | + |
71 | + def update_slug(self, commit=True): |
72 | + if not getattr(self, self._slug_field) and \ |
73 | + getattr(self, self._slug_from): |
74 | + setattr(self, self._slug_field, self.generate_slug()) |
75 | + |
76 | + if commit: |
77 | + self.save() |
78 | + |
79 | + |
80 | +class AutoUniqueSlugMixin(AutoSlugMixin): |
81 | + """ Make sure that the generated slug is unique. """ |
82 | + |
83 | + def is_unique_slug(self, slug): |
84 | + qs = self.__class__.objects.filter(**{self._slug_field: slug}) |
85 | + return not qs.exists() |
86 | + |
87 | + def generate_slug(self): |
88 | + original_slug = super(AutoUniqueSlugMixin, self).generate_slug() |
89 | + slug = original_slug |
90 | + |
91 | + iteration = 1 |
92 | + while not self.is_unique_slug(slug): |
93 | + slug = "%s-%d" % (original_slug, iteration) |
94 | + iteration += 1 |
95 | + |
96 | + return slug |
97 | |
98 | === modified file 'summit/schedule/forms.py' |
99 | --- summit/schedule/forms.py 2012-01-25 21:20:09 +0000 |
100 | +++ summit/schedule/forms.py 2012-03-02 03:37:19 +0000 |
101 | @@ -37,6 +37,67 @@ |
102 | obj.user.first_name, obj.user.last_name, obj.user.username) |
103 | |
104 | |
105 | +class MeetingFormBase(forms.ModelForm): |
106 | + participants = MultipleAttendeeField( |
107 | + queryset=Attendee.objects.all, |
108 | + widget=forms.CheckboxSelectMultiple, |
109 | + label='Participants', |
110 | + required=False) |
111 | + |
112 | + class Media: |
113 | + css = {'all': ( |
114 | + '/media/css/colortip-1.0-jquery.css', |
115 | + )} |
116 | + js = ( |
117 | + '/media/js/colortip-1.0-jquery.js', |
118 | + ) |
119 | + |
120 | + def __init__(self, *args, **kwargs): |
121 | + if 'instance' in kwargs: |
122 | + if kwargs['instance'].pk is not None: |
123 | + # We get the 'initial' keyword argument or initialize it |
124 | + # as a dict if it didn't exist. |
125 | + initial = kwargs.setdefault('initial', {}) |
126 | + # The widget for a ModelMultipleChoiceField expects |
127 | + # a list of primary key for the selected data. |
128 | + initial['participants'] = [ |
129 | + attendee.pk |
130 | + for attendee in kwargs['instance'].participants.all()] |
131 | + |
132 | + super(MeetingFormBase, self).__init__(*args, **kwargs) |
133 | + |
134 | + summit = self.instance.summit |
135 | + self.fields['participants'].queryset = Attendee.objects.filter( |
136 | + summit=summit).order_by('user__first_name', |
137 | + 'user__last_name', |
138 | + 'user__username') |
139 | + |
140 | + def save(self, commit=True): |
141 | + instance = super(MeetingFormBase, self).save(commit) |
142 | + |
143 | + # Prepare a 'save_m2m' method for the form, |
144 | + if 'save_m2m' in dir(self): |
145 | + old_save_m2m = self.save_m2m |
146 | + else: |
147 | + old_save_m2m = None |
148 | + def save_m2m(): |
149 | + if old_save_m2m is not None: |
150 | + old_save_m2m() |
151 | + # This is where we actually link the pizza with toppings |
152 | + instance.participants.clear() |
153 | + for participant in self.cleaned_data['participants']: |
154 | + record = Participant(meeting=instance, attendee=participant, |
155 | + required=True) |
156 | + record.save() |
157 | + self.save_m2m = save_m2m |
158 | + |
159 | + # Do we need to save all changes now? |
160 | + if commit: |
161 | + instance.save() |
162 | + self.save_m2m() |
163 | + |
164 | + return instance |
165 | + |
166 | class PrivateMeetingFormBase(forms.ModelForm): |
167 | participants = MultipleAttendeeField( |
168 | queryset=Attendee.objects.all, |
169 | @@ -98,16 +159,28 @@ |
170 | |
171 | return instance |
172 | |
173 | - |
174 | class CreatePrivateMeeting(PrivateMeetingFormBase, RenderableMixin): |
175 | class Meta: |
176 | model = Meeting |
177 | - fields = ('name', 'title', 'description', 'spec_url', 'wiki_url', |
178 | + fields = ('title', 'description', 'tracks', 'spec_url', 'wiki_url', |
179 | 'pad_url', 'requires_dial_in') |
180 | |
181 | |
182 | class EditPrivateMeeting(PrivateMeetingFormBase, RenderableMixin): |
183 | class Meta: |
184 | model = Meeting |
185 | - fields = ('title', 'description', 'spec_url', 'wiki_url', 'pad_url', |
186 | + fields = ('title', 'description', 'tracks', 'spec_url', 'wiki_url', 'pad_url', |
187 | + 'requires_dial_in') |
188 | + |
189 | +class ProposeMeeting(PrivateMeetingFormBase, RenderableMixin): |
190 | + class Meta: |
191 | + model = Meeting |
192 | + fields = ('title', 'description', 'tracks', 'spec_url', 'wiki_url', |
193 | + 'pad_url', 'requires_dial_in') |
194 | + |
195 | + |
196 | +class EditMeeting(PrivateMeetingFormBase, RenderableMixin): |
197 | + class Meta: |
198 | + model = Meeting |
199 | + fields = ('title', 'description', 'tracks', 'spec_url', 'wiki_url', 'pad_url', |
200 | 'requires_dial_in') |
201 | |
202 | === added file 'summit/schedule/migrations/0017_add_review_status.py' |
203 | --- summit/schedule/migrations/0017_add_review_status.py 1970-01-01 00:00:00 +0000 |
204 | +++ summit/schedule/migrations/0017_add_review_status.py 2012-03-02 03:37:19 +0000 |
205 | @@ -0,0 +1,204 @@ |
206 | +# encoding: utf-8 |
207 | +import datetime |
208 | +from south.db import db |
209 | +from south.v2 import SchemaMigration |
210 | +from django.db import models |
211 | + |
212 | +class Migration(SchemaMigration): |
213 | + |
214 | + def forwards(self, orm): |
215 | + |
216 | + # Adding field 'Meeting.approved' |
217 | + db.add_column('schedule_meeting', 'approved', self.gf('django.db.models.fields.CharField')(default='PENDING', max_length=10, null=True), keep_default=False) |
218 | + |
219 | + |
220 | + def backwards(self, orm): |
221 | + |
222 | + # Deleting field 'Meeting.approved' |
223 | + db.delete_column('schedule_meeting', 'approved') |
224 | + |
225 | + |
226 | + models = { |
227 | + 'auth.group': { |
228 | + 'Meta': {'object_name': 'Group'}, |
229 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
230 | + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), |
231 | + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) |
232 | + }, |
233 | + 'auth.permission': { |
234 | + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, |
235 | + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
236 | + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), |
237 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
238 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) |
239 | + }, |
240 | + 'auth.user': { |
241 | + 'Meta': {'ordering': "['username']", 'object_name': 'User'}, |
242 | + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), |
243 | + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), |
244 | + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), |
245 | + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), |
246 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
247 | + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), |
248 | + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
249 | + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
250 | + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), |
251 | + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), |
252 | + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), |
253 | + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), |
254 | + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) |
255 | + }, |
256 | + 'contenttypes.contenttype': { |
257 | + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, |
258 | + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
259 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
260 | + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
261 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) |
262 | + }, |
263 | + 'schedule.agenda': { |
264 | + 'Meta': {'ordering': "('slot', 'room')", 'unique_together': "(('slot', 'room'),)", 'object_name': 'Agenda'}, |
265 | + 'auto': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
266 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
267 | + 'meeting': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['schedule.Meeting']"}), |
268 | + 'room': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['schedule.Room']"}), |
269 | + 'slot': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['schedule.Slot']"}) |
270 | + }, |
271 | + 'schedule.attendee': { |
272 | + 'Meta': {'ordering': "('user__username', 'summit')", 'object_name': 'Attendee'}, |
273 | + 'crew': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_column': "'crew'"}), |
274 | + 'end_utc': ('django.db.models.fields.DateTimeField', [], {'db_column': "'end'"}), |
275 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
276 | + 'secret_key_id': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}), |
277 | + 'start_utc': ('django.db.models.fields.DateTimeField', [], {'db_column': "'start'"}), |
278 | + 'summit': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['schedule.Summit']"}), |
279 | + 'topics': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['schedule.Topic']", 'symmetrical': 'False', 'blank': 'True'}), |
280 | + 'tracks': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['schedule.Track']", 'symmetrical': 'False', 'blank': 'True'}), |
281 | + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) |
282 | + }, |
283 | + 'schedule.attendeebusy': { |
284 | + 'Meta': {'ordering': "('attendee', 'start_utc', 'end_utc')", 'object_name': 'AttendeeBusy'}, |
285 | + 'attendee': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'busy_set'", 'to': "orm['schedule.Attendee']"}), |
286 | + 'end_utc': ('django.db.models.fields.DateTimeField', [], {'db_column': "'end'"}), |
287 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
288 | + 'start_utc': ('django.db.models.fields.DateTimeField', [], {'db_column': "'start'"}) |
289 | + }, |
290 | + 'schedule.crew': { |
291 | + 'Meta': {'ordering': "('date_utc', 'attendee')", 'object_name': 'Crew'}, |
292 | + 'attendee': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'crew_schedule'", 'to': "orm['schedule.Attendee']"}), |
293 | + 'date_utc': ('django.db.models.fields.DateField', [], {'db_column': "'date'"}), |
294 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) |
295 | + }, |
296 | + 'schedule.lead': { |
297 | + 'Meta': {'ordering': "('summit', 'track')", 'object_name': 'Lead'}, |
298 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
299 | + 'lead': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'lead'", 'to': "orm['schedule.Attendee']"}), |
300 | + 'summit': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['schedule.Summit']"}), |
301 | + 'track': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['schedule.Track']"}) |
302 | + }, |
303 | + 'schedule.meeting': { |
304 | + 'Meta': {'object_name': 'Meeting'}, |
305 | + 'approved': ('django.db.models.fields.CharField', [], {'default': "'PENDING'", 'max_length': '10', 'null': 'True'}), |
306 | + 'approver': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'approver_set'", 'null': 'True', 'to': "orm['schedule.Attendee']"}), |
307 | + 'assignee': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'assignee_set'", 'null': 'True', 'to': "orm['schedule.Attendee']"}), |
308 | + 'description': ('django.db.models.fields.TextField', [], {'max_length': '2047', 'blank': 'True'}), |
309 | + 'drafter': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'drafter_set'", 'null': 'True', 'to': "orm['schedule.Attendee']"}), |
310 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
311 | + 'name': ('summit.schedule.fields.NameField', [], {'max_length': '100'}), |
312 | + 'pad_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), |
313 | + 'participants': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['schedule.Attendee']", 'symmetrical': 'False', 'through': "orm['schedule.Participant']", 'blank': 'True'}), |
314 | + 'priority': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), |
315 | + 'private': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
316 | + 'private_key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}), |
317 | + 'requires_dial_in': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
318 | + 'scribe': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'scribe_set'", 'null': 'True', 'to': "orm['schedule.Attendee']"}), |
319 | + 'slots': ('django.db.models.fields.IntegerField', [], {'default': '1'}), |
320 | + 'spec_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}), |
321 | + 'status': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}), |
322 | + 'summit': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['schedule.Summit']"}), |
323 | + 'title': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
324 | + 'topics': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['schedule.Topic']", 'symmetrical': 'False', 'blank': 'True'}), |
325 | + 'tracks': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['schedule.Track']", 'symmetrical': 'False', 'blank': 'True'}), |
326 | + 'type': ('django.db.models.fields.CharField', [], {'default': "u'blueprint'", 'max_length': '15'}), |
327 | + 'video': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
328 | + 'wiki_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}) |
329 | + }, |
330 | + 'schedule.participant': { |
331 | + 'Meta': {'ordering': "('meeting', 'attendee', 'required')", 'object_name': 'Participant'}, |
332 | + 'attendee': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['schedule.Attendee']"}), |
333 | + 'from_launchpad': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
334 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
335 | + 'meeting': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['schedule.Meeting']"}), |
336 | + 'required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) |
337 | + }, |
338 | + 'schedule.room': { |
339 | + 'Meta': {'ordering': "('summit', 'name')", 'object_name': 'Room'}, |
340 | + 'end_utc': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_column': "'end'", 'blank': 'True'}), |
341 | + 'has_dial_in': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
342 | + 'icecast_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}), |
343 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
344 | + 'name': ('summit.schedule.fields.NameField', [], {'max_length': '50'}), |
345 | + 'size': ('django.db.models.fields.IntegerField', [], {'default': '0'}), |
346 | + 'start_utc': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_column': "'start'", 'blank': 'True'}), |
347 | + 'summit': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['schedule.Summit']"}), |
348 | + 'title': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
349 | + 'tracks': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['schedule.Track']", 'symmetrical': 'False', 'blank': 'True'}), |
350 | + 'type': ('django.db.models.fields.CharField', [], {'default': "u'open'", 'max_length': '7'}) |
351 | + }, |
352 | + 'schedule.roombusy': { |
353 | + 'Meta': {'ordering': "('room', 'start_utc', 'end_utc')", 'object_name': 'RoomBusy'}, |
354 | + 'end_utc': ('django.db.models.fields.DateTimeField', [], {'db_column': "'end'"}), |
355 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
356 | + 'room': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'busy_set'", 'to': "orm['schedule.Room']"}), |
357 | + 'start_utc': ('django.db.models.fields.DateTimeField', [], {'db_column': "'start'"}) |
358 | + }, |
359 | + 'schedule.slot': { |
360 | + 'Meta': {'ordering': "('summit', 'start_utc', 'end_utc')", 'object_name': 'Slot'}, |
361 | + 'end_utc': ('django.db.models.fields.DateTimeField', [], {'db_column': "'end'"}), |
362 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
363 | + 'start_utc': ('django.db.models.fields.DateTimeField', [], {'db_column': "'start'"}), |
364 | + 'summit': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['schedule.Summit']"}), |
365 | + 'type': ('django.db.models.fields.CharField', [], {'default': "u'open'", 'max_length': '7'}) |
366 | + }, |
367 | + 'schedule.summit': { |
368 | + 'Meta': {'ordering': "('name',)", 'object_name': 'Summit'}, |
369 | + 'date_end': ('django.db.models.fields.DateField', [], {'null': 'True'}), |
370 | + 'date_start': ('django.db.models.fields.DateField', [], {'null': 'True'}), |
371 | + 'description': ('django.db.models.fields.TextField', [], {'max_length': '2047', 'blank': 'True'}), |
372 | + 'etherpad': ('django.db.models.fields.URLField', [], {'default': "'http://pad.ubuntu.com/'", 'max_length': '75'}), |
373 | + 'hashtag': ('django.db.models.fields.CharField', [], {'max_length': '25', 'blank': 'True'}), |
374 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
375 | + 'last_update': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), |
376 | + 'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), |
377 | + 'managers': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'managers'", 'blank': 'True', 'to': "orm['auth.User']"}), |
378 | + 'name': ('summit.schedule.fields.NameField', [], {'max_length': '50'}), |
379 | + 'qr': ('django.db.models.fields.URLField', [], {'default': "''", 'max_length': '100', 'blank': 'True'}), |
380 | + 'schedulers': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'schedulers'", 'blank': 'True', 'to': "orm['auth.User']"}), |
381 | + 'state': ('django.db.models.fields.CharField', [], {'default': "u'sponsor'", 'max_length': '10'}), |
382 | + 'timezone': ('django.db.models.fields.CharField', [], {'max_length': '50'}), |
383 | + 'title': ('django.db.models.fields.CharField', [], {'max_length': '100'}) |
384 | + }, |
385 | + 'schedule.summitsprint': { |
386 | + 'Meta': {'ordering': "('summit', 'import_url')", 'object_name': 'SummitSprint'}, |
387 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
388 | + 'import_url': ('django.db.models.fields.URLField', [], {'max_length': '200'}), |
389 | + 'summit': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'sprint_set'", 'to': "orm['schedule.Summit']"}) |
390 | + }, |
391 | + 'schedule.topic': { |
392 | + 'Meta': {'ordering': "('summit', 'title')", 'object_name': 'Topic'}, |
393 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
394 | + 'summit': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['schedule.Summit']"}), |
395 | + 'title': ('django.db.models.fields.CharField', [], {'max_length': '100'}) |
396 | + }, |
397 | + 'schedule.track': { |
398 | + 'Meta': {'ordering': "('summit', 'title', 'slug')", 'object_name': 'Track'}, |
399 | + 'allow_adjacent_sessions': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
400 | + 'color': ('django.db.models.fields.CharField', [], {'default': "'FFFFFF'", 'max_length': '6'}), |
401 | + 'description': ('django.db.models.fields.TextField', [], {'max_length': '1000', 'null': 'True'}), |
402 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
403 | + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}), |
404 | + 'summit': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['schedule.Summit']"}), |
405 | + 'title': ('django.db.models.fields.CharField', [], {'max_length': '100'}) |
406 | + } |
407 | + } |
408 | + |
409 | + complete_apps = ['schedule'] |
410 | |
411 | === modified file 'summit/schedule/models/meetingmodel.py' |
412 | --- summit/schedule/models/meetingmodel.py 2012-01-27 12:35:43 +0000 |
413 | +++ summit/schedule/models/meetingmodel.py 2012-03-02 03:37:19 +0000 |
414 | @@ -32,12 +32,14 @@ |
415 | from summit.schedule.models.topicmodel import Topic |
416 | from summit.schedule.models.attendeemodel import Attendee |
417 | |
418 | +from summit.schedule.autoslug import AutoSlugMixin |
419 | + |
420 | __all__ = ( |
421 | 'Meeting', |
422 | ) |
423 | |
424 | |
425 | -class Meeting(models.Model): |
426 | +class Meeting(AutoSlugMixin, models.Model): |
427 | |
428 | class SchedulingError(Exception): |
429 | pass |
430 | @@ -69,6 +71,12 @@ |
431 | (90, u'Essential'), |
432 | ) |
433 | |
434 | + REVIEW_CHOICES = ( |
435 | + (u'PENDING', u'Pending'), |
436 | + (u'APPROVED', u'Approved'), |
437 | + (u'DECLINED', u'Declined'), |
438 | + ) |
439 | + |
440 | summit = models.ForeignKey(Summit) |
441 | name = NameField(max_length=100, help_text="Lowercase alphanumeric characters and dashes only.") |
442 | title = models.CharField(max_length=100, help_text="Alphanumeric characters and spaces are allowed") |
443 | @@ -90,6 +98,8 @@ |
444 | pad_url = models.URLField(verify_exists=False, |
445 | verbose_name="Pad URL", null=True, blank=True) |
446 | slots = models.IntegerField(default=1) |
447 | + approved = models.CharField(max_length=10, choices=REVIEW_CHOICES, |
448 | + null=True, default='PENDING') |
449 | private = models.BooleanField(default=False) |
450 | private_key = models.CharField(max_length=32, null=True, blank=True) |
451 | |
452 | @@ -112,6 +122,10 @@ |
453 | class Meta: |
454 | app_label = 'schedule' |
455 | |
456 | + def save(self): |
457 | + super(Meeting, self).save() |
458 | + self.update_slug() |
459 | + |
460 | def share(self): |
461 | if not self.pk or not self.private: |
462 | return |
463 | |
464 | === added file 'summit/schedule/templates/schedule/edit_meeting.html' |
465 | --- summit/schedule/templates/schedule/edit_meeting.html 1970-01-01 00:00:00 +0000 |
466 | +++ summit/schedule/templates/schedule/edit_meeting.html 2012-03-02 03:37:19 +0000 |
467 | @@ -0,0 +1,45 @@ |
468 | +{% extends "base.html" %} |
469 | + |
470 | +{% block page_name %}Edit Meeting - {{ summit.title }}{%endblock %} |
471 | +{% block sub_nav %}{% endblock %} |
472 | + |
473 | +{% block extrahead %}{{ block.super }} |
474 | + <script type="text/javascript" src="{{MEDIA_URL}}js/colortip-1.0-jquery.js"></script> |
475 | + <link rel="stylesheet" type="text/css" href="{{MEDIA_URL}}css/colortip-1.0-jquery.css"/> |
476 | +{% endblock %} |
477 | + |
478 | +{% block extrafooter %} |
479 | +<script type="text/javascript"><!-- |
480 | +$(document).ready(function(){ |
481 | + $('span[rel*=help]').colorTip({color:'orange'}); |
482 | +}); |
483 | +--></script> |
484 | +<style> |
485 | +ul { |
486 | + height: 12em; |
487 | + overflow-y: scroll; |
488 | + overflow-x: hidden; |
489 | +} |
490 | +</style> |
491 | +{% endblock %} |
492 | + |
493 | + |
494 | +{% block content %} |
495 | + |
496 | + <article id="form" class="main-content"> |
497 | + {% if form.errors %} |
498 | + <p style="color: red;"> |
499 | + Please correct the error{{ form.errors|pluralize }} below. |
500 | + </p> |
501 | + {% endif %} |
502 | + |
503 | + <form action="{{ request.path_info }}" method="POST"> |
504 | + <fieldset> |
505 | + <legend>Edit Meeting</legend> |
506 | + {{ form.as_template }} |
507 | + </fieldset> |
508 | + {% if is_popup %}<input type="hidden" name="_popup" value="1">{% endif %} |
509 | + <input type="submit" name="submit" value="Save" class="submit-button" /> |
510 | + </form> |
511 | + </article> |
512 | +{% endblock %} |
513 | |
514 | === modified file 'summit/schedule/templates/schedule/meeting.html' |
515 | --- summit/schedule/templates/schedule/meeting.html 2012-02-02 15:56:27 +0000 |
516 | +++ summit/schedule/templates/schedule/meeting.html 2012-03-02 03:37:19 +0000 |
517 | @@ -29,6 +29,8 @@ |
518 | <a class="sub-nav-item" href="{% url summit.schedule.views.meeting meeting.summit.name, meeting.id, meeting.name|default:'-' %}+share">Share Meeting</a> |
519 | {% endif %} |
520 | <a class="sub-nav-item" href="{% url summit.schedule.views.edit_private summit.name, meeting.id, meeting.name|default:'-' %}">Edit Meeting</a> |
521 | + {% else %} |
522 | + <a class="sub-nav-item" href="{% url summit.schedule.views.edit_meeting summit.name, meeting.id, meeting.name|default:'-' %}">Edit Meeting</a> |
523 | {% endif %} |
524 | {% if user_is_attending %} |
525 | {% if user_is_participating %} |
526 | |
527 | === added file 'summit/schedule/templates/schedule/propose_meeting.html' |
528 | --- summit/schedule/templates/schedule/propose_meeting.html 1970-01-01 00:00:00 +0000 |
529 | +++ summit/schedule/templates/schedule/propose_meeting.html 2012-03-02 03:37:19 +0000 |
530 | @@ -0,0 +1,45 @@ |
531 | +{% extends "base.html" %} |
532 | + |
533 | +{% block page_name %}Propse a Meeting - {{ summit.title }}{%endblock %} |
534 | +{% block sub_nav %}{% endblock %} |
535 | + |
536 | +{% block extrahead %}{{ block.super }} |
537 | + <script type="text/javascript" src="{{MEDIA_URL}}js/colortip-1.0-jquery.js"></script> |
538 | + <link rel="stylesheet" type="text/css" href="{{MEDIA_URL}}css/colortip-1.0-jquery.css"/> |
539 | +{% endblock %} |
540 | + |
541 | +{% block extrafooter %} |
542 | +<script type="text/javascript"><!-- |
543 | +$(document).ready(function(){ |
544 | + $('span[rel*=help]').colorTip({color:'orange'}); |
545 | +}); |
546 | +--></script> |
547 | +<style> |
548 | +ul { |
549 | + height: 12em; |
550 | + overflow-y: scroll; |
551 | + overflow-x: hidden; |
552 | +} |
553 | +</style> |
554 | +{% endblock %} |
555 | + |
556 | + |
557 | +{% block content %} |
558 | + |
559 | + <article id="form" class="main-content"> |
560 | + {% if form.errors %} |
561 | + <p style="color: red;"> |
562 | + Please correct the error{{ form.errors|pluralize }} below. |
563 | + </p> |
564 | + {% endif %} |
565 | + |
566 | + <form action="{{ request.path_info }}" method="POST"> |
567 | + <fieldset> |
568 | + <legend>Propose a Meeting</legend> |
569 | + {{ form.as_template }} |
570 | + </fieldset> |
571 | + {% if is_popup %}<input type="hidden" name="_popup" value="1">{% endif %} |
572 | + <input type="submit" name="submit" value="Create" class="submit-button" /> |
573 | + </form> |
574 | + </article> |
575 | +{% endblock %} |
576 | |
577 | === added file 'summit/schedule/templates/schedule/review.html' |
578 | --- summit/schedule/templates/schedule/review.html 1970-01-01 00:00:00 +0000 |
579 | +++ summit/schedule/templates/schedule/review.html 2012-03-02 03:37:19 +0000 |
580 | @@ -0,0 +1,117 @@ |
581 | +{% extends "base.html" %} |
582 | +{% load datetime %} |
583 | + |
584 | +{% block page_name %} |
585 | +Review Proposed Meetings - {{ summit.title }} |
586 | +{% endblock %} |
587 | + |
588 | +{% block head %} |
589 | +<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> |
590 | +{% if linaro %} |
591 | + <base target="_blank" /> |
592 | +{% endif %} |
593 | +{{ block.super }} |
594 | +<script language="JavaScript"> |
595 | + |
596 | +function show_agenda_details(agenda_id, index_id) { |
597 | + var elem_id = 'agenda-'+agenda_id+'-'+index_id+'-details' |
598 | + var details = document.getElementById(elem_id) |
599 | + details.style.display='block'; |
600 | +} |
601 | + |
602 | +function hide_agenda_details(agenda_id, index_id) { |
603 | + var elem_id = 'agenda-'+agenda_id+'-'+index_id+'-details' |
604 | + var details = document.getElementById(elem_id) |
605 | + details.style.display='none'; |
606 | +} |
607 | + |
608 | +</script> |
609 | +<style> |
610 | +TABLE img.icon { |
611 | + vertical-align: bottom; |
612 | + width: 16px; |
613 | + height: 16px; |
614 | +} |
615 | + |
616 | +table.basic span.main-agenda-item-tracks { |
617 | + font-size: 0.8em; |
618 | +} |
619 | + |
620 | +div.agenda-details { |
621 | + display: none; |
622 | + padding: 5px; |
623 | + position: absolute; |
624 | + margin-left: 20px; |
625 | + font-size: 0.9em; |
626 | + color: #FFFFFF; |
627 | + background-color: #101010; |
628 | + border: #000000 1px solid; |
629 | + border-radius: 5px; |
630 | + min-width: 200px; |
631 | + max-width: 500px; |
632 | + z-index: 100; |
633 | +} |
634 | + |
635 | +div.meeting-description { |
636 | + white-space: pre-wrap; |
637 | +} |
638 | + |
639 | +div.agenda-details LI { |
640 | + font-size: 1.0em; |
641 | +} |
642 | + |
643 | +div.schedule-head { |
644 | + position: relative; |
645 | + overflow: visible; |
646 | + z-index: 10; |
647 | +} |
648 | + |
649 | +div.schedule-head .schedule-qrcode { |
650 | + height: 63px; |
651 | + vertical-align: top; |
652 | + margin-left: -3px; |
653 | + margin-top: -3px; |
654 | +} |
655 | + |
656 | +div.schedule-head .schedule-date { |
657 | + font-weight: normal; |
658 | + font-size: 36px; |
659 | + margin-left: 5px; |
660 | +} |
661 | + |
662 | +div.schedule-head .schedule-crew, .last-updated { |
663 | + font-size: 0.7pc; |
664 | +} |
665 | + |
666 | +</style> |
667 | +{% endblock %} |
668 | + |
669 | +{% block sub_nav_links %} |
670 | +{% endblock %} |
671 | + |
672 | +{% block content %} |
673 | +<article class="main-content"> |
674 | + |
675 | +<div class="schedule-head"> |
676 | + Review |
677 | +</div> |
678 | + |
679 | + |
680 | +<table class="basic"> |
681 | +{% for meeting in pending.all %} |
682 | + |
683 | +<tr style="background-color: {{ meeting.track_color }}; {% if meeting.private %}border: 1px; border-style: solid; border-color: #FF0000;{% endif %}"> |
684 | +<td width="75%"> |
685 | + <a href="{{ meeting.meeting_page_url }}" style="color: #000000;" class="main-agenda-item-name"> |
686 | + {{ meeting.title }} |
687 | + </a> |
688 | +</td> |
689 | +</tr> |
690 | +</table> |
691 | +{% endfor %} |
692 | + |
693 | +<br /><br /><br /><br /> |
694 | +</article> |
695 | + |
696 | + |
697 | +{% endblock %} |
698 | |
699 | === modified file 'summit/schedule/templates/schedule/summit.html' |
700 | --- summit/schedule/templates/schedule/summit.html 2012-02-05 21:01:37 +0000 |
701 | +++ summit/schedule/templates/schedule/summit.html 2012-03-02 03:37:19 +0000 |
702 | @@ -81,6 +81,7 @@ |
703 | {% if create_meeting %} |
704 | <a class="sub-nav-item" href="/{{ summit.name }}/create_pm" title="Create Private Meeting">Create Private Meeting</a> |
705 | {% endif %} |
706 | + <a class="sub-nav-item" href="/{{ summit.name }}/propose_meeting" title="Propose Meeting">Propose a Meeting</a> |
707 | <a class="sub-nav-item" href="/{{ summit.name }}/tracks" title="Tracks">Tracks</a> |
708 | <a class="sub-nav-item" href="/{{ summit.name }}.ical">All Sessions (iCal)</a> |
709 | {% if attendee %} |
710 | |
711 | === modified file 'summit/schedule/views.py' |
712 | --- summit/schedule/views.py 2012-02-02 15:04:05 +0000 |
713 | +++ summit/schedule/views.py 2012-03-02 03:37:19 +0000 |
714 | @@ -35,7 +35,7 @@ |
715 | |
716 | from summit.schedule.render import schedule_factory, Schedule |
717 | |
718 | -from summit.schedule.forms import CreatePrivateMeeting, EditPrivateMeeting |
719 | +from summit.schedule.forms import * |
720 | |
721 | __all__ = ( |
722 | 'summit', |
723 | @@ -401,7 +401,7 @@ |
724 | if not summit.can_create_pm(attendee): |
725 | return HttpResponseRedirect(reverse('summit.schedule.views.summit', args=(summit.name,))) |
726 | else: |
727 | - meeting = Meeting(summit=summit, approver=attendee, private=True) |
728 | + meeting = Meeting(summit=summit, approver=attendee, private=True, approved='APPROVED') |
729 | |
730 | if request.method == 'POST': |
731 | form = CreatePrivateMeeting(data=request.POST, instance=meeting) |
732 | @@ -419,6 +419,26 @@ |
733 | context, RequestContext(request)) |
734 | |
735 | @summit_attendee_required |
736 | +def propose_meeting(request, summit, attendee): |
737 | + |
738 | + meeting = Meeting(summit=summit, drafter=attendee, private=False, approved=False) |
739 | + |
740 | + if request.method == 'POST': |
741 | + form = ProposeMeeting(data=request.POST, instance=meeting) |
742 | + if form.is_valid(): |
743 | + form.save() |
744 | + return HttpResponseRedirect(meeting.meeting_page_url) |
745 | + else: |
746 | + form = ProposeMeeting(instance=meeting) |
747 | + |
748 | + context = { |
749 | + 'summit': summit, |
750 | + 'form': form, |
751 | + } |
752 | + return render_to_response('schedule/propose_meeting.html', |
753 | + context, RequestContext(request)) |
754 | + |
755 | +@summit_attendee_required |
756 | def edit_private(request, summit, attendee, meeting_id, meeting_slug): |
757 | meeting = get_object_or_404(summit.meeting_set, id=meeting_id) |
758 | |
759 | @@ -439,3 +459,37 @@ |
760 | } |
761 | return render_to_response('schedule/edit_private.html', |
762 | context, RequestContext(request)) |
763 | + |
764 | +@summit_attendee_required |
765 | +def edit_meeting(request, summit, attendee, meeting_id, meeting_slug): |
766 | + meeting = get_object_or_404(summit.meeting_set, id=meeting_id) |
767 | + |
768 | + if attendee != meeting.drafter: |
769 | + return HttpResponseRedirect(reverse('summit.schedule.views.summit', args=(summit.name,))) |
770 | + else: |
771 | + if request.method == 'POST': |
772 | + form = EditMeeting(data=request.POST, instance=meeting) |
773 | + if form.is_valid(): |
774 | + form.save() |
775 | + return HttpResponseRedirect(meeting.meeting_page_url) |
776 | + else: |
777 | + form = EditMeeting(instance=meeting) |
778 | + |
779 | + context = { |
780 | + 'summit': summit, |
781 | + 'form': form, |
782 | + } |
783 | + return render_to_response('schedule/edit_meeting.html', |
784 | + context, RequestContext(request)) |
785 | + |
786 | +@summit_only_required |
787 | +def review_pending(request, summit): |
788 | + |
789 | + pending = Meeting.objects.filter(summit=summit, approved='PENDING') |
790 | + |
791 | + context = { |
792 | + 'summit': summit, |
793 | + 'pending': pending, |
794 | + } |
795 | + return render_to_response("schedule/review.html", context, |
796 | + context_instance=RequestContext(request)) |
797 | |
798 | === modified file 'summit/urls.py' |
799 | --- summit/urls.py 2012-01-23 01:18:55 +0000 |
800 | +++ summit/urls.py 2012-03-02 03:37:19 +0000 |
801 | @@ -67,6 +67,9 @@ |
802 | url(r'^mobile/', 'mobile', name='mobile'), |
803 | url(r'^logout$', 'logout_view', name='logout'), |
804 | (r'^(?P<summit_name>[\w-]+)/$', 'summit'), |
805 | + (r'^(?P<summit_name>[\w-]+)/propose_meeting/$', 'propose_meeting'), |
806 | + (r'^(?P<summit_name>[\w-]+)/edit_meeting/(?P<meeting_id>\d+)/(?P<meeting_slug>[%+\.\w-]+)/$', 'edit_meeting'), |
807 | + (r'^(?P<summit_name>[\w-]+)/review/$', 'review_pending'), |
808 | (r'^(?P<summit_name>[\w-]+)/create_pm/$', 'create_private'), |
809 | (r'^(?P<summit_name>[\w-]+)/edit_pm/(?P<meeting_id>\d+)/(?P<meeting_slug>[%+\.\w-]+)/$', 'edit_private'), |
810 | (r'^(?P<summit_name>[\w-]+)/tracks$', 'tracks'), |