views.py 15.9 KB
Newer Older
1
"""
2
3
api/views.py

4
Rest Framework Class Views
David Haynes's avatar
David Haynes committed
5
6
7

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. 
8
"""
9
10
11
12
# Future Imports
from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

13
14
15
# Python std. lib. imports
import datetime

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

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

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

    Past examples include:
David Haynes's avatar
David Haynes committed
31
32
33
34
35

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

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

David Haynes's avatar
David Haynes committed
39
40
41
42
43
44
    ---

    ## Default behavior

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

David Haynes's avatar
David Haynes committed
45
    Return all active Alert objects.
46

David Haynes's avatar
David Haynes committed
47
48
49
50
51
    ## Built-in query parameters

    ### **Search**

    [GET /api/alerts/?search=](/api/alerts/?search=&format=json)
52
53
54

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

David Haynes's avatar
David Haynes committed
55
56
57
58
59
60
61
62
63
64
    **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)

65
66
    Query parameter that orders the returned objects based on the provided field to order by.

David Haynes's avatar
David Haynes committed
67
68
69
    **Example Usage**

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

David Haynes's avatar
David Haynes committed
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
    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
86
87
88
89
90
91
92
93

    ## 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
94
    """
95
96
97
98
99
100
101
102
103
104
    # 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
105
106
    serializer_class = AlertSerializer

107
108
109
110
111
112
113
    # 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
114
    def get_queryset(self):
115
116
117
118
        """
        Handle incoming GET requests and enumerate objects that get returned by
        the API.
        """
David Haynes's avatar
David Haynes committed
119
120
121
122
123
124
125
126
127
        # 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
128
129
130
131
132
            alertable = [
                alert.pk
                for alert in Alert.objects.all()
                if alert.is_active()
            ]
David Haynes's avatar
David Haynes committed
133
134
            # Return active Alerts
            return Alert.objects.filter(pk__in=alertable)
David Haynes's avatar
David Haynes committed
135

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

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

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

    ---

    ## Default behavior

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

David Haynes's avatar
David Haynes committed
152
    Return all Category objects.
153

David Haynes's avatar
David Haynes committed
154
155
156
157
158
    ## Built-in query parameters

    ### **Search**

    [GET /api/categories/?search=](/api/categories/?search=&format=json)
159
160
161

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

David Haynes's avatar
David Haynes committed
162
163
164
165
166
167
168
169
170
171
    **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)

172
173
    Query parameter that orders the returned objects based on the provided field to order by.

David Haynes's avatar
David Haynes committed
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
    **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**
189

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

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

    # Associate a serializer with the ViewSet
201
202
    serializer_class = CategorySerializer

203
204
205
206
207
208
209
    # 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
210
    def get_queryset(self):
211
212
213
214
        """
        Handle incoming GET requests and enumerate objects that get returned by
        the API.
        """
David Haynes's avatar
David Haynes committed
215
216
        return Category.objects.all()

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

David Haynes's avatar
David Haynes committed
221
222
223
224
225
226
    ---

    ## Default behavior

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

David Haynes's avatar
David Haynes committed
227
    Return all Location objects.
228

David Haynes's avatar
David Haynes committed
229
230
231
232
233
    ## Built-in query parameters

    ### **Search**

    [GET /api/locations/?search=](/api/locations/?search=&format=json)
234
235
236

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

David Haynes's avatar
David Haynes committed
237
238
239
240
241
242
243
244
245
246
247
    **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)
248

David Haynes's avatar
David Haynes committed
249
    **Example Usage**
250

David Haynes's avatar
David Haynes committed
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
    [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
268
    """
269
270
271
272
    # All model fields that are available for filtering
    FILTER_FIELDS = (
        # Location fields
        'building',
273
        'friendly_building',
274
        'address',
275
276
        'on_campus',
        'campus_region'
277
278
279
    )

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

282
283
284
285
286
287
288
    # 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
289
    def get_queryset(self):
290
291
292
293
        """
        Handle incoming GET requests and enumerate objects that get returned by
        the API.
        """
David Haynes's avatar
David Haynes committed
294
295
        return Location.objects.all()

296
class FacilityViewSet(viewsets.ReadOnlyModelViewSet):
297
    """
298
299
    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
300
301
302
303
304
305
306
307
308
309
310
    ---

    ## 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**
311

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

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

David Haynes's avatar
David Haynes committed
316
317
318
319
320
321
322
323
324
325
    **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
326
327
    Query parameter that orders the returned objects based on the provided field to order by.

David Haynes's avatar
David Haynes committed
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
    **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
343

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

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

David Haynes's avatar
David Haynes committed
348
    ## Custom query parameters
David Haynes's avatar
David Haynes committed
349

David Haynes's avatar
David Haynes committed
350
351
352
353
354
355
356
357
358
359
360
    ### **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.
361
    """
362
    # All model fields that are available for filtering
363
364
365
    FILTER_FIELDS = (
        # Facility fields
        'facility_name',
366
        'logo',
367
368
369
370
371
372
        'tapingo_url',
        'facility_product_tags__name',
        # Category fields
        'facility_category__name',
        # Location fields
        'facility_location__building',
373
        'facility_location__friendly_building',
374
375
        'facility_location__address',
        'facility_location__on_campus',
376
        'facility_location__campus_region',
377
378
379
380
        # Schedule fields
        'main_schedule__name',
        'main_schedule__valid_start',
        'main_schedule__valid_end',
David Haynes's avatar
David Haynes committed
381
        'main_schedule__twenty_four_hours',
382
        'main_schedule__schedule_for_removal',
383
384
385
        'special_schedules__name',
        'special_schedules__valid_start',
        'special_schedules__valid_end',
386
387
        'special_schedules__twenty_four_hours',
        'special_schedules__schedule_for_removal'
388
389
    )

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

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

401
    def get_queryset(self):
402
        """
403
404
        Handle incoming GET requests and enumerate objects that get returned by
        the API.
405
        """
David Haynes's avatar
David Haynes committed
406
        # Define ?open_now
David Haynes's avatar
David Haynes committed
407
        open_now = self.request.query_params.get('open_now', None)
David Haynes's avatar
David Haynes committed
408
409
410
        # Define ?closed_now
        closed_now = self.request.query_params.get('closed_now', None)
        if open_now is not None or closed_now is not None:
411
            # List of all open facilities
David Haynes's avatar
David Haynes committed
412
413
414
415
416
            open_facilities = [
                facility.pk
                for facility in Facility.objects.all()
                if facility.is_open()
            ]
417
418
            # Return all Facility objects with the primary keys located in the
            # open_facilities list
David Haynes's avatar
David Haynes committed
419
420
421
422
423
424
            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)
425
        # Default behavior
426
        else:
427
428
429
            for facility in Facility.objects.all():
                # Remove all special_schedules that have expired
                facility.clean_special_schedules()
David Haynes's avatar
David Haynes committed
430
            return Facility.objects.all()
431

432
class ScheduleViewSet(viewsets.ModelViewSet):
433
    """
434
435
    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
436
437
438
439
440
441
    ---

    ## Default behavior

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

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

David Haynes's avatar
David Haynes committed
444
445
446
447
448
    ## Built-in query parameters

    ### **Search**

    [GET /api/schedules/?search=](/api/schedules/?search=&format=json)
449
450
451

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

David Haynes's avatar
David Haynes committed
452
453
454
455
456
457
458
459
460
461
    **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)

462
463
    Query parameter that orders the returned objects based on the provided field to order by.

David Haynes's avatar
David Haynes committed
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
    **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)
484

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

    # Associate a serializer with the ViewSet
498
499
    serializer_class = ScheduleSerializer

500
501
502
503
504
505
506
    # 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
507
    def get_queryset(self):
508
509
510
511
512
        """
        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
513
514
515
516
517
        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
518
            if schedule.valid_end < datetime.datetime.now(schedule.valid_end.tzinfo)
David Haynes's avatar
David Haynes committed
519
        ]
520
521
        # Return all Schedule objects that have not expired
        return Schedule.objects.exclude(pk__in=filter_old_schedules)
David Haynes's avatar
David Haynes committed
522

523
class OpenTimeViewSet(viewsets.ModelViewSet):
524
    """
525
526
527
528
529
530
    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.
531
    """
532
    # Associate a serializer with the ViewSet
533
    serializer_class = OpenTimeSerializer
David Haynes's avatar
David Haynes committed
534
535

    def get_queryset(self):
536
537
538
539
        """
        Handle incoming GET requests and enumerate objects that get returned by
        the API.
        """
540
        return OpenTime.objects.all()