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
        "facility_name",
        "facility_classifier",
        "logo",
        "tapingo_url",
        "note",
        "facility_product_tags__name",
387
        # Category fields
388
        "facility_category__name",
389
        # Location fields
390 391 392 393 394
        "facility_location__building",
        "facility_location__friendly_building",
        "facility_location__address",
        "facility_location__on_campus",
        "facility_location__campus_region",
395
        # Schedule fields
396 397 398 399 400 401 402 403 404 405
        "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",
406 407
    )

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

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

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

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

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

453

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

    ## Default behavior

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

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

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

    ### **Search**

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

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

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

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

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

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

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

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

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

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

550

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

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

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