Skip to content

Commit ca36d50

Browse files
committed
Merged in ^/personal/henrik/6.25.2-floorplans@11546, which provides basic meeting venue floorplan and room location support, including JSON API access to floorplan images and room location. Floorplan data for IETF 96 in Berlin is also provided in a data migration. Not part of this release, but planned for a later release, is support for arrows pointing out rooms, and secretariat support for room location input.
- Legacy-Id: 11547
2 parents e01298c + fa184df commit ca36d50

27 files changed

Lines changed: 641 additions & 90 deletions

ietf/doc/models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -575,7 +575,7 @@ class Meta:
575575
def save_document_in_history(doc):
576576
"""This should be called before saving changes to a Document instance,
577577
so that the DocHistory entries contain all previous states, while
578-
the Group entry contain the current state. XXX TODO: Call this
578+
the Document entry contain the current state. XXX TODO: Call this
579579
directly from Document.save(), and add event listeners for save()
580580
on related objects so we can save as needed when they change, too.
581581
"""

ietf/doc/resources.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Autogenerated by the makeresources management command 2015-10-19 12:29 PDT
22
from tastypie.resources import ModelResource
33
from ietf.api import ToOneField
4-
from tastypie.fields import ToManyField, CharField # pyflakes:ignore
4+
from tastypie.fields import ToManyField, CharField
55
from tastypie.constants import ALL, ALL_WITH_RELATIONS # pyflakes:ignore
66
from tastypie.cache import SimpleCache
77

ietf/doc/templatetags/ietf_filters.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -616,3 +616,7 @@ def format_timedelta(timedelta):
616616
hours, remainder = divmod(s, 3600)
617617
minutes, seconds = divmod(remainder, 60)
618618
return '{hours:02d}:{minutes:02d}'.format(hours=hours,minutes=minutes)
619+
620+
@register.filter()
621+
def nbsp(value):
622+
return mark_safe(" ".join(value.split(' ')))

ietf/meeting/admin.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
from django.contrib import admin
22

3-
from ietf.meeting.models import Meeting, Room, Session, TimeSlot, Constraint, Schedule, SchedTimeSessAssignment, ResourceAssociation
3+
from ietf.meeting.models import (Meeting, Room, Session, TimeSlot, Constraint, Schedule,
4+
SchedTimeSessAssignment, ResourceAssociation, FloorPlan)
45

56
class RoomAdmin(admin.ModelAdmin):
6-
list_display = ["id", "meeting", "name", "capacity", ]
7+
list_display = ["id", "meeting", "name", "capacity", "x1", "y1", "x2", "y2", ]
78
list_filter = ["meeting"]
89
ordering = ["-meeting"]
910

@@ -98,3 +99,8 @@ class SchedTimeSessAssignmentAdmin(admin.ModelAdmin):
9899
class ResourceAssociationAdmin(admin.ModelAdmin):
99100
list_display = ["desc", "icon", "desc", ]
100101
admin.site.register(ResourceAssociation, ResourceAssociationAdmin)
102+
103+
class FloorPlanAdmin(admin.ModelAdmin):
104+
list_display = ['id', 'meeting', 'name', 'order', 'image', ]
105+
raw_id_fields = ['meeting', ]
106+
admin.site.register(FloorPlan, FloorPlanAdmin)

ietf/meeting/factories.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
import datetime
44

55
from django.db.models import Max
6+
from django.core.files.base import ContentFile
67

7-
from ietf.meeting.models import Meeting, Session, Schedule, TimeSlot, SessionPresentation
8+
from ietf.meeting.models import Meeting, Session, Schedule, TimeSlot, SessionPresentation, FloorPlan
89
from ietf.group.factories import GroupFactory
910
from ietf.person.factories import PersonFactory
1011

@@ -106,3 +107,18 @@ class Meta:
106107
def rev(self):
107108
return self.document.rev
108109

110+
class FloorPlanFactory(factory.DjangoModelFactory):
111+
class Meta:
112+
model = FloorPlan
113+
114+
name = factory.Sequence(lambda n: u'Venue Floor %d' % n)
115+
meeting = factory.SubFactory(MeetingFactory)
116+
order = factory.Sequence(lambda n: n)
117+
image = factory.LazyAttribute(
118+
lambda _: ContentFile(
119+
factory.django.ImageField()._make_data(
120+
{'width': 1024, 'height': 768}
121+
), 'floorplan.jpg'
122+
)
123+
)
124+
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# -*- coding: utf-8 -*-
2+
from __future__ import unicode_literals
3+
4+
from django.db import models, migrations
5+
import ietf.utils.storage
6+
import ietf.meeting.models
7+
8+
9+
class Migration(migrations.Migration):
10+
11+
dependencies = [
12+
('meeting', '0024_migrate_interim_meetings'),
13+
]
14+
15+
operations = [
16+
migrations.CreateModel(
17+
name='FloorPlan',
18+
fields=[
19+
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
20+
('name', models.CharField(max_length=255)),
21+
('order', models.SmallIntegerField()),
22+
('image', models.ImageField(default=None, storage=ietf.utils.storage.NoLocationMigrationFileSystemStorage(location=None), upload_to=ietf.meeting.models.floorplan_path, blank=True)),
23+
('meeting', models.ForeignKey(to='meeting.Meeting')),
24+
],
25+
options={
26+
},
27+
bases=(models.Model,),
28+
),
29+
migrations.AddField(
30+
model_name='room',
31+
name='floorplan',
32+
field=models.ForeignKey(default=None, blank=True, to='meeting.FloorPlan', null=True),
33+
preserve_default=True,
34+
),
35+
migrations.AddField(
36+
model_name='room',
37+
name='x1',
38+
field=models.SmallIntegerField(default=None, null=True, blank=True),
39+
preserve_default=True,
40+
),
41+
migrations.AddField(
42+
model_name='room',
43+
name='x2',
44+
field=models.SmallIntegerField(default=None, null=True, blank=True),
45+
preserve_default=True,
46+
),
47+
migrations.AddField(
48+
model_name='room',
49+
name='y1',
50+
field=models.SmallIntegerField(default=None, null=True, blank=True),
51+
preserve_default=True,
52+
),
53+
migrations.AddField(
54+
model_name='room',
55+
name='y2',
56+
field=models.SmallIntegerField(default=None, null=True, blank=True),
57+
preserve_default=True,
58+
),
59+
]
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# -*- coding: utf-8 -*-
2+
from __future__ import unicode_literals
3+
4+
from django.db import migrations
5+
6+
floors = [
7+
(1, "Berlin Intercontinental Floor 1", 1, 'floor/floorplan-96-berlin-intercontinental-floor-1.jpg'),
8+
(2, "Berlin Intercontinental Floor 1", 2, 'floor/floorplan-96-berlin-intercontinental-floor-2.jpg'),
9+
(3, "Berlin Intercontinental Floor 1", 14, 'floor/floorplan-96-berlin-intercontinental-floor-14.jpg'),
10+
]
11+
12+
rooms = [
13+
("Bellevue", 1, 176, 1348, 324, 1526),
14+
("Kaminzimmer", 1, 696, 820, 812, 1038),
15+
("Charlottenburg I", 1, 374, 320, 528, 400),
16+
("Charlottenburg II/III", 1, 374, 172, 528, 316),
17+
("Chess", 2, 238, 614, 336, 782),
18+
("Glienicke", 1, 228, 1251, 324, 1310),
19+
("Hugos 360", 3, 801, 1346, 976, 1509),
20+
("King", 2, 802, 1389, 890, 1508),
21+
("Koepenick I/II", 1, 370, 453, 458, 602),
22+
("Lincke", 2, 40, 99, 532, 166),
23+
("Potsdam I", 1, 1228, 790, 1550, 994),
24+
("Potsdam I/III", 1, 1017, 792, 1550, 994),
25+
("Potsdam II", 1, 1311, 1036, 1536, 1142),
26+
("Potsdam III", 1, 1017, 792, 1228, 987),
27+
("Rook", 2, 915, 1150, 1004, 1269),
28+
("Schoeneberg", 1, 369, 42, 534, 126),
29+
("Tegel", 1, 201, 1088, 326, 1184),
30+
("Tiergarten", 1, 240, 612, 334, 780),
31+
("Wintergarten/Pavillion", 1, 466, 1038, 711, 1504),
32+
]
33+
34+
def forward(apps, schema_editor):
35+
FloorPlan = apps.get_model('meeting','FloorPlan')
36+
Room = apps.get_model('meeting','Room')
37+
Meeting = apps.get_model('meeting','Meeting')
38+
meeting = Meeting.objects.get(number='96')
39+
for item in floors:
40+
id, name, order, image = item
41+
f = FloorPlan(id=id, name=name, meeting=meeting, order=order, image=image)
42+
f.save()
43+
44+
for item in rooms:
45+
name, floor_id, x1, y1, x2, y2 = item
46+
room = Room.objects.get(name=name, meeting=meeting)
47+
room.floorplan_id = floor_id
48+
room.x1 = x1
49+
room.y1 = y1
50+
room.x2 = x2
51+
room.y2 = y2
52+
room.save()
53+
54+
def backward(apps, schema_editor):
55+
pass
56+
57+
class Migration(migrations.Migration):
58+
59+
dependencies = [
60+
('meeting', '0025_add_floorplan_and_room_coordinates'),
61+
]
62+
63+
operations = [
64+
migrations.RunPython(forward,backward)
65+
]

ietf/meeting/models.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from ietf.group.utils import can_manage_materials
2626
from ietf.name.models import MeetingTypeName, TimeSlotTypeName, SessionStatusName, ConstraintName, RoomResourceName
2727
from ietf.person.models import Person
28+
from ietf.utils.storage import NoLocationMigrationFileSystemStorage
2829

2930
countries = pytz.country_names.items()
3031
countries.sort(lambda x,y: cmp(x[1], y[1]))
@@ -274,6 +275,8 @@ def updated(self):
274275
class Meta:
275276
ordering = ["-date", "id"]
276277

278+
# === Rooms, Resources, Floorplans =============================================
279+
277280
class ResourceAssociation(models.Model):
278281
name = models.ForeignKey(RoomResourceName)
279282
#url = models.UrlField() # not sure what this was for.
@@ -298,6 +301,15 @@ class Room(models.Model):
298301
capacity = models.IntegerField(null=True, blank=True)
299302
resources = models.ManyToManyField(ResourceAssociation, blank = True)
300303
session_types = models.ManyToManyField(TimeSlotTypeName, blank = True)
304+
# floorplan-related properties
305+
floorplan = models.ForeignKey('FloorPlan', null=True, blank=True, default=None)
306+
# floorplan: room pixel position : (0,0) is top left of image, (xd, yd)
307+
# is room width, height.
308+
x1 = models.SmallIntegerField(null=True, blank=True, default=None)
309+
y1 = models.SmallIntegerField(null=True, blank=True, default=None)
310+
x2 = models.SmallIntegerField(null=True, blank=True, default=None)
311+
y2 = models.SmallIntegerField(null=True, blank=True, default=None)
312+
# end floorplan-related stuff
301313

302314
def __unicode__(self):
303315
return "%s size: %s" % (self.name, self.capacity)
@@ -332,6 +344,36 @@ def json_dict(self, host_scheme):
332344
'capacity': self.capacity,
333345
}
334346

347+
def left(self):
348+
return min(self.x1, self.x2) if (self.x1 and self.x2) else 0
349+
def top(self):
350+
return min(self.y1, self.y2) if (self.y1 and self.y2) else 0
351+
def right(self):
352+
return max(self.x1, self.x2) if (self.x1 and self.x2) else 0
353+
def bottom(self):
354+
return max(self.y1, self.y2) if (self.y1 and self.y2) else 0
355+
def functional_display_name(self):
356+
if not self.functional_name:
357+
return ""
358+
if self.functional_name.lower().startswith('breakout'):
359+
return ""
360+
if self.functional_name[0].isdigit():
361+
return ""
362+
return self.functional_name
363+
class Meta:
364+
ordering = ["-meeting", "name"]
365+
366+
def floorplan_path(instance, filename):
367+
root, ext = os.path.splitext(filename)
368+
return u"%s/floorplan-%s-%s%s" % (settings.FLOORPLAN_MEDIA_DIR, instance.meeting.number, slugify(instance.name), ext)
369+
370+
class FloorPlan(models.Model):
371+
name = models.CharField(max_length=255)
372+
meeting = models.ForeignKey(Meeting)
373+
order = models.SmallIntegerField()
374+
image = models.ImageField(storage=NoLocationMigrationFileSystemStorage(), upload_to=floorplan_path, blank=True, default=None)
375+
376+
# === Schedules, Sessions, Timeslots and Assignments ===========================
335377

336378
class TimeSlot(models.Model):
337379
"""
@@ -1307,3 +1349,4 @@ def badness_fast(self, timeslot, scheduleslot, session_pk_list):
13071349
if self.badness_test(1):
13081350
self.badness_log(1, "badgroup: %s badness = %u\n" % (self.group.acronym, badness))
13091351
return badness
1352+

ietf/meeting/resources.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from ietf import api
99

1010
from ietf.meeting.models import ( Meeting, ResourceAssociation, Constraint, Room, Schedule, Session,
11-
TimeSlot, SchedTimeSessAssignment, SessionPresentation )
11+
TimeSlot, SchedTimeSessAssignment, SessionPresentation, FloorPlan )
1212

1313
from ietf.name.resources import MeetingTypeNameResource
1414
class MeetingResource(ModelResource):
@@ -83,11 +83,28 @@ class Meta:
8383
}
8484
api.meeting.register(ConstraintResource())
8585

86+
class FloorPlanResource(ModelResource):
87+
meeting = ToOneField(MeetingResource, 'meeting')
88+
class Meta:
89+
queryset = FloorPlan.objects.all()
90+
serializer = api.Serializer()
91+
cache = SimpleCache()
92+
#resource_name = 'floorplan'
93+
filtering = {
94+
"id": ALL,
95+
"name": ALL,
96+
"order": ALL,
97+
"image": ALL,
98+
"meeting": ALL_WITH_RELATIONS,
99+
}
100+
api.meeting.register(FloorPlanResource())
101+
86102
from ietf.name.resources import TimeSlotTypeNameResource
87103
class RoomResource(ModelResource):
88104
meeting = ToOneField(MeetingResource, 'meeting')
89105
resources = ToManyField(ResourceAssociationResource, 'resources', null=True)
90106
session_types = ToManyField(TimeSlotTypeNameResource, 'session_types', null=True)
107+
floorplan = ToOneField(FloorPlanResource, 'floorplan', null=True)
91108
class Meta:
92109
cache = SimpleCache()
93110
queryset = Room.objects.all()
@@ -101,6 +118,7 @@ class Meta:
101118
"meeting": ALL_WITH_RELATIONS,
102119
"resources": ALL_WITH_RELATIONS,
103120
"session_types": ALL_WITH_RELATIONS,
121+
"floorplan": ALL_WITH_RELATIONS,
104122
}
105123
api.meeting.register(RoomResource())
106124

ietf/meeting/test_data.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ def make_meeting_test_data():
4040
unofficial_schedule = Schedule.objects.create(meeting=meeting, owner=plainman, name="test-unofficial-agenda", visible=True, public=True)
4141
pname = RoomResourceName.objects.create(name='projector',slug='proj')
4242
projector = ResourceAssociation.objects.create(name=pname,icon="notfound.png",desc="Basic projector")
43-
room = Room.objects.create(meeting=meeting, name="Test Room", capacity=123)
43+
room = Room.objects.create(meeting=meeting, name="Test Room", capacity=123, functional_name="Testing Ground")
4444
breakfast_room = Room.objects.create(meeting=meeting, name="Breakfast Room", capacity=40)
4545
room.session_types.add("session")
4646
breakfast_room.session_types.add("lead")

0 commit comments

Comments
 (0)