views.py 15.8 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
17
18
19
20
21
22
23
from .serializers import (
    CategorySerializer,
    FacilitySerializer,
    ScheduleSerializer,
    OpenTimeSerializer,
    LocationSerializer,
    AlertSerializer,
)
24

25
# Other Imports
David Haynes's avatar
David Haynes committed
26
27
from rest_framework import viewsets, filters
from django_filters.rest_framework import DjangoFilterBackend
28

29

David Haynes's avatar
David Haynes committed
30
31
class AlertViewSet(viewsets.ReadOnlyModelViewSet):
    """
32
33
34
    Some type of notification that is displayed to clients that conveys a message.

    Past examples include:
David Haynes's avatar
David Haynes committed
35
36
37
38
39

    - Random closings
    - Modified schedules being in effect
    - Election reminder
    - Advertising for other SRCT projects
40
41
42

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

David Haynes's avatar
David Haynes committed
43
44
45
46
47
48
    ---

    ## Default behavior

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

David Haynes's avatar
David Haynes committed
49
    Return all active Alert objects.
50

David Haynes's avatar
David Haynes committed
51
52
53
54
55
    ## Built-in query parameters

    ### **Search**

    [GET /api/alerts/?search=](/api/alerts/?search=&format=json)
56
57
58

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

David Haynes's avatar
David Haynes committed
59
60
61
62
63
64
65
66
67
68
    **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)

69
70
    Query parameter that orders the returned objects based on the provided field to order by.

David Haynes's avatar
David Haynes committed
71
72
73
    **Example Usage**

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

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

    ## 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
98
    """
99

100
101
102
    # All model fields that are available for filtering
    FILTER_FIELDS = (
        # Alert fields
103
104
105
106
        "urgency_tag",
        "message",
        "start_datetime",
        "end_datetime",
107
108
109
    )

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

112
    # Setup filtering
113
114
115
116
117
    filter_backends = (
        filters.SearchFilter,
        DjangoFilterBackend,
        filters.OrderingFilter,
    )
118
119
120
121
    search_fields = FILTER_FIELDS
    ordering_fields = FILTER_FIELDS
    filter_fields = FILTER_FIELDS

David Haynes's avatar
David Haynes committed
122
    def get_queryset(self):
123
124
125
126
        """
        Handle incoming GET requests and enumerate objects that get returned by
        the API.
        """
David Haynes's avatar
David Haynes committed
127
        # Define ?all_alerts
128
        all_alerts = self.request.query_params.get("all_alerts", None)
David Haynes's avatar
David Haynes committed
129
130
131
132
133
134
135

        # 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
136
            alertable = [alert.pk for alert in Alert.objects.all() if alert.is_active()]
David Haynes's avatar
David Haynes committed
137
138
            # Return active Alerts
            return Alert.objects.filter(pk__in=alertable)
David Haynes's avatar
David Haynes committed
139

140

141
class CategoryViewSet(viewsets.ReadOnlyModelViewSet):
142
    """
143
144
    A Category is a grouping of Facilities that serve a common/similar purpose.

David Haynes's avatar
David Haynes committed
145
146
147
148
149
150
151
152
153
154
155
    Examples:

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

    ---

    ## Default behavior

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

David Haynes's avatar
David Haynes committed
157
    Return all Category objects.
158

David Haynes's avatar
David Haynes committed
159
160
161
162
163
    ## Built-in query parameters

    ### **Search**

    [GET /api/categories/?search=](/api/categories/?search=&format=json)
164
165
166

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

David Haynes's avatar
David Haynes committed
167
168
169
170
171
172
173
174
175
176
    **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)

177
178
    Query parameter that orders the returned objects based on the provided field to order by.

David Haynes's avatar
David Haynes committed
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
    **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**
194

David Haynes's avatar
David Haynes committed
195
196
197
    [GET /api/categories/?name=dining](/api/categories/?name=dining&format=json)

    Return the Category object that is named "dining".
198
    """
199

200
201
202
    # All model fields that are available for filtering
    FILTER_FIELDS = (
        # Category fields
203
        "name",
204
205
206
    )

    # Associate a serializer with the ViewSet
207
208
    serializer_class = CategorySerializer

209
    # Setup filtering
210
211
212
213
214
    filter_backends = (
        filters.SearchFilter,
        DjangoFilterBackend,
        filters.OrderingFilter,
    )
215
216
217
218
    search_fields = FILTER_FIELDS
    ordering_fields = FILTER_FIELDS
    filter_fields = FILTER_FIELDS

David Haynes's avatar
David Haynes committed
219
    def get_queryset(self):
220
221
222
223
        """
        Handle incoming GET requests and enumerate objects that get returned by
        the API.
        """
David Haynes's avatar
David Haynes committed
224
225
        return Category.objects.all()

226

David Haynes's avatar
David Haynes committed
227
228
class LocationViewSet(viewsets.ReadOnlyModelViewSet):
    """
229
230
    Represents a specific location that a Facility can be found.

David Haynes's avatar
David Haynes committed
231
232
233
234
235
236
    ---

    ## Default behavior

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

David Haynes's avatar
David Haynes committed
237
    Return all Location objects.
238

David Haynes's avatar
David Haynes committed
239
240
241
242
243
    ## Built-in query parameters

    ### **Search**

    [GET /api/locations/?search=](/api/locations/?search=&format=json)
244
245
246

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

David Haynes's avatar
David Haynes committed
247
248
249
250
251
252
253
254
255
256
257
    **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)
258

David Haynes's avatar
David Haynes committed
259
    **Example Usage**
260

David Haynes's avatar
David Haynes committed
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
    [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
278
    """
279

280
281
282
    # All model fields that are available for filtering
    FILTER_FIELDS = (
        # Location fields
283
284
285
286
287
        "building",
        "friendly_building",
        "address",
        "on_campus",
        "campus_region",
288
289
290
    )

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

293
    # Setup filtering
294
295
296
297
298
    filter_backends = (
        filters.SearchFilter,
        DjangoFilterBackend,
        filters.OrderingFilter,
    )
299
300
301
302
    search_fields = FILTER_FIELDS
    ordering_fields = FILTER_FIELDS
    filter_fields = FILTER_FIELDS

David Haynes's avatar
David Haynes committed
303
    def get_queryset(self):
304
305
306
307
        """
        Handle incoming GET requests and enumerate objects that get returned by
        the API.
        """
David Haynes's avatar
David Haynes committed
308
309
        return Location.objects.all()

310

311
class FacilityViewSet(viewsets.ReadOnlyModelViewSet):
312
    """
313
314
    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
315
316
317
318
319
320
321
322
323
324
325
    ---

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

David Haynes's avatar
David Haynes committed
327
    [GET /api/facilities/?search=](/api/facilities/?search=&format=json)
David Haynes's avatar
David Haynes committed
328
329
330

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

David Haynes's avatar
David Haynes committed
331
332
333
334
335
336
337
338
339
340
    **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
341
342
    Query parameter that orders the returned objects based on the provided field to order by.

David Haynes's avatar
David Haynes committed
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
    **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
358

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

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

David Haynes's avatar
David Haynes committed
363
    ## Custom query parameters
David Haynes's avatar
David Haynes committed
364

David Haynes's avatar
David Haynes committed
365
366
367
368
369
370
371
372
373
374
375
    ### **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.
376
    """
377

378
    # All model fields that are available for filtering
379
380
    FILTER_FIELDS = (
        # Facility fields
381
382
383
384
385
386
387
        "facility_name",
        "facility_classifier",
        "logo",
        "tapingo_url",
        "note",
        "facility_product_tags__name",
        "facility_labels__name",
388
        # Category fields
389
        "facility_category__name",
390
        # Location fields
391
392
393
394
395
        "facility_location__building",
        "facility_location__friendly_building",
        "facility_location__address",
        "facility_location__on_campus",
        "facility_location__campus_region",
396
        # Schedule fields
397
398
399
400
401
402
403
404
405
406
        "main_schedule__name",
        "main_schedule__valid_start",
        "main_schedule__valid_end",
        "main_schedule__twenty_four_hours",
        "main_schedule__schedule_for_removal",
        "special_schedules__name",
        "special_schedules__valid_start",
        "special_schedules__valid_end",
        "special_schedules__twenty_four_hours",
        "special_schedules__schedule_for_removal",
407
408
    )

David Haynes's avatar
David Haynes committed
409
    # Associate a serializer with the ViewSet
410
    serializer_class = FacilitySerializer
411
412

    # Setup filtering
413
414
415
416
417
    filter_backends = (
        filters.SearchFilter,
        DjangoFilterBackend,
        filters.OrderingFilter,
    )
418
419
420
    search_fields = FILTER_FIELDS
    ordering_fields = FILTER_FIELDS
    filter_fields = FILTER_FIELDS
421
    lookup_field = "slug"
422

423
    def get_queryset(self):
424
        """
425
426
        Handle incoming GET requests and enumerate objects that get returned by
        the API.
427
        """
David Haynes's avatar
David Haynes committed
428
        # Define ?open_now
429
        open_now = self.request.query_params.get("open_now", None)
David Haynes's avatar
David Haynes committed
430
        # Define ?closed_now
431
        closed_now = self.request.query_params.get("closed_now", None)
432
433
434
435
436

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

David Haynes's avatar
David Haynes committed
437
        if open_now is not None or closed_now is not None:
438
            # List of all open facilities
David Haynes's avatar
David Haynes committed
439
            open_facilities = [
440
                facility.pk for facility in Facility.objects.all() if facility.is_open()
David Haynes's avatar
David Haynes committed
441
            ]
442
443
            # Return all Facility objects with the primary keys located in the
            # open_facilities list
David Haynes's avatar
David Haynes committed
444
445
446
447
448
449
            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)
450
        # Default behavior
451
        else:
David Haynes's avatar
David Haynes committed
452
            return Facility.objects.all()
453

454

455
class ScheduleViewSet(viewsets.ModelViewSet):
456
    """
457
458
    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
459
460
461
462
463
464
    ---

    ## Default behavior

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

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

David Haynes's avatar
David Haynes committed
467
468
469
470
471
    ## Built-in query parameters

    ### **Search**

    [GET /api/schedules/?search=](/api/schedules/?search=&format=json)
472
473
474

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

David Haynes's avatar
David Haynes committed
475
476
477
478
479
480
481
482
483
484
    **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)

485
486
    Query parameter that orders the returned objects based on the provided field to order by.

David Haynes's avatar
David Haynes committed
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
    **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)
507

David Haynes's avatar
David Haynes committed
508
    Return the Schedule object that has "southside_main" as its name.
509
    """
510

511
512
513
    # All model fields that are available for filtering
    FILTER_FIELDS = (
        # Schedule fields
514
515
516
517
518
519
        "name",
        "valid_start",
        "valid_end",
        "twenty_four_hours",
        "schedule_for_removal",
        "promote_to_main",
520
521
522
    )

    # Associate a serializer with the ViewSet
523
524
    serializer_class = ScheduleSerializer

525
    # Setup filtering
526
527
528
529
530
    filter_backends = (
        filters.SearchFilter,
        DjangoFilterBackend,
        filters.OrderingFilter,
    )
531
532
533
534
    search_fields = FILTER_FIELDS
    ordering_fields = FILTER_FIELDS
    filter_fields = FILTER_FIELDS

David Haynes's avatar
David Haynes committed
535
    def get_queryset(self):
536
537
538
539
540
        """
        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
541
542
543
544
545
        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
546
            if schedule.valid_end < datetime.datetime.now(schedule.valid_end.tzinfo)
David Haynes's avatar
David Haynes committed
547
        ]
548
549
        # Return all Schedule objects that have not expired
        return Schedule.objects.exclude(pk__in=filter_old_schedules)
David Haynes's avatar
David Haynes committed
550

551

552
class OpenTimeViewSet(viewsets.ModelViewSet):
553
    """
554
555
556
557
558
559
    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.
560
    """
561

562
    # Associate a serializer with the ViewSet
563
    serializer_class = OpenTimeSerializer
David Haynes's avatar
David Haynes committed
564
565

    def get_queryset(self):
566
567
568
569
        """
        Handle incoming GET requests and enumerate objects that get returned by
        the API.
        """
570
        return OpenTime.objects.all()