We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Undocumented classification
Hi there, 
I've just noticed that there are some new(?) classifications that are not in the docs. 
These are:
* `TH`
* `CFE`
* `MR`
* `ER`
* `CF`

What do these stand for?

Thanks,
Tiago

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `backend/uclapi/roombookings/helpers.py`
Content:
```
1 from __future__ import unicode_literals
2 
3 import datetime
4 import json
5 from datetime import timedelta
6 
7 import pytz
8 import redis
9 
10 from django.core.exceptions import FieldError, ObjectDoesNotExist
11 from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
12 
13 import ciso8601
14 
15 from common.helpers import PrettyJsonResponse
16 from .models import BookingA, BookingB, Location, Lock, SiteLocation
17 from .api_helpers import generate_token
18 
19 from uclapi.settings import REDIS_UCLAPI_HOST
20 
21 TOKEN_EXPIRY_TIME = 30 * 60
22 
23 def _create_page_token(query, pagination):
24     r = redis.StrictRedis(host=REDIS_UCLAPI_HOST)
25     page_data = {
26         "current_page": 0,
27         "pagination": pagination,
28         "query": json.dumps(query, default=str),
29     }
30     page_token = generate_token()
31     r.set(page_token, json.dumps(page_data), ex=TOKEN_EXPIRY_TIME)
32     return page_token
33 
34 
35 def _get_paginated_bookings(page_token):
36     r = redis.StrictRedis(host=REDIS_UCLAPI_HOST)
37     try:
38         page_data = json.loads(r.get(page_token).decode('ascii'))
39     except (AttributeError, json.decoder.JSONDecodeError):
40         return {
41             "error": "Page token does not exist"
42         }
43 
44     page_data["current_page"] += 1
45     r.set(page_token, json.dumps(page_data), ex=TOKEN_EXPIRY_TIME)
46 
47     pagination = page_data["pagination"]
48     query = json.loads(page_data["query"])
49     bookings, is_last_page = _paginated_result(
50         query,
51         page_data["current_page"],
52         pagination
53     )
54 
55     # if there is a next page
56     bookings["next_page_exists"] = not is_last_page
57 
58     if not is_last_page:
59         # append the page_token to return json
60         bookings["page_token"] = page_token
61 
62     return bookings
63 
64 
65 def _paginated_result(query, page_number, pagination):
66     try:
67         lock = Lock.objects.all()[0]
68         curr = BookingA if not lock.bookingA else BookingB
69         all_bookings = curr.objects.filter(**query).order_by('startdatetime')
70     except FieldError:
71         return {
72             "error": "something wrong with encoded query params"
73         }, False
74 
75     paginator = Paginator(all_bookings, pagination)
76 
77     try:
78         bookings = paginator.page(page_number)
79     except PageNotAnInteger:
80         # give first page
81         bookings = paginator.page(1)
82     except EmptyPage:
83         # return empty page
84         # bookings = paginator.page(paginator.num_pages)
85         bookings = []
86 
87     serialized_bookings = _serialize_bookings(bookings)
88 
89     return (
90         {"bookings": serialized_bookings},
91         (page_number == paginator.num_pages)
92     )
93 
94 
95 def _localize_time(time_string):
96     london_time = pytz.timezone("Europe/London")
97     ret_time = time_string.replace(" ", "+")
98     ret_time = ciso8601.parse_datetime(ret_time)
99     ret_time = ret_time.astimezone(london_time)
100     return ret_time.replace(tzinfo=None)
101 
102 
103 def _round_date(time_string, up=False):
104     """
105     Rounds the datetime to the nearest day.
106     Rounds down by default until up is passed as True in which case
107     it rounds up
108     """
109     date = datetime.date(
110         time_string.year,
111         time_string.month,
112         time_string.day
113     )
114 
115     if up:
116         date += datetime.timedelta(days=1)
117 
118     return date
119 
120 
121 def _parse_datetime(start_time, end_time, search_date):
122     parsed_start_time, parsed_end_time = None, None
123     try:
124         if start_time:
125             # + gets decoded into a space in params
126             parsed_start_time = _localize_time(start_time)
127 
128         if end_time:
129             parsed_end_time = _localize_time(end_time)
130 
131         if not end_time and not start_time:
132             if search_date:
133                 search_date = datetime.datetime.strptime(
134                                             search_date, "%Y%m%d").date()
135                 day_start = datetime.time(0, 0, 1)  # start of the day
136                 day_end = datetime.time(23, 59, 59)  # end of the day
137                 parsed_start_time = datetime.datetime.combine(
138                     search_date,
139                     day_start
140                 )
141                 parsed_end_time = datetime.datetime.combine(
142                     search_date,
143                     day_end
144                 )
145     except (TypeError, NameError, ValueError, AttributeError):
146         return -1, -1, False
147 
148     return parsed_start_time, parsed_end_time, True
149 
150 
151 def _serialize_rooms(room_set):
152     rooms = []
153     for room in room_set:
154         room_to_add = {
155             "roomname": room.roomname,
156             "roomid": room.roomid,
157             "siteid": room.siteid,
158             "sitename": room.sitename,
159             "capacity": room.capacity,
160             "classification": room.roomclass,
161             "automated": room.automated,
162             "location": {
163                 "address": [
164                     room.address1,
165                     room.address2,
166                     room.address3,
167                     room.address4
168                 ]
169             }
170         }
171         try:
172             location = Location.objects.get(
173                 siteid=room.siteid,
174                 roomid=room.roomid
175             )
176             room_to_add['location']['coordinates'] = {
177                 "lat": location.lat,
178                 "lng": location.lng
179             }
180         except Location.DoesNotExist:
181             # no location for this room, try building
182             try:
183                 location = SiteLocation.objects.get(
184                     siteid=room.siteid
185                 )
186                 room_to_add['location']['coordinates'] = {
187                     "lat": location.lat,
188                     "lng": location.lng
189                 }
190             except SiteLocation.DoesNotExist:
191                 # no location for this room
192                 pass
193 
194         rooms.append(room_to_add)
195     return rooms
196 
197 
198 def _serialize_bookings(bookings):
199     ret_bookings = []
200     for bk in bookings:
201         ret_bookings.append({
202             "roomname": bk.roomname,
203             "siteid": bk.siteid,
204             "roomid": bk.roomid,
205             "description": bk.title,
206             "start_time": _kloppify(datetime.datetime.strftime(
207                 bk.startdatetime, "%Y-%m-%dT%H:%M:%S"), bk.startdatetime),
208             "end_time": _kloppify(datetime.datetime.strftime(
209                 bk.finishdatetime, "%Y-%m-%dT%H:%M:%S"), bk.finishdatetime),
210             "contact": bk.condisplayname,
211             "slotid": bk.slotid,
212             "weeknumber": bk.weeknumber,
213             "phone": bk.phone
214         })
215 
216     return ret_bookings
217 
218 
219 def _serialize_equipment(equipment):
220     ret_equipment = []
221 
222     for item in equipment:
223         ret_equipment.append({
224             "type": item.type,
225             "description": item.description,
226             "units": item.units
227         })
228 
229     return ret_equipment
230 
231 
232 def _kloppify(date_string, date):
233     local_time = pytz.timezone('Europe/London')
234 
235     if (local_time.localize(date).dst() > timedelta(0)):
236         return date_string + "+01:00"
237     return date_string + "+00:00"
238 
239 
240 def _return_json_bookings(bookings, rate_limiting_data=None):
241     if "error" in bookings:
242         return PrettyJsonResponse({
243             "ok": False,
244             "error": bookings["error"]
245         }, rate_limiting_data)
246 
247     bookings["ok"] = True
248 
249     return PrettyJsonResponse(bookings, rate_limiting_data)
250 
251 
252 def how_many_seconds_until_midnight():
253     """Returns the number of seconds until midnight."""
254     tomorrow = datetime.datetime.now() + timedelta(days=1)
255     midnight = datetime.datetime(
256         year=tomorrow.year, month=tomorrow.month,
257         day=tomorrow.day, hour=0, minute=0, second=0
258     )
259     return (midnight - datetime.datetime.now()).seconds
260 
261 
262 def _create_map_of_overlapping_bookings(bookings, start, end):
263     """
264     Creates map of room to bookings where each booking overlaps
265     with the given time range
266     """
267     bookings_map = {}
268 
269     for booking in bookings:
270         booking_start = _localize_time(booking["start_time"])
271         booking_end = _localize_time(booking["end_time"])
272 
273         if _overlaps(
274             start1=booking_start,
275             end1=booking_end,
276             start2=start,
277             end2=end
278         ):
279             roomid, siteid = booking["roomid"], booking["siteid"]
280             bookings_map[(roomid, siteid)] = bookings_map.get(
281                 (roomid, siteid), []
282             ) + [booking]
283 
284     return bookings_map
285 
286 
287 def _overlaps(start1, end1, start2, end2):
288     """
289     takes 4 datetimes
290     checks if they overlap
291     """
292     return max(start1, start2) < min(end1, end2)
293 
294 
295 def _filter_for_free_rooms(all_rooms, bookings, start, end):
296     """
297     Find all rooms which don't have any bookings.
298     Args:
299         all_rooms: All available rooms.
300         bookings: All the bookings made in the days of the given time period
301         start: Start time for free rooms
302         end: End time for free rooms
303     """
304     rooms_with_bookings = list(all_rooms)
305     bookings_map = _create_map_of_overlapping_bookings(bookings, start, end)
306 
307     free_rooms = []
308     for room in rooms_with_bookings:
309         roomid, siteid = room["roomid"], room["siteid"]
310         if (
311             (roomid, siteid) not in bookings_map
312             or not bookings_map[(roomid, siteid)]
313         ):
314             # if room doesn't have any overlapping bookings
315             free_rooms.append(room)
316 
317     return free_rooms
318
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/backend/uclapi/roombookings/helpers.py b/backend/uclapi/roombookings/helpers.py
--- a/backend/uclapi/roombookings/helpers.py
+++ b/backend/uclapi/roombookings/helpers.py
@@ -7,7 +7,7 @@
 import pytz
 import redis
 
-from django.core.exceptions import FieldError, ObjectDoesNotExist
+from django.core.exceptions import FieldError
 from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
 
 import ciso8601
@@ -20,6 +20,30 @@
 
 TOKEN_EXPIRY_TIME = 30 * 60
 
+ROOM_TYPE_MAP = {
+    "AN": "Anechoic Chamber",
+    "CI": "Clinic Room",
+    "CF": "Catering Facilities",
+    "CFE": "Cafe",
+    "CL": "Cloakroom",
+    "CR": "Classroom",
+    "ER": "Equipment Room",
+    "IN": "Installation",
+    "LA": "Laboratory",
+    "LB": "Library",
+    "LT": "Lecture Theatre",
+    "MR": "Meeting Room",
+    "OF": "Office",
+    "PC1": "Public Cluster",
+    "PC2": "Public Cluster - Tutorial",
+    "PC3": "Public Cluster - Students",
+    "RC": "Reverberation Chamber",
+    "SS": "Social Space",
+    "STU": "Studio",
+    "TH": "Theatre",
+}
+
+
 def _create_page_token(query, pagination):
     r = redis.StrictRedis(host=REDIS_UCLAPI_HOST)
     page_data = {
@@ -151,6 +175,12 @@
 def _serialize_rooms(room_set):
     rooms = []
     for room in room_set:
+        # Maps room classification to a textual version
+        # e.g. LT => Lecture Theatre
+        classification_name = ROOM_TYPE_MAP.get(
+            room.roomclass,
+            "Unknown Room Type"
+        )
         room_to_add = {
             "roomname": room.roomname,
             "roomid": room.roomid,
@@ -158,6 +188,7 @@
             "sitename": room.sitename,
             "capacity": room.capacity,
             "classification": room.roomclass,
+            "classification_name": classification_name,
             "automated": room.automated,
             "location": {
                 "address": [
@@ -308,8 +339,8 @@
     for room in rooms_with_bookings:
         roomid, siteid = room["roomid"], room["siteid"]
         if (
-            (roomid, siteid) not in bookings_map
-            or not bookings_map[(roomid, siteid)]
+            (roomid, siteid) not in bookings_map or
+            not bookings_map[(roomid, siteid)]
         ):
             # if room doesn't have any overlapping bookings
             free_rooms.append(room)

{"golden_diff": "diff --git a/backend/uclapi/roombookings/helpers.py b/backend/uclapi/roombookings/helpers.py\n--- a/backend/uclapi/roombookings/helpers.py\n+++ b/backend/uclapi/roombookings/helpers.py\n@@ -7,7 +7,7 @@\n import pytz\n import redis\n \n-from django.core.exceptions import FieldError, ObjectDoesNotExist\n+from django.core.exceptions import FieldError\n from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator\n \n import ciso8601\n@@ -20,6 +20,30 @@\n \n TOKEN_EXPIRY_TIME = 30 * 60\n \n+ROOM_TYPE_MAP = {\n+    \"AN\": \"Anechoic Chamber\",\n+    \"CI\": \"Clinic Room\",\n+    \"CF\": \"Catering Facilities\",\n+    \"CFE\": \"Cafe\",\n+    \"CL\": \"Cloakroom\",\n+    \"CR\": \"Classroom\",\n+    \"ER\": \"Equipment Room\",\n+    \"IN\": \"Installation\",\n+    \"LA\": \"Laboratory\",\n+    \"LB\": \"Library\",\n+    \"LT\": \"Lecture Theatre\",\n+    \"MR\": \"Meeting Room\",\n+    \"OF\": \"Office\",\n+    \"PC1\": \"Public Cluster\",\n+    \"PC2\": \"Public Cluster - Tutorial\",\n+    \"PC3\": \"Public Cluster - Students\",\n+    \"RC\": \"Reverberation Chamber\",\n+    \"SS\": \"Social Space\",\n+    \"STU\": \"Studio\",\n+    \"TH\": \"Theatre\",\n+}\n+\n+\n def _create_page_token(query, pagination):\n     r = redis.StrictRedis(host=REDIS_UCLAPI_HOST)\n     page_data = {\n@@ -151,6 +175,12 @@\n def _serialize_rooms(room_set):\n     rooms = []\n     for room in room_set:\n+        # Maps room classification to a textual version\n+        # e.g. LT => Lecture Theatre\n+        classification_name = ROOM_TYPE_MAP.get(\n+            room.roomclass,\n+            \"Unknown Room Type\"\n+        )\n         room_to_add = {\n             \"roomname\": room.roomname,\n             \"roomid\": room.roomid,\n@@ -158,6 +188,7 @@\n             \"sitename\": room.sitename,\n             \"capacity\": room.capacity,\n             \"classification\": room.roomclass,\n+            \"classification_name\": classification_name,\n             \"automated\": room.automated,\n             \"location\": {\n                 \"address\": [\n@@ -308,8 +339,8 @@\n     for room in rooms_with_bookings:\n         roomid, siteid = room[\"roomid\"], room[\"siteid\"]\n         if (\n-            (roomid, siteid) not in bookings_map\n-            or not bookings_map[(roomid, siteid)]\n+            (roomid, siteid) not in bookings_map or\n+            not bookings_map[(roomid, siteid)]\n         ):\n             # if room doesn't have any overlapping bookings\n             free_rooms.append(room)\n", "issue": "Undocumented classification\nHi there, \r\nI've just noticed that there are some new(?) classifications that are not in the docs. \r\nThese are:\r\n* `TH`\r\n* `CFE`\r\n* `MR`\r\n* `ER`\r\n* `CF`\r\n\r\nWhat do these stand for?\r\n\r\nThanks,\r\nTiago\n", "before_files": [{"content": "from __future__ import unicode_literals\n\nimport datetime\nimport json\nfrom datetime import timedelta\n\nimport pytz\nimport redis\n\nfrom django.core.exceptions import FieldError, ObjectDoesNotExist\nfrom django.core.paginator import EmptyPage, PageNotAnInteger, Paginator\n\nimport ciso8601\n\nfrom common.helpers import PrettyJsonResponse\nfrom .models import BookingA, BookingB, Location, Lock, SiteLocation\nfrom .api_helpers import generate_token\n\nfrom uclapi.settings import REDIS_UCLAPI_HOST\n\nTOKEN_EXPIRY_TIME = 30 * 60\n\ndef _create_page_token(query, pagination):\n    r = redis.StrictRedis(host=REDIS_UCLAPI_HOST)\n    page_data = {\n        \"current_page\": 0,\n        \"pagination\": pagination,\n        \"query\": json.dumps(query, default=str),\n    }\n    page_token = generate_token()\n    r.set(page_token, json.dumps(page_data), ex=TOKEN_EXPIRY_TIME)\n    return page_token\n\n\ndef _get_paginated_bookings(page_token):\n    r = redis.StrictRedis(host=REDIS_UCLAPI_HOST)\n    try:\n        page_data = json.loads(r.get(page_token).decode('ascii'))\n    except (AttributeError, json.decoder.JSONDecodeError):\n        return {\n            \"error\": \"Page token does not exist\"\n        }\n\n    page_data[\"current_page\"] += 1\n    r.set(page_token, json.dumps(page_data), ex=TOKEN_EXPIRY_TIME)\n\n    pagination = page_data[\"pagination\"]\n    query = json.loads(page_data[\"query\"])\n    bookings, is_last_page = _paginated_result(\n        query,\n        page_data[\"current_page\"],\n        pagination\n    )\n\n    # if there is a next page\n    bookings[\"next_page_exists\"] = not is_last_page\n\n    if not is_last_page:\n        # append the page_token to return json\n        bookings[\"page_token\"] = page_token\n\n    return bookings\n\n\ndef _paginated_result(query, page_number, pagination):\n    try:\n        lock = Lock.objects.all()[0]\n        curr = BookingA if not lock.bookingA else BookingB\n        all_bookings = curr.objects.filter(**query).order_by('startdatetime')\n    except FieldError:\n        return {\n            \"error\": \"something wrong with encoded query params\"\n        }, False\n\n    paginator = Paginator(all_bookings, pagination)\n\n    try:\n        bookings = paginator.page(page_number)\n    except PageNotAnInteger:\n        # give first page\n        bookings = paginator.page(1)\n    except EmptyPage:\n        # return empty page\n        # bookings = paginator.page(paginator.num_pages)\n        bookings = []\n\n    serialized_bookings = _serialize_bookings(bookings)\n\n    return (\n        {\"bookings\": serialized_bookings},\n        (page_number == paginator.num_pages)\n    )\n\n\ndef _localize_time(time_string):\n    london_time = pytz.timezone(\"Europe/London\")\n    ret_time = time_string.replace(\" \", \"+\")\n    ret_time = ciso8601.parse_datetime(ret_time)\n    ret_time = ret_time.astimezone(london_time)\n    return ret_time.replace(tzinfo=None)\n\n\ndef _round_date(time_string, up=False):\n    \"\"\"\n    Rounds the datetime to the nearest day.\n    Rounds down by default until up is passed as True in which case\n    it rounds up\n    \"\"\"\n    date = datetime.date(\n        time_string.year,\n        time_string.month,\n        time_string.day\n    )\n\n    if up:\n        date += datetime.timedelta(days=1)\n\n    return date\n\n\ndef _parse_datetime(start_time, end_time, search_date):\n    parsed_start_time, parsed_end_time = None, None\n    try:\n        if start_time:\n            # + gets decoded into a space in params\n            parsed_start_time = _localize_time(start_time)\n\n        if end_time:\n            parsed_end_time = _localize_time(end_time)\n\n        if not end_time and not start_time:\n            if search_date:\n                search_date = datetime.datetime.strptime(\n                                            search_date, \"%Y%m%d\").date()\n                day_start = datetime.time(0, 0, 1)  # start of the day\n                day_end = datetime.time(23, 59, 59)  # end of the day\n                parsed_start_time = datetime.datetime.combine(\n                    search_date,\n                    day_start\n                )\n                parsed_end_time = datetime.datetime.combine(\n                    search_date,\n                    day_end\n                )\n    except (TypeError, NameError, ValueError, AttributeError):\n        return -1, -1, False\n\n    return parsed_start_time, parsed_end_time, True\n\n\ndef _serialize_rooms(room_set):\n    rooms = []\n    for room in room_set:\n        room_to_add = {\n            \"roomname\": room.roomname,\n            \"roomid\": room.roomid,\n            \"siteid\": room.siteid,\n            \"sitename\": room.sitename,\n            \"capacity\": room.capacity,\n            \"classification\": room.roomclass,\n            \"automated\": room.automated,\n            \"location\": {\n                \"address\": [\n                    room.address1,\n                    room.address2,\n                    room.address3,\n                    room.address4\n                ]\n            }\n        }\n        try:\n            location = Location.objects.get(\n                siteid=room.siteid,\n                roomid=room.roomid\n            )\n            room_to_add['location']['coordinates'] = {\n                \"lat\": location.lat,\n                \"lng\": location.lng\n            }\n        except Location.DoesNotExist:\n            # no location for this room, try building\n            try:\n                location = SiteLocation.objects.get(\n                    siteid=room.siteid\n                )\n                room_to_add['location']['coordinates'] = {\n                    \"lat\": location.lat,\n                    \"lng\": location.lng\n                }\n            except SiteLocation.DoesNotExist:\n                # no location for this room\n                pass\n\n        rooms.append(room_to_add)\n    return rooms\n\n\ndef _serialize_bookings(bookings):\n    ret_bookings = []\n    for bk in bookings:\n        ret_bookings.append({\n            \"roomname\": bk.roomname,\n            \"siteid\": bk.siteid,\n            \"roomid\": bk.roomid,\n            \"description\": bk.title,\n            \"start_time\": _kloppify(datetime.datetime.strftime(\n                bk.startdatetime, \"%Y-%m-%dT%H:%M:%S\"), bk.startdatetime),\n            \"end_time\": _kloppify(datetime.datetime.strftime(\n                bk.finishdatetime, \"%Y-%m-%dT%H:%M:%S\"), bk.finishdatetime),\n            \"contact\": bk.condisplayname,\n            \"slotid\": bk.slotid,\n            \"weeknumber\": bk.weeknumber,\n            \"phone\": bk.phone\n        })\n\n    return ret_bookings\n\n\ndef _serialize_equipment(equipment):\n    ret_equipment = []\n\n    for item in equipment:\n        ret_equipment.append({\n            \"type\": item.type,\n            \"description\": item.description,\n            \"units\": item.units\n        })\n\n    return ret_equipment\n\n\ndef _kloppify(date_string, date):\n    local_time = pytz.timezone('Europe/London')\n\n    if (local_time.localize(date).dst() > timedelta(0)):\n        return date_string + \"+01:00\"\n    return date_string + \"+00:00\"\n\n\ndef _return_json_bookings(bookings, rate_limiting_data=None):\n    if \"error\" in bookings:\n        return PrettyJsonResponse({\n            \"ok\": False,\n            \"error\": bookings[\"error\"]\n        }, rate_limiting_data)\n\n    bookings[\"ok\"] = True\n\n    return PrettyJsonResponse(bookings, rate_limiting_data)\n\n\ndef how_many_seconds_until_midnight():\n    \"\"\"Returns the number of seconds until midnight.\"\"\"\n    tomorrow = datetime.datetime.now() + timedelta(days=1)\n    midnight = datetime.datetime(\n        year=tomorrow.year, month=tomorrow.month,\n        day=tomorrow.day, hour=0, minute=0, second=0\n    )\n    return (midnight - datetime.datetime.now()).seconds\n\n\ndef _create_map_of_overlapping_bookings(bookings, start, end):\n    \"\"\"\n    Creates map of room to bookings where each booking overlaps\n    with the given time range\n    \"\"\"\n    bookings_map = {}\n\n    for booking in bookings:\n        booking_start = _localize_time(booking[\"start_time\"])\n        booking_end = _localize_time(booking[\"end_time\"])\n\n        if _overlaps(\n            start1=booking_start,\n            end1=booking_end,\n            start2=start,\n            end2=end\n        ):\n            roomid, siteid = booking[\"roomid\"], booking[\"siteid\"]\n            bookings_map[(roomid, siteid)] = bookings_map.get(\n                (roomid, siteid), []\n            ) + [booking]\n\n    return bookings_map\n\n\ndef _overlaps(start1, end1, start2, end2):\n    \"\"\"\n    takes 4 datetimes\n    checks if they overlap\n    \"\"\"\n    return max(start1, start2) < min(end1, end2)\n\n\ndef _filter_for_free_rooms(all_rooms, bookings, start, end):\n    \"\"\"\n    Find all rooms which don't have any bookings.\n    Args:\n        all_rooms: All available rooms.\n        bookings: All the bookings made in the days of the given time period\n        start: Start time for free rooms\n        end: End time for free rooms\n    \"\"\"\n    rooms_with_bookings = list(all_rooms)\n    bookings_map = _create_map_of_overlapping_bookings(bookings, start, end)\n\n    free_rooms = []\n    for room in rooms_with_bookings:\n        roomid, siteid = room[\"roomid\"], room[\"siteid\"]\n        if (\n            (roomid, siteid) not in bookings_map\n            or not bookings_map[(roomid, siteid)]\n        ):\n            # if room doesn't have any overlapping bookings\n            free_rooms.append(room)\n\n    return free_rooms\n", "path": "backend/uclapi/roombookings/helpers.py"}], "after_files": [{"content": "from __future__ import unicode_literals\n\nimport datetime\nimport json\nfrom datetime import timedelta\n\nimport pytz\nimport redis\n\nfrom django.core.exceptions import FieldError\nfrom django.core.paginator import EmptyPage, PageNotAnInteger, Paginator\n\nimport ciso8601\n\nfrom common.helpers import PrettyJsonResponse\nfrom .models import BookingA, BookingB, Location, Lock, SiteLocation\nfrom .api_helpers import generate_token\n\nfrom uclapi.settings import REDIS_UCLAPI_HOST\n\nTOKEN_EXPIRY_TIME = 30 * 60\n\nROOM_TYPE_MAP = {\n    \"AN\": \"Anechoic Chamber\",\n    \"CI\": \"Clinic Room\",\n    \"CF\": \"Catering Facilities\",\n    \"CFE\": \"Cafe\",\n    \"CL\": \"Cloakroom\",\n    \"CR\": \"Classroom\",\n    \"ER\": \"Equipment Room\",\n    \"IN\": \"Installation\",\n    \"LA\": \"Laboratory\",\n    \"LB\": \"Library\",\n    \"LT\": \"Lecture Theatre\",\n    \"MR\": \"Meeting Room\",\n    \"OF\": \"Office\",\n    \"PC1\": \"Public Cluster\",\n    \"PC2\": \"Public Cluster - Tutorial\",\n    \"PC3\": \"Public Cluster - Students\",\n    \"RC\": \"Reverberation Chamber\",\n    \"SS\": \"Social Space\",\n    \"STU\": \"Studio\",\n    \"TH\": \"Theatre\",\n}\n\n\ndef _create_page_token(query, pagination):\n    r = redis.StrictRedis(host=REDIS_UCLAPI_HOST)\n    page_data = {\n        \"current_page\": 0,\n        \"pagination\": pagination,\n        \"query\": json.dumps(query, default=str),\n    }\n    page_token = generate_token()\n    r.set(page_token, json.dumps(page_data), ex=TOKEN_EXPIRY_TIME)\n    return page_token\n\n\ndef _get_paginated_bookings(page_token):\n    r = redis.StrictRedis(host=REDIS_UCLAPI_HOST)\n    try:\n        page_data = json.loads(r.get(page_token).decode('ascii'))\n    except (AttributeError, json.decoder.JSONDecodeError):\n        return {\n            \"error\": \"Page token does not exist\"\n        }\n\n    page_data[\"current_page\"] += 1\n    r.set(page_token, json.dumps(page_data), ex=TOKEN_EXPIRY_TIME)\n\n    pagination = page_data[\"pagination\"]\n    query = json.loads(page_data[\"query\"])\n    bookings, is_last_page = _paginated_result(\n        query,\n        page_data[\"current_page\"],\n        pagination\n    )\n\n    # if there is a next page\n    bookings[\"next_page_exists\"] = not is_last_page\n\n    if not is_last_page:\n        # append the page_token to return json\n        bookings[\"page_token\"] = page_token\n\n    return bookings\n\n\ndef _paginated_result(query, page_number, pagination):\n    try:\n        lock = Lock.objects.all()[0]\n        curr = BookingA if not lock.bookingA else BookingB\n        all_bookings = curr.objects.filter(**query).order_by('startdatetime')\n    except FieldError:\n        return {\n            \"error\": \"something wrong with encoded query params\"\n        }, False\n\n    paginator = Paginator(all_bookings, pagination)\n\n    try:\n        bookings = paginator.page(page_number)\n    except PageNotAnInteger:\n        # give first page\n        bookings = paginator.page(1)\n    except EmptyPage:\n        # return empty page\n        # bookings = paginator.page(paginator.num_pages)\n        bookings = []\n\n    serialized_bookings = _serialize_bookings(bookings)\n\n    return (\n        {\"bookings\": serialized_bookings},\n        (page_number == paginator.num_pages)\n    )\n\n\ndef _localize_time(time_string):\n    london_time = pytz.timezone(\"Europe/London\")\n    ret_time = time_string.replace(\" \", \"+\")\n    ret_time = ciso8601.parse_datetime(ret_time)\n    ret_time = ret_time.astimezone(london_time)\n    return ret_time.replace(tzinfo=None)\n\n\ndef _round_date(time_string, up=False):\n    \"\"\"\n    Rounds the datetime to the nearest day.\n    Rounds down by default until up is passed as True in which case\n    it rounds up\n    \"\"\"\n    date = datetime.date(\n        time_string.year,\n        time_string.month,\n        time_string.day\n    )\n\n    if up:\n        date += datetime.timedelta(days=1)\n\n    return date\n\n\ndef _parse_datetime(start_time, end_time, search_date):\n    parsed_start_time, parsed_end_time = None, None\n    try:\n        if start_time:\n            # + gets decoded into a space in params\n            parsed_start_time = _localize_time(start_time)\n\n        if end_time:\n            parsed_end_time = _localize_time(end_time)\n\n        if not end_time and not start_time:\n            if search_date:\n                search_date = datetime.datetime.strptime(\n                                            search_date, \"%Y%m%d\").date()\n                day_start = datetime.time(0, 0, 1)  # start of the day\n                day_end = datetime.time(23, 59, 59)  # end of the day\n                parsed_start_time = datetime.datetime.combine(\n                    search_date,\n                    day_start\n                )\n                parsed_end_time = datetime.datetime.combine(\n                    search_date,\n                    day_end\n                )\n    except (TypeError, NameError, ValueError, AttributeError):\n        return -1, -1, False\n\n    return parsed_start_time, parsed_end_time, True\n\n\ndef _serialize_rooms(room_set):\n    rooms = []\n    for room in room_set:\n        # Maps room classification to a textual version\n        # e.g. LT => Lecture Theatre\n        classification_name = ROOM_TYPE_MAP.get(\n            room.roomclass,\n            \"Unknown Room Type\"\n        )\n        room_to_add = {\n            \"roomname\": room.roomname,\n            \"roomid\": room.roomid,\n            \"siteid\": room.siteid,\n            \"sitename\": room.sitename,\n            \"capacity\": room.capacity,\n            \"classification\": room.roomclass,\n            \"classification_name\": classification_name,\n            \"automated\": room.automated,\n            \"location\": {\n                \"address\": [\n                    room.address1,\n                    room.address2,\n                    room.address3,\n                    room.address4\n                ]\n            }\n        }\n        try:\n            location = Location.objects.get(\n                siteid=room.siteid,\n                roomid=room.roomid\n            )\n            room_to_add['location']['coordinates'] = {\n                \"lat\": location.lat,\n                \"lng\": location.lng\n            }\n        except Location.DoesNotExist:\n            # no location for this room, try building\n            try:\n                location = SiteLocation.objects.get(\n                    siteid=room.siteid\n                )\n                room_to_add['location']['coordinates'] = {\n                    \"lat\": location.lat,\n                    \"lng\": location.lng\n                }\n            except SiteLocation.DoesNotExist:\n                # no location for this room\n                pass\n\n        rooms.append(room_to_add)\n    return rooms\n\n\ndef _serialize_bookings(bookings):\n    ret_bookings = []\n    for bk in bookings:\n        ret_bookings.append({\n            \"roomname\": bk.roomname,\n            \"siteid\": bk.siteid,\n            \"roomid\": bk.roomid,\n            \"description\": bk.title,\n            \"start_time\": _kloppify(datetime.datetime.strftime(\n                bk.startdatetime, \"%Y-%m-%dT%H:%M:%S\"), bk.startdatetime),\n            \"end_time\": _kloppify(datetime.datetime.strftime(\n                bk.finishdatetime, \"%Y-%m-%dT%H:%M:%S\"), bk.finishdatetime),\n            \"contact\": bk.condisplayname,\n            \"slotid\": bk.slotid,\n            \"weeknumber\": bk.weeknumber,\n            \"phone\": bk.phone\n        })\n\n    return ret_bookings\n\n\ndef _serialize_equipment(equipment):\n    ret_equipment = []\n\n    for item in equipment:\n        ret_equipment.append({\n            \"type\": item.type,\n            \"description\": item.description,\n            \"units\": item.units\n        })\n\n    return ret_equipment\n\n\ndef _kloppify(date_string, date):\n    local_time = pytz.timezone('Europe/London')\n\n    if (local_time.localize(date).dst() > timedelta(0)):\n        return date_string + \"+01:00\"\n    return date_string + \"+00:00\"\n\n\ndef _return_json_bookings(bookings, rate_limiting_data=None):\n    if \"error\" in bookings:\n        return PrettyJsonResponse({\n            \"ok\": False,\n            \"error\": bookings[\"error\"]\n        }, rate_limiting_data)\n\n    bookings[\"ok\"] = True\n\n    return PrettyJsonResponse(bookings, rate_limiting_data)\n\n\ndef how_many_seconds_until_midnight():\n    \"\"\"Returns the number of seconds until midnight.\"\"\"\n    tomorrow = datetime.datetime.now() + timedelta(days=1)\n    midnight = datetime.datetime(\n        year=tomorrow.year, month=tomorrow.month,\n        day=tomorrow.day, hour=0, minute=0, second=0\n    )\n    return (midnight - datetime.datetime.now()).seconds\n\n\ndef _create_map_of_overlapping_bookings(bookings, start, end):\n    \"\"\"\n    Creates map of room to bookings where each booking overlaps\n    with the given time range\n    \"\"\"\n    bookings_map = {}\n\n    for booking in bookings:\n        booking_start = _localize_time(booking[\"start_time\"])\n        booking_end = _localize_time(booking[\"end_time\"])\n\n        if _overlaps(\n            start1=booking_start,\n            end1=booking_end,\n            start2=start,\n            end2=end\n        ):\n            roomid, siteid = booking[\"roomid\"], booking[\"siteid\"]\n            bookings_map[(roomid, siteid)] = bookings_map.get(\n                (roomid, siteid), []\n            ) + [booking]\n\n    return bookings_map\n\n\ndef _overlaps(start1, end1, start2, end2):\n    \"\"\"\n    takes 4 datetimes\n    checks if they overlap\n    \"\"\"\n    return max(start1, start2) < min(end1, end2)\n\n\ndef _filter_for_free_rooms(all_rooms, bookings, start, end):\n    \"\"\"\n    Find all rooms which don't have any bookings.\n    Args:\n        all_rooms: All available rooms.\n        bookings: All the bookings made in the days of the given time period\n        start: Start time for free rooms\n        end: End time for free rooms\n    \"\"\"\n    rooms_with_bookings = list(all_rooms)\n    bookings_map = _create_map_of_overlapping_bookings(bookings, start, end)\n\n    free_rooms = []\n    for room in rooms_with_bookings:\n        roomid, siteid = room[\"roomid\"], room[\"siteid\"]\n        if (\n            (roomid, siteid) not in bookings_map or\n            not bookings_map[(roomid, siteid)]\n        ):\n            # if room doesn't have any overlapping bookings\n            free_rooms.append(room)\n\n    return free_rooms\n", "path": "backend/uclapi/roombookings/helpers.py"}]}