Merge lp:~cjohnston/summit/new-meeting-stuff into lp:summit

Proposed by Chris Johnston
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
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.

To post a comment you must log in.
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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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'),

Subscribers

People subscribed via source and target branches