views.py 15.9 KB
Newer Older
David Haynes's avatar
David Haynes committed
1
2
#!/usr/bin/env python
# -*- coding: utf-8 -*-
3
"""
4
5
api/views.py

6
Rest Framework Class Views
David Haynes's avatar
David Haynes committed
7
8
9

Each ViewSet determines what data is returned when an API endpoint is hit. In
addition, we define filtering and documentation for each of these endpoints. 
10
"""
11
12
13
# Python std. lib. imports
import datetime

14
# App Imports
David Haynes's avatar
David Haynes committed
15
from .models import Facility, OpenTime, Category, Schedule, Location, Alert
16
from .serializers import (CategorySerializer, FacilitySerializer,
David Haynes's avatar
David Haynes committed
17
                          ScheduleSerializer, OpenTimeSerializer,
David Haynes's avatar
David Haynes committed
18
                          LocationSerializer, AlertSerializer)
19

20
# Other Imports
David Haynes's avatar
David Haynes committed
21
22
from rest_framework import viewsets, filters
from django_filters.rest_framework import DjangoFilterBackend
23

David Haynes's avatar
David Haynes committed
24
25
class AlertViewSet(viewsets.ReadOnlyModelViewSet):
    """
26
27
28
    Some type of notification that is displayed to clients that conveys a message.

    Past examples include:
David Haynes's avatar
David Haynes committed
29
30
31
32
33

    - Random closings
    - Modified schedules being in effect
    - Election reminder
    - Advertising for other SRCT projects
34
35
36

    Alerts last for a period of time until the information is no longer dank.

David Haynes's avatar
David Haynes committed
37
38
39
40
41
42
    ---

    ## Default behavior

    [GET /api/alerts/](/api/alerts/?format=json)

David Haynes's avatar
David Haynes committed
43
    Return all active Alert objects.
44

David Haynes's avatar
David Haynes committed
45
46
47
48
49
    ## Built-in query parameters

    ### **Search**

    [GET /api/alerts/?search=](/api/alerts/?search=&format=json)
50
51
52

    Query parameter that returns objects that match a keyword provided in the search.

David Haynes's avatar
David Haynes committed
53
54
55
56
57
58
59
60
61
62
    **Example Usage**

    [GET /api/alerts/?search=srct](/api/alerts/?search=srct&format=json)

    Return any Alert objects that have the string "srct" located in one of its fields.

    ### **Ordering**

    [GET /api/alerts/?ordering=](/api/alerts/?ordering=&format=json)

63
64
    Query parameter that orders the returned objects based on the provided field to order by.

David Haynes's avatar
David Haynes committed
65
66
67
    **Example Usage**

    [GET /api/alerts/?ordering=urgency_tag](/api/alerts/?ordering=urgency_tag&format=json)
68

David Haynes's avatar
David Haynes committed
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
    Return all Alert objects ordered by urgency_tag ascending.

    [GET /api/alerts/?ordering=-urgency_tag](/api/alerts/?ordering=-urgency_tag&format=json)

    Return all Alert objects ordered by urgency_tag descending.

    ### **Field filtering**

    You can query directly against any field.

    **Example Usage**

    [GET /api/alerts/?urgency_tag=major](/api/alerts/?urgency_tag=major&format=json)

    Return all Alert objects that are tagged as "major" urgency.
David Haynes's avatar
David Haynes committed
84
85
86
87
88
89
90
91

    ## Custom query parameters

    ### **all_alerts**

    [GET /api/alerts/?all_alerts](/api/alerts/?all_alerts&format=json)

    Return all Alert objects.
David Haynes's avatar
David Haynes committed
92
    """
93
94
95
96
97
98
99
100
101
102
    # All model fields that are available for filtering
    FILTER_FIELDS = (
        # Alert fields
        'urgency_tag',
        'message',
        'start_datetime',
        'end_datetime'
    )

    # Associate a serializer with the ViewSet
David Haynes's avatar
David Haynes committed
103
104
    serializer_class = AlertSerializer

105
106
107
108
109
110
111
    # Setup filtering
    filter_backends = (filters.SearchFilter, DjangoFilterBackend,
                       filters.OrderingFilter, )
    search_fields = FILTER_FIELDS
    ordering_fields = FILTER_FIELDS
    filter_fields = FILTER_FIELDS

David Haynes's avatar
David Haynes committed
112
    def get_queryset(self):
113
114
115
116
        """
        Handle incoming GET requests and enumerate objects that get returned by
        the API.
        """
David Haynes's avatar
David Haynes committed
117
118
119
120
121
122
123
124
125
        # Define ?all_alerts
        all_alerts = self.request.query_params.get('all_alerts', None)

        # Return all Alert objects if requested
        if all_alerts is not None:
            return Alert.objects.all()
        # Default behavior
        else:
            # Enumerate all Alert objects that are active
David Haynes's avatar
David Haynes committed
126
127
128
129
130
            alertable = [
                alert.pk
                for alert in Alert.objects.all()
                if alert.is_active()
            ]
David Haynes's avatar
David Haynes committed
131
132
            # Return active Alerts
            return Alert.objects.filter(pk__in=alertable)
David Haynes's avatar
David Haynes committed
133

134
class CategoryViewSet(viewsets.ReadOnlyModelViewSet):
135
    """
136
137
    A Category is a grouping of Facilities that serve a common/similar purpose.

David Haynes's avatar
David Haynes committed
138
139
140
141
142
143
144
145
146
147
148
    Examples:

    - Dining
    - Gyms
    - Study areas (Libraries, The Ridge, JC, etc)

    ---

    ## Default behavior

    [GET /api/categories/](/api/categories/)
149

David Haynes's avatar
David Haynes committed
150
    Return all Category objects.
151

David Haynes's avatar
David Haynes committed
152
153
154
155
156
    ## Built-in query parameters

    ### **Search**

    [GET /api/categories/?search=](/api/categories/?search=&format=json)
157
158
159

    Query parameter that returns objects that match a keyword provided in the search.

David Haynes's avatar
David Haynes committed
160
161
162
163
164
165
166
167
168
169
    **Example Usage**

    [GET /api/categories/?search=din](/api/categories/?search=din&format=json)

    Return all Category objects that have a field that matches the string "din".

    ### **Ordering**

    [GET /api/categories/?ordering=](/api/categories/?ordering=&format=json)

170
171
    Query parameter that orders the returned objects based on the provided field to order by.

David Haynes's avatar
David Haynes committed
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
    **Example Usage**

    [GET /api/categories/?ordering=name](/api/categories/?ordering=name&format=json)

    Return all Category objects filtered by name ascending.

    [GET /api/categories/?ordering=-name](/api/categories/?ordering=-name&format=json)

    Return all Category objects filtered by name descending.

    ### **Field filtering**

    You can query directly against any field.

    **Example Usage**
187

David Haynes's avatar
David Haynes committed
188
189
190
    [GET /api/categories/?name=dining](/api/categories/?name=dining&format=json)

    Return the Category object that is named "dining".
191
    """
192
193
194
195
196
197
198
    # All model fields that are available for filtering
    FILTER_FIELDS = (
        # Category fields
        'name',
    )

    # Associate a serializer with the ViewSet
199
200
    serializer_class = CategorySerializer

201
202
203
204
205
206
207
    # Setup filtering
    filter_backends = (filters.SearchFilter, DjangoFilterBackend,
                       filters.OrderingFilter, )
    search_fields = FILTER_FIELDS
    ordering_fields = FILTER_FIELDS
    filter_fields = FILTER_FIELDS

David Haynes's avatar
David Haynes committed
208
    def get_queryset(self):
209
210
211
212
        """
        Handle incoming GET requests and enumerate objects that get returned by
        the API.
        """
David Haynes's avatar
David Haynes committed
213
214
        return Category.objects.all()

David Haynes's avatar
David Haynes committed
215
216
class LocationViewSet(viewsets.ReadOnlyModelViewSet):
    """
217
218
    Represents a specific location that a Facility can be found.

David Haynes's avatar
David Haynes committed
219
220
221
222
223
224
    ---

    ## Default behavior

    [GET /api/locations/](/api/locations/?format=json)

David Haynes's avatar
David Haynes committed
225
    Return all Location objects.
226

David Haynes's avatar
David Haynes committed
227
228
229
230
231
    ## Built-in query parameters

    ### **Search**

    [GET /api/locations/?search=](/api/locations/?search=&format=json)
232
233
234

    Query parameter that returns objects that match a keyword provided in the search.

David Haynes's avatar
David Haynes committed
235
236
237
238
239
240
241
242
243
244
245
    **Example Usage**

    [GET /api/locations/?search=johnson](/api/locations/?search=johnson&format=json)

    Return all Location objects that have a field that matches the "johnson" string.

    ### **Ordering**

    Order the returned objects based on the provided field.

    [GET /api/locations/?ordering=](/api/locations/?ordering=&format=json)
246

David Haynes's avatar
David Haynes committed
247
    **Example Usage**
248

David Haynes's avatar
David Haynes committed
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
    [GET /api/locations/?ordering=building](/api/locations/?ordering=building&format=json)

    Return all Location objects ordered by building name ascending.

    [GET /api/locations/?ordering=-building](/api/locations/?ordering=-building&format=json)

    Return all Location objects ordered by building name descending.

    ### **Field filtering**

    You can query directly against any field.

    **Example Usage**

    [GET /api/locations/?building=Johnson+Center](/api/locations/?building=Johnson+Center&format=json)

    Return all Location objects located in the "Johnson Center" building.
David Haynes's avatar
David Haynes committed
266
    """
267
268
269
270
    # All model fields that are available for filtering
    FILTER_FIELDS = (
        # Location fields
        'building',
271
        'friendly_building',
272
        'address',
273
274
        'on_campus',
        'campus_region'
275
276
277
    )

    # Associate a serializer with the ViewSet
David Haynes's avatar
David Haynes committed
278
279
    serializer_class = LocationSerializer

280
281
282
283
284
285
286
    # Setup filtering
    filter_backends = (filters.SearchFilter, DjangoFilterBackend,
                       filters.OrderingFilter, )
    search_fields = FILTER_FIELDS
    ordering_fields = FILTER_FIELDS
    filter_fields = FILTER_FIELDS

David Haynes's avatar
David Haynes committed
287
    def get_queryset(self):
288
289
290
291
        """
        Handle incoming GET requests and enumerate objects that get returned by
        the API.
        """
David Haynes's avatar
David Haynes committed
292
293
        return Location.objects.all()

294
class FacilityViewSet(viewsets.ReadOnlyModelViewSet):
295
    """
296
297
    A Facility is some type of establishment that has a schedule of open hours and a location that serves a specific purpose that can be categorized.

David Haynes's avatar
David Haynes committed
298
299
300
301
302
303
304
305
306
307
308
    ---

    ## Default behavior

    [GET /api/facilities/](/api/facilities/?format=json)

    Return all Facility objects. Additionally, we filter out stale special_schedules to reduce client side calculations.

    ## Built-in query parameters

    ### **Search**
309

David Haynes's avatar
David Haynes committed
310
    [GET /api/facilities/?search=](/api/facilities/?search=&format=json)
David Haynes's avatar
David Haynes committed
311
312
313

    Query parameter that returns objects that match a keyword provided in the search.

David Haynes's avatar
David Haynes committed
314
315
316
317
318
319
320
321
322
323
    **Example Usage**

    [GET /api/facilities/?search=south](/api/facilities/?search=south&format=json)

    Return all Facility objects that have a field that matches the string "south".

    ### **Ordering**

    [GET /api/facilities/?ordering=](/api/facilities/?ordering=&format=json)

David Haynes's avatar
David Haynes committed
324
325
    Query parameter that orders the returned objects based on the provided field to order by.

David Haynes's avatar
David Haynes committed
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
    **Example Usage**

    [GET /api/facilities/?ordering=facility_name](/api/facilities/?ordering=facility_name&format=json)

    Return all Facility objects ordered by facility_name ascending.

    [GET /api/facilities/?ordering=-facility_name](/api/facilities/?ordering=-facility_name&format=json)

    Return all Facility objects ordered by facility_name descending.

    ### **Field filtering**

    You can query directly against any field.

    **Example Usage**
David Haynes's avatar
David Haynes committed
341

David Haynes's avatar
David Haynes committed
342
    [GET /api/facilities/?facility_name=Southside](/api/facilities/?facility_name=Southside&format=json)
David Haynes's avatar
David Haynes committed
343

David Haynes's avatar
David Haynes committed
344
    Return the Facility object that has "Southside" as its name.
David Haynes's avatar
David Haynes committed
345

David Haynes's avatar
David Haynes committed
346
    ## Custom query parameters
David Haynes's avatar
David Haynes committed
347

David Haynes's avatar
David Haynes committed
348
349
350
351
352
353
354
355
356
357
358
    ### **open_now**

    [GET /api/facilities/?open_now](/api/facilities/?open_now&format=json)

    Only return open Facility objects.

    ### **closed_now**

    [GET /api/facilities/?closed_now](/api/facilities/?closed_now&format=json)

    Only return closed Facility objects.
359
    """
360
    # All model fields that are available for filtering
361
362
363
    FILTER_FIELDS = (
        # Facility fields
        'facility_name',
David Haynes's avatar
David Haynes committed
364
        'facility_classifier',
365
        'logo',
366
        'tapingo_url',
367
        'note',
368
        'facility_product_tags__name',
David Haynes's avatar
David Haynes committed
369
        'facility_labels__name',
370
371
372
373
        # Category fields
        'facility_category__name',
        # Location fields
        'facility_location__building',
374
        'facility_location__friendly_building',
375
376
        'facility_location__address',
        'facility_location__on_campus',
377
        'facility_location__campus_region',
378
379
380
381
        # Schedule fields
        'main_schedule__name',
        'main_schedule__valid_start',
        'main_schedule__valid_end',
David Haynes's avatar
David Haynes committed
382
        'main_schedule__twenty_four_hours',
383
        'main_schedule__schedule_for_removal',
384
385
386
        'special_schedules__name',
        'special_schedules__valid_start',
        'special_schedules__valid_end',
387
388
        'special_schedules__twenty_four_hours',
        'special_schedules__schedule_for_removal'
389
390
    )

David Haynes's avatar
David Haynes committed
391
    # Associate a serializer with the ViewSet
392
    serializer_class = FacilitySerializer
393
394

    # Setup filtering
395
396
397
398
399
    filter_backends = (filters.SearchFilter, DjangoFilterBackend,
                       filters.OrderingFilter, )
    search_fields = FILTER_FIELDS
    ordering_fields = FILTER_FIELDS
    filter_fields = FILTER_FIELDS
400
    lookup_field = 'slug'
401

402
    def get_queryset(self):
403
        """
404
405
        Handle incoming GET requests and enumerate objects that get returned by
        the API.
406
        """
David Haynes's avatar
David Haynes committed
407
        # Define ?open_now
David Haynes's avatar
David Haynes committed
408
        open_now = self.request.query_params.get('open_now', None)
David Haynes's avatar
David Haynes committed
409
410
        # Define ?closed_now
        closed_now = self.request.query_params.get('closed_now', None)
411
412
413
414
415

        # Clean the schedules in every Facility
        for facility in Facility.objects.all():
            facility.clean_schedules()

David Haynes's avatar
David Haynes committed
416
        if open_now is not None or closed_now is not None:
417
            # List of all open facilities
David Haynes's avatar
David Haynes committed
418
419
420
421
422
            open_facilities = [
                facility.pk
                for facility in Facility.objects.all()
                if facility.is_open()
            ]
423
424
            # Return all Facility objects with the primary keys located in the
            # open_facilities list
David Haynes's avatar
David Haynes committed
425
426
427
428
429
430
            if open_now:
                return Facility.objects.filter(pk__in=open_facilities)
            # Return all Facility objects with the primary keys not located in
            # the open_facilities list
            elif closed_now:
                return Facility.objects.exclude(pk__in=open_facilities)
431
        # Default behavior
432
        else:
David Haynes's avatar
David Haynes committed
433
            return Facility.objects.all()
434

435
class ScheduleViewSet(viewsets.ModelViewSet):
436
    """
437
438
    A period of time between two dates that represents the beginning and end of a "schedule" or rather, a collection of open times for a facility.

David Haynes's avatar
David Haynes committed
439
440
441
442
443
444
    ---

    ## Default behavior

    [GET /api/schedules/](/api/schedules/?format=json)

445
    Return all Schedule objects that have not expired. (ie. end_date is before today)
446

David Haynes's avatar
David Haynes committed
447
448
449
450
451
    ## Built-in query parameters

    ### **Search**

    [GET /api/schedules/?search=](/api/schedules/?search=&format=json)
452
453
454

    Query parameter that returns objects that match a keyword provided in the search.

David Haynes's avatar
David Haynes committed
455
456
457
458
459
460
461
462
463
464
    **Example Usage**

    [GET /api/schedules/?search=south](/api/schedules/?search=south&format=json)

    Return all Schedule objects that have a field matching the string "south".

    ### **Ordering**

    [GET /api/schedules/?ordering=](/api/schedules/?ordering=&format=json)

465
466
    Query parameter that orders the returned objects based on the provided field to order by.

David Haynes's avatar
David Haynes committed
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
    **Example Usage**

    [GET /api/schedules/?ordering=name](/api/schedules/?ordering=name&format=json)

    Return all Schedule objects ordered by name ascending.

    [GET /api/schedules/?ordering=-name](/api/schedules/?ordering=-name&format=json)

    [GET /api/schedules/?ordering=name](/api/schedules/?ordering=name&format=json)

    Return all Schedule objects ordered by name ascending.
    Return all Schedule objects ordered by name descending.

    ### **Field filtering**

    You can query directly against any field.

    **Example Usage**

    [GET /api/schedules/?name=southside_main](/api/schedules/?name=southside_main&format=json)
487

David Haynes's avatar
David Haynes committed
488
    Return the Schedule object that has "southside_main" as its name.
489
    """
490
491
492
493
494
495
    # All model fields that are available for filtering
    FILTER_FIELDS = (
        # Schedule fields
        'name',
        'valid_start',
        'valid_end',
496
        'twenty_four_hours',
497
498
        'schedule_for_removal',
        'promote_to_main'
499
500
501
    )

    # Associate a serializer with the ViewSet
502
503
    serializer_class = ScheduleSerializer

504
505
506
507
508
509
510
    # Setup filtering
    filter_backends = (filters.SearchFilter, DjangoFilterBackend,
                       filters.OrderingFilter, )
    search_fields = FILTER_FIELDS
    ordering_fields = FILTER_FIELDS
    filter_fields = FILTER_FIELDS

David Haynes's avatar
David Haynes committed
511
    def get_queryset(self):
512
513
514
515
516
        """
        Handle incoming GET requests and enumerate objects that get returned by
        the API.
        """
        # List of all schedules that are outdated
David Haynes's avatar
David Haynes committed
517
518
519
520
521
        filter_old_schedules = [
            schedule.pk
            for schedule in Schedule.objects.all()
            # If the schedule ended before today
            if schedule.valid_end and schedule.valid_start
522
            if schedule.valid_end < datetime.datetime.now(schedule.valid_end.tzinfo)
David Haynes's avatar
David Haynes committed
523
        ]
524
525
        # Return all Schedule objects that have not expired
        return Schedule.objects.exclude(pk__in=filter_old_schedules)
David Haynes's avatar
David Haynes committed
526

527
class OpenTimeViewSet(viewsets.ModelViewSet):
528
    """
529
530
531
532
533
534
    Represents a time period when a Facility is open.

    Monday = 0, Sunday = 6.

    These objects are returned within a larger Schedule object and thus are not
    an endpoint that is query-able, so just return everything when requested.
535
    """
536
    # Associate a serializer with the ViewSet
537
    serializer_class = OpenTimeSerializer
David Haynes's avatar
David Haynes committed
538
539

    def get_queryset(self):
540
541
542
543
        """
        Handle incoming GET requests and enumerate objects that get returned by
        the API.
        """
544
        return OpenTime.objects.all()