views.py 15.3 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
        "urgency_tag",
David Haynes's avatar
David Haynes committed
104 105 106
        "subject",
        "body",
        "url",
107 108
        "start_datetime",
        "end_datetime",
109 110 111
    )

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

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

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

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

142

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

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

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

    ---

    ## Default behavior

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

David Haynes's avatar
David Haynes committed
159
    Return all Category objects.
160

David Haynes's avatar
David Haynes committed
161 162 163 164 165
    ## Built-in query parameters

    ### **Search**

    [GET /api/categories/?search=](/api/categories/?search=&format=json)
166 167 168

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

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

179 180
    Query parameter that orders the returned objects based on the provided field to order by.

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

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

    Return the Category object that is named "dining".
200
    """
201

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

    # Associate a serializer with the ViewSet
209 210
    serializer_class = CategorySerializer

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

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

228

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

David Haynes's avatar
David Haynes committed
233 234 235 236 237 238
    ---

    ## Default behavior

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

David Haynes's avatar
David Haynes committed
239
    Return all Location objects.
240

David Haynes's avatar
David Haynes committed
241 242 243 244 245
    ## Built-in query parameters

    ### **Search**

    [GET /api/locations/?search=](/api/locations/?search=&format=json)
246 247 248

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

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

David Haynes's avatar
David Haynes committed
261
    **Example Usage**
262

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

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

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

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

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

312

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

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

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

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

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

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

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

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

David Haynes's avatar
David Haynes committed
365
    ## Custom query parameters
David Haynes's avatar
David Haynes committed
366

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

380
    # All model fields that are available for filtering
381 382
    FILTER_FIELDS = (
        # Facility fields
383 384 385 386 387 388
        "facility_name",
        "facility_classifier",
        "logo",
        "tapingo_url",
        "note",
        "facility_product_tags__name",
389
        # Category fields
390
        "facility_category__name",
391
        # Location fields
392 393 394 395 396
        "facility_location__building",
        "facility_location__friendly_building",
        "facility_location__address",
        "facility_location__on_campus",
        "facility_location__campus_region",
397
        # Schedule fields
398 399 400 401 402 403 404 405
        "main_schedule__name",
        "main_schedule__valid_start",
        "main_schedule__valid_end",
        "main_schedule__twenty_four_hours",
        "special_schedules__name",
        "special_schedules__valid_start",
        "special_schedules__valid_end",
        "special_schedules__twenty_four_hours",
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

David Haynes's avatar
David Haynes committed
432 433
        if open_now is not None:
            open_now = [
434
                facility.pk for facility in Facility.objects.all() if facility.is_open()
David Haynes's avatar
David Haynes committed
435
            ]
David Haynes's avatar
David Haynes committed
436 437 438 439 440 441 442 443
            return Facility.objects.filter(pk__in=open_now)
        elif closed_now is not None:
            closed_now = [
                facility.pk
                for facility in Facility.objects.all()
                if not facility.is_open()
            ]
            return Facility.objects.filter(pk__in=closed_now)
444
        else:
David Haynes's avatar
David Haynes committed
445
            return Facility.objects.all()
446

447

448
class ScheduleViewSet(viewsets.ModelViewSet):
449
    """
450 451
    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
452 453 454 455 456 457
    ---

    ## Default behavior

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

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

David Haynes's avatar
David Haynes committed
460 461 462 463 464
    ## Built-in query parameters

    ### **Search**

    [GET /api/schedules/?search=](/api/schedules/?search=&format=json)
465 466 467

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

David Haynes's avatar
David Haynes committed
468 469 470 471 472 473 474 475 476 477
    **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)

478 479
    Query parameter that orders the returned objects based on the provided field to order by.

David Haynes's avatar
David Haynes committed
480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499
    **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)
500

David Haynes's avatar
David Haynes committed
501
    Return the Schedule object that has "southside_main" as its name.
502
    """
503

504 505 506
    # All model fields that are available for filtering
    FILTER_FIELDS = (
        # Schedule fields
507 508 509 510
        "name",
        "valid_start",
        "valid_end",
        "twenty_four_hours",
511 512 513
    )

    # Associate a serializer with the ViewSet
514 515
    serializer_class = ScheduleSerializer

516
    # Setup filtering
517 518 519 520 521
    filter_backends = (
        filters.SearchFilter,
        DjangoFilterBackend,
        filters.OrderingFilter,
    )
522 523 524 525
    search_fields = FILTER_FIELDS
    ordering_fields = FILTER_FIELDS
    filter_fields = FILTER_FIELDS

David Haynes's avatar
David Haynes committed
526
    def get_queryset(self):
527 528 529 530 531
        """
        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
532 533 534 535 536
        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
537
            if schedule.valid_end < datetime.datetime.now(schedule.valid_end.tzinfo)
David Haynes's avatar
David Haynes committed
538
        ]
539 540
        # Return all Schedule objects that have not expired
        return Schedule.objects.exclude(pk__in=filter_old_schedules)
David Haynes's avatar
David Haynes committed
541

542

543
class OpenTimeViewSet(viewsets.ModelViewSet):
544
    """
545 546 547 548 549 550
    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.
551
    """
552

553
    # Associate a serializer with the ViewSet
554
    serializer_class = OpenTimeSerializer
David Haynes's avatar
David Haynes committed
555 556

    def get_queryset(self):
557 558 559 560
        """
        Handle incoming GET requests and enumerate objects that get returned by
        the API.
        """
561
        return OpenTime.objects.all()