From 9e5d99095445012335fc7a530bbbcd40923f96d9 Mon Sep 17 00:00:00 2001
From: Henrik Levkowetz <henrik@levkowetz.com>
Date: Thu, 7 Jul 2016 12:37:09 +0000
Subject: [PATCH] Added meeting FloorPlan model and added location parameters
 and ordering to the Room model.  - Legacy-Id: 11540

---
 ietf/meeting/admin.py                         | 10 +++-
 ...0025_add_floorplan_and_room_coordinates.py | 59 +++++++++++++++++++
 ietf/meeting/models.py                        | 43 ++++++++++++++
 ietf/meeting/resources.py                     | 20 ++++++-
 4 files changed, 129 insertions(+), 3 deletions(-)
 create mode 100644 ietf/meeting/migrations/0025_add_floorplan_and_room_coordinates.py

diff --git a/ietf/meeting/admin.py b/ietf/meeting/admin.py
index 31a842c72..e575eeef0 100644
--- a/ietf/meeting/admin.py
+++ b/ietf/meeting/admin.py
@@ -1,9 +1,10 @@
 from django.contrib import admin
 
-from ietf.meeting.models import Meeting, Room, Session, TimeSlot, Constraint, Schedule, SchedTimeSessAssignment, ResourceAssociation
+from ietf.meeting.models import (Meeting, Room, Session, TimeSlot, Constraint, Schedule,
+    SchedTimeSessAssignment, ResourceAssociation, FloorPlan)
 
 class RoomAdmin(admin.ModelAdmin):
-    list_display = ["id", "meeting", "name", "capacity", ]
+    list_display = ["id", "meeting", "name", "capacity", "x1", "y1", "x2", "y2", ]
     list_filter = ["meeting"]
     ordering = ["-meeting"]
 
@@ -98,3 +99,8 @@ admin.site.register(SchedTimeSessAssignment, SchedTimeSessAssignmentAdmin)
 class ResourceAssociationAdmin(admin.ModelAdmin):
     list_display = ["desc", "icon", "desc", ]
 admin.site.register(ResourceAssociation, ResourceAssociationAdmin)
+
+class FloorPlanAdmin(admin.ModelAdmin):
+    list_display = ['id', 'meeting', 'name', 'order', 'image', ]
+    raw_id_fields = ['meeting', ]
+admin.site.register(FloorPlan, FloorPlanAdmin)
diff --git a/ietf/meeting/migrations/0025_add_floorplan_and_room_coordinates.py b/ietf/meeting/migrations/0025_add_floorplan_and_room_coordinates.py
new file mode 100644
index 000000000..8c41e29df
--- /dev/null
+++ b/ietf/meeting/migrations/0025_add_floorplan_and_room_coordinates.py
@@ -0,0 +1,59 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+import ietf.utils.storage
+import ietf.meeting.models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('meeting', '0024_migrate_interim_meetings'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='FloorPlan',
+            fields=[
+                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+                ('name', models.CharField(max_length=255)),
+                ('order', models.SmallIntegerField()),
+                ('image', models.ImageField(default=None, storage=ietf.utils.storage.NoLocationMigrationFileSystemStorage(location=None), upload_to=ietf.meeting.models.floorplan_path, blank=True)),
+                ('meeting', models.ForeignKey(to='meeting.Meeting')),
+            ],
+            options={
+            },
+            bases=(models.Model,),
+        ),
+        migrations.AddField(
+            model_name='room',
+            name='floorplan',
+            field=models.ForeignKey(default=None, blank=True, to='meeting.FloorPlan', null=True),
+            preserve_default=True,
+        ),
+        migrations.AddField(
+            model_name='room',
+            name='x1',
+            field=models.SmallIntegerField(default=None, null=True, blank=True),
+            preserve_default=True,
+        ),
+        migrations.AddField(
+            model_name='room',
+            name='x2',
+            field=models.SmallIntegerField(default=None, null=True, blank=True),
+            preserve_default=True,
+        ),
+        migrations.AddField(
+            model_name='room',
+            name='y1',
+            field=models.SmallIntegerField(default=None, null=True, blank=True),
+            preserve_default=True,
+        ),
+        migrations.AddField(
+            model_name='room',
+            name='y2',
+            field=models.SmallIntegerField(default=None, null=True, blank=True),
+            preserve_default=True,
+        ),
+    ]
diff --git a/ietf/meeting/models.py b/ietf/meeting/models.py
index 5db515988..67bc8fb21 100644
--- a/ietf/meeting/models.py
+++ b/ietf/meeting/models.py
@@ -25,6 +25,7 @@ from ietf.group.models import Group
 from ietf.group.utils import can_manage_materials
 from ietf.name.models import MeetingTypeName, TimeSlotTypeName, SessionStatusName, ConstraintName, RoomResourceName
 from ietf.person.models import Person
+from ietf.utils.storage import NoLocationMigrationFileSystemStorage
 
 countries = pytz.country_names.items()
 countries.sort(lambda x,y: cmp(x[1], y[1]))
@@ -274,6 +275,8 @@ class Meeting(models.Model):
     class Meta:
         ordering = ["-date", "id"]
 
+# === Rooms, Resources, Floorplans =============================================
+
 class ResourceAssociation(models.Model):
     name = models.ForeignKey(RoomResourceName)
     #url  = models.UrlField()       # not sure what this was for.
@@ -298,6 +301,15 @@ class Room(models.Model):
     capacity = models.IntegerField(null=True, blank=True)
     resources = models.ManyToManyField(ResourceAssociation, blank = True)
     session_types = models.ManyToManyField(TimeSlotTypeName, blank = True)
+    # floorplan-related properties
+    floorplan = models.ForeignKey('FloorPlan', null=True, blank=True, default=None)
+    # floorplan: room pixel position : (0,0) is top left of image, (xd, yd)
+    # is room width, height.
+    x1 = models.SmallIntegerField(null=True, blank=True, default=None)
+    y1 = models.SmallIntegerField(null=True, blank=True, default=None)
+    x2 = models.SmallIntegerField(null=True, blank=True, default=None)
+    y2 = models.SmallIntegerField(null=True, blank=True, default=None)
+    # end floorplan-related stuff
 
     def __unicode__(self):
         return "%s size: %s" % (self.name, self.capacity)
@@ -332,6 +344,36 @@ class Room(models.Model):
             'capacity':             self.capacity,
             }
 
+    def left(self):
+        return min(self.x1, self.x2) if (self.x1 and self.x2) else 0
+    def top(self):
+        return min(self.y1, self.y2) if (self.y1 and self.y2) else 0
+    def right(self):
+        return max(self.x1, self.x2) if (self.x1 and self.x2) else 0
+    def bottom(self):
+        return max(self.y1, self.y2) if (self.y1 and self.y2) else 0
+    def functional_display_name(self):
+        if not self.functional_name:
+            return ""
+        if self.functional_name.lower().startswith('breakout'):
+            return ""
+        if self.functional_name[0].isdigit():
+            return ""
+        return self.functional_name
+    class Meta:
+        ordering = ["-meeting", "name"]
+
+def floorplan_path(instance, filename):
+    root, ext = os.path.splitext(filename)
+    return u"%s/floorplan-%s-%s%s" % (settings.FLOORPLAN_MEDIA_DIR, instance.meeting.number, slugify(instance.name), ext)
+
+class FloorPlan(models.Model):
+    name    = models.CharField(max_length=255)
+    meeting = models.ForeignKey(Meeting)
+    order   = models.SmallIntegerField()
+    image   = models.ImageField(storage=NoLocationMigrationFileSystemStorage(), upload_to=floorplan_path, blank=True, default=None)
+
+# === Schedules, Sessions, Timeslots and Assignments ===========================
 
 class TimeSlot(models.Model):
     """
@@ -1307,3 +1349,4 @@ class Session(models.Model):
         if self.badness_test(1):
             self.badness_log(1, "badgroup: %s badness = %u\n" % (self.group.acronym, badness))
         return badness
+
diff --git a/ietf/meeting/resources.py b/ietf/meeting/resources.py
index e83e6bbca..2ba4456b6 100644
--- a/ietf/meeting/resources.py
+++ b/ietf/meeting/resources.py
@@ -8,7 +8,7 @@ from tastypie.cache import SimpleCache
 from ietf import api
 
 from ietf.meeting.models import ( Meeting, ResourceAssociation, Constraint, Room, Schedule, Session,
-                                TimeSlot, SchedTimeSessAssignment, SessionPresentation )
+                                TimeSlot, SchedTimeSessAssignment, SessionPresentation, FloorPlan )
 
 from ietf.name.resources import MeetingTypeNameResource
 class MeetingResource(ModelResource):
@@ -83,11 +83,28 @@ class ConstraintResource(ModelResource):
         }
 api.meeting.register(ConstraintResource())
 
+class FloorPlanResource(ModelResource):
+    meeting          = ToOneField(MeetingResource, 'meeting')
+    class Meta:
+        queryset = FloorPlan.objects.all()
+        serializer = api.Serializer()
+        cache = SimpleCache()
+        #resource_name = 'floorplan'
+        filtering = { 
+            "id": ALL,
+            "name": ALL,
+            "order": ALL,
+            "image": ALL,
+            "meeting": ALL_WITH_RELATIONS,
+        }
+api.meeting.register(FloorPlanResource())
+
 from ietf.name.resources import TimeSlotTypeNameResource
 class RoomResource(ModelResource):
     meeting          = ToOneField(MeetingResource, 'meeting')
     resources        = ToManyField(ResourceAssociationResource, 'resources', null=True)
     session_types    = ToManyField(TimeSlotTypeNameResource, 'session_types', null=True)
+    floorplan        = ToOneField(FloorPlanResource, 'floorplan', null=True)
     class Meta:
         cache = SimpleCache()
         queryset = Room.objects.all()
@@ -101,6 +118,7 @@ class RoomResource(ModelResource):
             "meeting": ALL_WITH_RELATIONS,
             "resources": ALL_WITH_RELATIONS,
             "session_types": ALL_WITH_RELATIONS,
+            "floorplan": ALL_WITH_RELATIONS,
         }
 api.meeting.register(RoomResource())