Merge lp:~mhall119/summit/support-short-slots into lp:summit

Proposed by Michael Hall on 2012-10-02
Status: Merged
Approved by: Chris Johnston on 2012-10-04
Approved revision: 455
Merged at revision: 451
Proposed branch: lp:~mhall119/summit/support-short-slots
Merge into: lp:summit
Diff against target: 559 lines (+309/-31)
7 files modified
summit/common/management/commands/lpupdate.py (+11/-2)
summit/schedule/admin/meetingadmin.py (+1/-1)
summit/schedule/management/commands/initslots.py (+14/-6)
summit/schedule/migrations/0025_add_meeting_blueprint_id.py (+207/-0)
summit/schedule/models/meetingmodel.py (+8/-0)
summit/schedule/models/summitmodel.py (+33/-14)
summit/schedule/tests.py (+35/-8)
To merge this branch: bzr merge lp:~mhall119/summit/support-short-slots
Reviewer Review Type Date Requested Status
Chris Johnston 2012-10-02 Approve on 2012-10-04
Review via email: mp+127557@code.launchpad.net

Commit Message

Adds the ability to support 30-minute slots in both initslots and lpupdate commands, even though we won't be using them for UDS-R.

Also adds support for LP Blueprint IDs, and using them to match instead of BP name, so that renames are handled properly, and removed BPs are marked as "REMOVED" but not deleted.

Description of the Change

Adds the ability to support 30-minute slots in both initslots and lpupdate commands, even though we won't be using them for UDS-R.

Also adds support for LP Blueprint IDs, and using them to match instead of BP name, so that renames are handled properly, and removed BPs are marked as "REMOVED" but not deleted.

To post a comment you must log in.
Chris Johnston (cjohnston) wrote :

When running -M I saw:

user yingscreative
user zsombi
user nncrash
will delete foundations-r-prior-release-feedback
will delete hardware-r-fwts-features
will delete hardware-r-ubuntu-drivers-common-and-lbm
will delete hardware-r-uefi-update-from-intel

I'm not sure that imported meetings should be deleted just because we ran a -M... Thoughts?

Michael Hall (mhall119) wrote :

it's not deleting, that's the old comment text, it's just marking them as REMOVED so they won't be scheduled

453. By Michael Hall on 2012-10-04

Change comment about deleting meetings

454. By Michael Hall on 2012-10-04

Update comment

455. By Michael Hall on 2012-10-04

only mark meetings removed when importing meetings

Chris Johnston (cjohnston) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'summit/common/management/commands/lpupdate.py'
2--- summit/common/management/commands/lpupdate.py 2012-02-25 00:13:45 +0000
3+++ summit/common/management/commands/lpupdate.py 2012-10-04 13:59:28 +0000
4@@ -17,6 +17,7 @@
5 from django.core.management.base import BaseCommand, CommandError
6
7 from summit.schedule.models import Summit
8+from optparse import make_option
9
10 __all__ = (
11 'Command',
12@@ -24,16 +25,24 @@
13
14
15 class Command(BaseCommand):
16+ option_list = BaseCommand.option_list + (
17+ make_option("-s", "--slots", dest="slots",
18+ help="Number of slots an imported meeting needs", type=int, default=1),
19+ make_option("-A", "--no-attendees", dest="skip_attendees", action="store_true",
20+ help="Don't import Attendee data", default=False),
21+ make_option("-M", "--no-meetings", dest="skip_meetings", action="store_true",
22+ help="Don't import Meeting data", default=False),
23+ )
24
25 def handle(self, summit='', *args, **options):
26 if not summit:
27 for summit in Summit.objects.all():
28 print "Updating %s" % summit
29- summit.update_from_launchpad()
30+ summit.update_from_launchpad(options)
31 else:
32 try:
33 summit = Summit.objects.get(name=summit)
34 except Summit.DoesNotExist:
35 raise CommandError("Summit doesn't exist: %s" % summit)
36 print "Updating %s" % summit
37- summit.update_from_launchpad()
38+ summit.update_from_launchpad(options)
39
40=== modified file 'summit/schedule/admin/meetingadmin.py'
41--- summit/schedule/admin/meetingadmin.py 2012-05-16 14:59:59 +0000
42+++ summit/schedule/admin/meetingadmin.py 2012-10-04 13:59:28 +0000
43@@ -144,7 +144,7 @@
44 'type', 'tracks', 'requires_dial_in', 'video'),
45 }),
46 ("References", {
47- 'fields': ('spec_url', 'wiki_url', 'pad_url'),
48+ 'fields': ('spec_url', 'launchpad_blueprint_id', 'wiki_url', 'pad_url'),
49 }),
50 ("Scheduling details", {
51 'classes': ('collapse', ),
52
53=== modified file 'summit/schedule/management/commands/initslots.py'
54--- summit/schedule/management/commands/initslots.py 2012-03-08 22:19:17 +0000
55+++ summit/schedule/management/commands/initslots.py 2012-10-04 13:59:28 +0000
56@@ -58,6 +58,9 @@
57 duration = datetime.timedelta(minutes=options["duration"])
58 interval = datetime.timedelta(minutes=options["interval"])
59 breaktime = datetime.timedelta(minutes=15)
60+
61+ slots_per_hour = (60 * 60) / duration.seconds
62+
63 try:
64 summit = Summit.objects.get(name=summit.__str__)
65 except Summit.DoesNotExist:
66@@ -77,14 +80,19 @@
67 # Determines the type of the session
68 if slottime.time() == lunch:
69 slot_type = 'lunch'
70+ slot_duration = datetime.timedelta(minutes=60)
71+ slot_count += (slots_per_hour - 1)
72 elif slottime.time() == plenary:
73 slot_type = 'plenary'
74+ slot_duration = datetime.timedelta(minutes=60)
75+ slot_count += (slots_per_hour - 1)
76 else:
77 slot_type = 'open'
78- slot_length = duration
79+ slot_duration = duration
80+ slot_length = slot_duration
81
82 # Morning Break
83- if slot_count == 2:
84+ if slot_count == (2 * slots_per_hour):
85 slot, created = Slot.objects.get_or_create(
86 summit=summit,
87 start_utc=start_time + slot_length - breaktime,
88@@ -93,7 +101,7 @@
89 )
90 slot_length = slot_length - breaktime
91 # Afternoon Break
92- elif slot_count == 8:
93+ elif slot_count == (7 * slots_per_hour)+1:
94 slot, created = Slot.objects.get_or_create(
95 summit=summit,
96 start_utc=start_time,
97@@ -105,10 +113,10 @@
98 slottime = slottime + interval
99 # Non-break intervals
100 elif slot_type == 'open':
101- nexthour = slottime + hour
102+ nexthour = slottime + duration
103 if nexthour.time() != lunch \
104 and nexthour.time() != plenary \
105- and slot_count != 7:
106+ and slot_count != (7*slots_per_hour):
107 slot_length = slot_length - interval
108
109 end_time = start_time + slot_length
110@@ -120,7 +128,7 @@
111 end_utc=end_time,
112 type=slot_type)
113
114- slottime = slottime + duration
115+ slottime = slottime + slot_duration
116
117 date = date + day
118 print "Initializing slots complete."
119
120=== added file 'summit/schedule/migrations/0025_add_meeting_blueprint_id.py'
121--- summit/schedule/migrations/0025_add_meeting_blueprint_id.py 1970-01-01 00:00:00 +0000
122+++ summit/schedule/migrations/0025_add_meeting_blueprint_id.py 2012-10-04 13:59:28 +0000
123@@ -0,0 +1,207 @@
124+# encoding: utf-8
125+import datetime
126+from south.db import db
127+from south.v2 import SchemaMigration
128+from django.db import models
129+
130+class Migration(SchemaMigration):
131+
132+ def forwards(self, orm):
133+
134+ # Adding field 'Meeting.launchpad_blueprint_id'
135+ db.add_column('schedule_meeting', 'launchpad_blueprint_id', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True), keep_default=False)
136+
137+
138+ def backwards(self, orm):
139+
140+ # Deleting field 'Meeting.launchpad_blueprint_id'
141+ db.delete_column('schedule_meeting', 'launchpad_blueprint_id')
142+
143+
144+ models = {
145+ 'auth.group': {
146+ 'Meta': {'object_name': 'Group'},
147+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
148+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
149+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
150+ },
151+ 'auth.permission': {
152+ 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
153+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
154+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
155+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
156+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
157+ },
158+ 'auth.user': {
159+ 'Meta': {'ordering': "['username']", 'object_name': 'User'},
160+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
161+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
162+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
163+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
164+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
165+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
166+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
167+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
168+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
169+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
170+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
171+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
172+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
173+ },
174+ 'contenttypes.contenttype': {
175+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
176+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
177+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
178+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
179+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
180+ },
181+ 'schedule.agenda': {
182+ 'Meta': {'ordering': "('slot', 'room')", 'unique_together': "(('slot', 'room'),)", 'object_name': 'Agenda'},
183+ 'auto': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
184+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
185+ 'meeting': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['schedule.Meeting']"}),
186+ 'room': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['schedule.Room']"}),
187+ 'slot': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['schedule.Slot']"})
188+ },
189+ 'schedule.attendee': {
190+ 'Meta': {'ordering': "('user__username', 'summit')", 'object_name': 'Attendee'},
191+ 'crew': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_column': "'crew'"}),
192+ 'end_utc': ('django.db.models.fields.DateTimeField', [], {'db_column': "'end'"}),
193+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
194+ 'secret_key_id': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
195+ 'start_utc': ('django.db.models.fields.DateTimeField', [], {'db_column': "'start'"}),
196+ 'summit': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['schedule.Summit']"}),
197+ 'tracks': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['schedule.Track']", 'symmetrical': 'False', 'blank': 'True'}),
198+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
199+ },
200+ 'schedule.attendeebusy': {
201+ 'Meta': {'ordering': "('attendee', 'start_utc', 'end_utc')", 'object_name': 'AttendeeBusy'},
202+ 'attendee': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'busy_set'", 'to': "orm['schedule.Attendee']"}),
203+ 'end_utc': ('django.db.models.fields.DateTimeField', [], {'db_column': "'end'"}),
204+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
205+ 'start_utc': ('django.db.models.fields.DateTimeField', [], {'db_column': "'start'"})
206+ },
207+ 'schedule.crew': {
208+ 'Meta': {'ordering': "('date_utc', 'attendee')", 'object_name': 'Crew'},
209+ 'attendee': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'crew_schedule'", 'to': "orm['schedule.Attendee']"}),
210+ 'date_utc': ('django.db.models.fields.DateField', [], {'db_column': "'date'"}),
211+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
212+ },
213+ 'schedule.lead': {
214+ 'Meta': {'ordering': "('summit', 'track')", 'object_name': 'Lead'},
215+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
216+ 'lead': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'lead'", 'to': "orm['schedule.Attendee']"}),
217+ 'summit': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['schedule.Summit']"}),
218+ 'track': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['schedule.Track']"})
219+ },
220+ 'schedule.meeting': {
221+ 'Meta': {'object_name': 'Meeting'},
222+ 'approved': ('django.db.models.fields.CharField', [], {'default': "'PENDING'", 'max_length': '10', 'null': 'True'}),
223+ 'approver': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'approver_set'", 'null': 'True', 'to': "orm['schedule.Attendee']"}),
224+ 'assignee': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'assignee_set'", 'null': 'True', 'to': "orm['schedule.Attendee']"}),
225+ 'description': ('django.db.models.fields.TextField', [], {'max_length': '2047', 'blank': 'True'}),
226+ 'drafter': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'drafter_set'", 'null': 'True', 'to': "orm['schedule.Attendee']"}),
227+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
228+ 'launchpad_blueprint_id': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
229+ 'name': ('summit.schedule.fields.NameField', [], {'max_length': '100'}),
230+ 'override_break': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
231+ 'pad_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
232+ 'participants': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['schedule.Attendee']", 'symmetrical': 'False', 'through': "orm['schedule.Participant']", 'blank': 'True'}),
233+ 'priority': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
234+ 'private': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
235+ 'private_key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
236+ 'requires_dial_in': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
237+ 'scribe': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'scribe_set'", 'null': 'True', 'to': "orm['schedule.Attendee']"}),
238+ 'slots': ('django.db.models.fields.IntegerField', [], {'default': '1'}),
239+ 'spec_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
240+ 'status': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}),
241+ 'summit': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['schedule.Summit']"}),
242+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
243+ 'tracks': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['schedule.Track']", 'symmetrical': 'False', 'blank': 'True'}),
244+ 'type': ('django.db.models.fields.CharField', [], {'default': "u'discussion'", 'max_length': '15'}),
245+ 'video': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
246+ 'wiki_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'})
247+ },
248+ 'schedule.participant': {
249+ 'Meta': {'ordering': "('meeting', 'attendee', 'participation')", 'object_name': 'Participant'},
250+ 'attendee': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['schedule.Attendee']"}),
251+ 'from_launchpad': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
252+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
253+ 'meeting': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['schedule.Meeting']"}),
254+ 'participation': ('django.db.models.fields.CharField', [], {'default': "'ATTENDING'", 'max_length': '32', 'null': 'True'})
255+ },
256+ 'schedule.room': {
257+ 'Meta': {'ordering': "('summit', 'name')", 'object_name': 'Room'},
258+ 'end_utc': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_column': "'end'", 'blank': 'True'}),
259+ 'has_dial_in': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
260+ 'icecast_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
261+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
262+ 'irc_channel': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
263+ 'name': ('summit.schedule.fields.NameField', [], {'max_length': '50'}),
264+ 'size': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
265+ 'start_utc': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_column': "'start'", 'blank': 'True'}),
266+ 'summit': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['schedule.Summit']"}),
267+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
268+ 'tracks': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['schedule.Track']", 'symmetrical': 'False', 'blank': 'True'}),
269+ 'type': ('django.db.models.fields.CharField', [], {'default': "u'open'", 'max_length': '7'})
270+ },
271+ 'schedule.roombusy': {
272+ 'Meta': {'ordering': "('room', 'start_utc', 'end_utc')", 'object_name': 'RoomBusy'},
273+ 'end_utc': ('django.db.models.fields.DateTimeField', [], {'db_column': "'end'"}),
274+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
275+ 'room': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'busy_set'", 'to': "orm['schedule.Room']"}),
276+ 'start_utc': ('django.db.models.fields.DateTimeField', [], {'db_column': "'start'"})
277+ },
278+ 'schedule.slot': {
279+ 'Meta': {'ordering': "('summit', 'start_utc', 'end_utc')", 'object_name': 'Slot'},
280+ 'end_utc': ('django.db.models.fields.DateTimeField', [], {'db_column': "'end'"}),
281+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
282+ 'start_utc': ('django.db.models.fields.DateTimeField', [], {'db_column': "'start'"}),
283+ 'summit': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['schedule.Summit']"}),
284+ 'type': ('django.db.models.fields.CharField', [], {'default': "u'open'", 'max_length': '7'})
285+ },
286+ 'schedule.summit': {
287+ 'Meta': {'ordering': "('name',)", 'object_name': 'Summit'},
288+ 'date_end': ('django.db.models.fields.DateField', [], {'null': 'True'}),
289+ 'date_start': ('django.db.models.fields.DateField', [], {'null': 'True'}),
290+ 'description': ('django.db.models.fields.TextField', [], {'max_length': '2047', 'blank': 'True'}),
291+ 'etherpad': ('django.db.models.fields.URLField', [], {'default': "'http://pad.ubuntu.com/'", 'max_length': '75'}),
292+ 'hashtag': ('django.db.models.fields.CharField', [], {'max_length': '25', 'blank': 'True'}),
293+ 'help_text': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
294+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
295+ 'last_update': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
296+ 'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
297+ 'managers': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'managers'", 'blank': 'True', 'to': "orm['auth.User']"}),
298+ 'name': ('summit.schedule.fields.NameField', [], {'max_length': '50'}),
299+ 'qr': ('django.db.models.fields.URLField', [], {'default': "''", 'max_length': '100', 'blank': 'True'}),
300+ 'schedulers': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'schedulers'", 'blank': 'True', 'to': "orm['auth.User']"}),
301+ 'sites': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['sites.Site']", 'symmetrical': 'False'}),
302+ 'state': ('django.db.models.fields.CharField', [], {'default': "u'sponsor'", 'max_length': '10'}),
303+ 'timezone': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
304+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '100'})
305+ },
306+ 'schedule.summitsprint': {
307+ 'Meta': {'ordering': "('summit', 'import_url')", 'object_name': 'SummitSprint'},
308+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
309+ 'import_url': ('django.db.models.fields.URLField', [], {'max_length': '200'}),
310+ 'summit': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'sprint_set'", 'to': "orm['schedule.Summit']"})
311+ },
312+ 'schedule.track': {
313+ 'Meta': {'ordering': "('summit', 'title', 'slug')", 'object_name': 'Track'},
314+ 'allow_adjacent_sessions': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
315+ 'color': ('django.db.models.fields.CharField', [], {'default': "'FFFFFF'", 'max_length': '6'}),
316+ 'description': ('django.db.models.fields.TextField', [], {'max_length': '1000', 'null': 'True'}),
317+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
318+ 'slug': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}),
319+ 'summit': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['schedule.Summit']"}),
320+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '100'})
321+ },
322+ 'sites.site': {
323+ 'Meta': {'ordering': "('domain',)", 'object_name': 'Site', 'db_table': "'django_site'"},
324+ 'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
325+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
326+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
327+ }
328+ }
329+
330+ complete_apps = ['schedule']
331
332=== modified file 'summit/schedule/models/meetingmodel.py'
333--- summit/schedule/models/meetingmodel.py 2012-07-14 20:19:27 +0000
334+++ summit/schedule/models/meetingmodel.py 2012-10-04 13:59:28 +0000
335@@ -74,6 +74,7 @@
336 (u'PENDING', u'Pending'),
337 (u'APPROVED', u'Approved'),
338 (u'DECLINED', u'Declined'),
339+ (u'REMOVED', u'Removed'),
340 )
341
342 summit = models.ForeignKey(Summit)
343@@ -118,6 +119,7 @@
344 related_name='scribe_set')
345 participants = models.ManyToManyField(Attendee, blank=True,
346 through='Participant')
347+ launchpad_blueprint_id = models.IntegerField(blank=True, null=True)
348
349 class Meta:
350 app_label = 'schedule'
351@@ -296,6 +298,12 @@
352
353 self.approved = 'APPROVED'
354
355+ name = elem.get("name")
356+ if name:
357+ self.name = name[:100]
358+ else:
359+ self.name = None
360+
361 status = elem.get("status")
362 if status:
363 self.status = status
364
365=== modified file 'summit/schedule/models/summitmodel.py'
366--- summit/schedule/models/summitmodel.py 2012-08-09 15:53:51 +0000
367+++ summit/schedule/models/summitmodel.py 2012-10-04 13:59:28 +0000
368@@ -233,14 +233,16 @@
369 % self.name)]
370 return urls
371
372- def update_from_launchpad_response(self, response):
373+ def update_from_launchpad_response(self, response, options={}):
374 meetings = set()
375- for elem in response.find("attendees").findall("person"):
376- self.update_attendee_from_launchpad(elem)
377- for elem in response.find("unscheduled").findall("meeting"):
378- meeting = self.update_meeting_from_launchpad(elem)
379- if meeting is not None:
380- meetings.add(meeting)
381+ if not options.get('skip_attendees', False):
382+ for elem in response.find("attendees").findall("person"):
383+ self.update_attendee_from_launchpad(elem, options)
384+ if not options.get('skip_meetings', False):
385+ for elem in response.find("unscheduled").findall("meeting"):
386+ meeting = self.update_meeting_from_launchpad(elem, options)
387+ if meeting is not None:
388+ meetings.add(meeting)
389 return meetings
390
391 def _get_sprint_info_from_launchpad(self, url):
392@@ -264,26 +266,35 @@
393 sprint_info = ElementTree.parse(export)
394 return sprint_info
395
396- def update_from_launchpad(self):
397+ def update_from_launchpad(self, options={}):
398 """Update from Launchpad data."""
399 in_lp = set()
400 urls = self.launchpad_sprint_import_urls()
401 for url in urls:
402 print "Importing from %s" % url
403 sprint_info = self._get_sprint_info_from_launchpad(url)
404- in_lp |= self.update_from_launchpad_response(sprint_info)
405+ in_lp |= self.update_from_launchpad_response(sprint_info, options)
406+
407+ if not options.get('skip_meetings', False):
408+ in_db = set(m for m in self.meeting_set.exclude(launchpad_blueprint_id__isnull=True))
409+
410+ for extra in in_db.difference(in_lp):
411+ if not len(extra.agenda_set.all()):
412+ print "Marking %s as removed" % extra.name
413+ extra.approved='REMOVED'
414+ extra.save()
415
416 self.last_update = datetime.utcnow()
417 self.save()
418
419- def update_attendee_from_launchpad(self, elem):
420+ def update_attendee_from_launchpad(self, elem, options={}):
421 """Update or create single attendee from Launchpad data."""
422+
423 username = elem.get("name", "")
424 if not username:
425 return
426
427 print "user %s" % username
428-
429 try:
430 attendee = self.attendee_set.get(user__username__exact=username[:30])
431 except ObjectDoesNotExist:
432@@ -298,10 +309,15 @@
433 start=datetime.utcnow(),
434 end=datetime.utcnow())
435
436+
437 attendee.update_from_launchpad(elem)
438
439- def update_meeting_from_launchpad(self, elem):
440+ def update_meeting_from_launchpad(self, elem, options={}):
441 """Update or create single meeting from Launchpad data."""
442+ bp_id = elem.get("id", None)
443+ if not bp_id:
444+ return
445+
446 full_name = elem.get("name", "")
447 if not full_name:
448 return
449@@ -309,10 +325,13 @@
450
451 print "meeting %s" % name
452 meeting = ""
453+
454+ slot_length = options.get('slots', 1)
455+
456 try:
457- meeting = self.meeting_set.get(name__exact=name)
458+ meeting = self.meeting_set.get(launchpad_blueprint_id=bp_id)
459 except ObjectDoesNotExist:
460- meeting = self.meeting_set.create(name=name, title=full_name[:100])
461+ meeting = self.meeting_set.create(name=name, title=full_name[:100], slots=slot_length, launchpad_blueprint_id=bp_id)
462 except:
463 pass
464
465
466=== modified file 'summit/schedule/tests.py'
467--- summit/schedule/tests.py 2012-09-09 21:15:17 +0000
468+++ summit/schedule/tests.py 2012-10-04 13:59:28 +0000
469@@ -1667,7 +1667,7 @@
470
471 def test_update_meeting_trims_name(self):
472 summit = factory.make_one(Summit)
473- elem = LaunchpadExportNode(name="very " * 20 + "longname")
474+ elem = LaunchpadExportNode(name="very " * 20 + "longname", id="42")
475 meeting = summit.update_meeting_from_launchpad(elem)
476 # Names are truncated at 100 chars.
477 expected_name = "very " * 20
478@@ -1678,7 +1678,7 @@
479 summit = factory.make_one(Summit)
480 name = "meeting-name"
481 title = "other-title"
482- elem = LaunchpadExportNode(name=name)
483+ elem = LaunchpadExportNode(name=name, id="42")
484 summit.meeting_set.create(name=name, title=title)
485 meeting = summit.update_meeting_from_launchpad(elem)
486 self.assertEqual(name, meeting.name)
487@@ -1695,7 +1695,7 @@
488 def test_update_from_launchpad_response_handles_no_name(self):
489 summit = factory.make_one(Summit)
490 elem = self.get_basic_launchpad_response()
491- meeting_node = LaunchpadExportNode(name=None)
492+ meeting_node = LaunchpadExportNode(name=None, id="42")
493 elem.find("unscheduled").add_child("meeting", meeting_node)
494 meetings = summit.update_from_launchpad_response(elem)
495 self.assertEqual(set(), meetings)
496@@ -1738,16 +1738,30 @@
497 summit = factory.make_one(Summit)
498 def get_sprint_info(url):
499 elem = self.get_basic_launchpad_response()
500- meeting_node = LaunchpadExportNode(name="foo")
501+ meeting_node = LaunchpadExportNode(name="foo", id="42")
502 elem.find("unscheduled").add_child("meeting", meeting_node)
503 return elem
504 summit._get_sprint_info_from_launchpad = get_sprint_info
505 summit.update_from_launchpad()
506 self.assertEqual(1, summit.meeting_set.all().count())
507
508+ def test_update_from_launchpad_does_renames(self):
509+ summit = factory.make_one(Summit)
510+ meeting = summit.meeting_set.create(name="name", launchpad_blueprint_id="42")
511+ def get_sprint_info(url):
512+ elem = self.get_basic_launchpad_response()
513+ meeting_node = LaunchpadExportNode(name="other", id="42")
514+ elem.find("unscheduled").add_child("meeting", meeting_node)
515+ return elem
516+ summit._get_sprint_info_from_launchpad = get_sprint_info
517+ summit.update_from_launchpad()
518+ # Since both had blueprint id 42, it should just update the existing meeting
519+ self.assertEqual(0, summit.meeting_set.filter(name__exact="name").count())
520+ self.assertEqual(1, summit.meeting_set.filter(name__exact="other").count())
521+
522 def test_update_from_launchpad_deletes_unseen_meetings(self):
523 summit = factory.make_one(Summit)
524- meeting = summit.meeting_set.create(spec_url='test_url', name="name")
525+ meeting = summit.meeting_set.create(spec_url='test_url', name="name", launchpad_blueprint_id="42")
526 def get_sprint_info(url):
527 elem = self.get_basic_launchpad_response()
528 meeting_node = LaunchpadExportNode(name="other")
529@@ -1755,14 +1769,27 @@
530 return elem
531 summit._get_sprint_info_from_launchpad = get_sprint_info
532 summit.update_from_launchpad()
533- self.assertEqual(0, summit.meeting_set.filter(name__exact="name").count())
534+ self.assertEqual(1, summit.meeting_set.filter(name__exact="name").count())
535+ self.assertEqual(0, summit.meeting_set.filter(name__exact="name").exclude(approved='REMOVED').count())
536+
537+ def test_update_from_launchpad_doesnt_delete_meetings_with_spec_url(self):
538+ summit = factory.make_one(Summit)
539+ meeting = summit.meeting_set.create(spec_url='http://example.com/foo', name="name", launchpad_blueprint_id="42")
540+ def get_sprint_info(url):
541+ elem = self.get_basic_launchpad_response()
542+ meeting_node = LaunchpadExportNode(name="other", id="43")
543+ elem.find("unscheduled").add_child("meeting", meeting_node)
544+ return elem
545+ summit._get_sprint_info_from_launchpad = get_sprint_info
546+ summit.update_from_launchpad()
547+ self.assertEqual(1, summit.meeting_set.filter(name__exact="name").count())
548
549 def test_update_from_launchpad_doesnt_delete_meetings_with_no_spec_url(self):
550 summit = factory.make_one(Summit)
551- meeting = summit.meeting_set.create(spec_url='', name="name")
552+ meeting = summit.meeting_set.create(spec_url='', name="name", launchpad_blueprint_id="42")
553 def get_sprint_info(url):
554 elem = self.get_basic_launchpad_response()
555- meeting_node = LaunchpadExportNode(name="other")
556+ meeting_node = LaunchpadExportNode(name="other", id="43")
557 elem.find("unscheduled").add_child("meeting", meeting_node)
558 return elem
559 summit._get_sprint_info_from_launchpad = get_sprint_info

Subscribers

People subscribed via source and target branches