models.py 13.3 KB
Newer Older
David Haynes's avatar
David Haynes committed
1
2
3
4
5
6
7
8
"""
api/models.py

Define the objects that will be stored in the database and later served through
the API.

https://docs.djangoproject.com/en/1.11/topics/db/models/
"""
9
10
11
12
13
14
15
16
# Future Imports
from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

# Python stdlib Imports
import datetime

# Django Imports
17
from django.db import models
David Haynes's avatar
David Haynes committed
18
from django.contrib.gis.db.models import PointField
19
from django.contrib.auth.models import User
20
from django.core.validators import RegexValidator
David Haynes's avatar
David Haynes committed
21
22

# Other Imports
Ben Waters's avatar
Ben Waters committed
23
from model_utils.models import TimeStampedModel
24
from autoslug import AutoSlugField
25
from taggit.managers import TaggableManager
Tyler Hallada's avatar
Tyler Hallada committed
26

Ben Waters's avatar
Ben Waters committed
27
class Category(TimeStampedModel):
28
    """
David Haynes's avatar
David Haynes committed
29
30
31
32
33
34
35
    Represents the "category" that a Facility falls under. A Category is a
    grouping of Facilities that serve a common/similar purpose.

    ex.
    - Dining
    - Gyms
    - Study areas (Libraries, The Ridge, JC, etc)
36
    """
David Haynes's avatar
David Haynes committed
37
    # The name of the category
38
39
40
41
42
    name = models.CharField(max_length=100)

    class Meta:
        verbose_name = "category"
        verbose_name_plural = "categories"
43
        # Sort by name in admin view.
44
45
        ordering = ['name']

46
    def __str__(self):
David Haynes's avatar
David Haynes committed
47
48
49
50
        """
        String representation of a Category object.
        """
        return self.name
51

David Haynes's avatar
David Haynes committed
52
53
class Location(TimeStampedModel):
    """
54
    Represents a specific location that a Facility can be found.
David Haynes's avatar
David Haynes committed
55
    """
56
57
58
59
60
61
62
    CAMPUS_LOCATIONS = (
        # (set in model, human readable version)
        ("prince william", "Prince William County Science and Technology"),
        ("korea", "Mason Korea"),
        ("fairfax", "Fairfax"),
        ("arlington", "Arlington")
    )
63
64
65
66
    # The building that the facility is located in (on campus).
    building = models.CharField(max_length=100)
    # The physical address of the facility.
    address = models.CharField(max_length=100)
67
68
    campus_region = models.CharField(choices=CAMPUS_LOCATIONS,
                                     max_length=100, default="fairfax")
69
    # Boolean for whether or not the location is "on campus" or not.
David Haynes's avatar
David Haynes committed
70
    on_campus = models.BooleanField(default=True)
David Haynes's avatar
David Haynes committed
71
72
    # A GeoJson coordinate pair that represents the physical location
    coordinate_location = PointField()
David Haynes's avatar
David Haynes committed
73

74
75
76
77
78
79
80
81
82
83
84
85
    class Meta:
        verbose_name = "location"
        verbose_name_plural = "locations"

    def __str__(self):
        """
        String representation of a Location object.
        """
        return 'Found in %s at %s | On Campus: %s' % (self.building,
                                                      self.address,
                                                      self.on_campus)

Ben Waters's avatar
Ben Waters committed
86
class Facility(TimeStampedModel):
David Haynes's avatar
David Haynes committed
87
    """
David Haynes's avatar
David Haynes committed
88
89
90
    Represents a specific facility location. 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
91
92
    """
    # The name of the Facility
David Haynes's avatar
David Haynes committed
93
    facility_name = models.CharField(max_length=100)
David Haynes's avatar
David Haynes committed
94
    # Instead of id
95
    slug = AutoSlugField(populate_from='facility_name', unique=True)
96

David Haynes's avatar
David Haynes committed
97
    # The category that this facility can be grouped with
98
    facility_category = models.ForeignKey('Category',
David Haynes's avatar
David Haynes committed
99
                                          related_name="categories")
David Haynes's avatar
David Haynes committed
100
    # The location object that relates to this facility
101
102
    facility_location = models.ForeignKey('Location',
                                          related_name="facilities")
103

David Haynes's avatar
David Haynes committed
104
    # The User(s) that claim ownership over this facility
105
    owners = models.ManyToManyField(User)
David Haynes's avatar
David Haynes committed
106
107

    # The schedule that is defaulted to if no special schedule is in effect
108
    main_schedule = models.ForeignKey('Schedule',
David Haynes's avatar
David Haynes committed
109
110
                                      related_name='facility_main')
    # A schedule that has a specific start and end date
111
    special_schedules = models.ManyToManyField('Schedule',
David Haynes's avatar
David Haynes committed
112
113
                                               related_name='facility_special',
                                               blank=True,
114
115
116
117
118
                                               help_text="""This schedule will
                                                            come into effect
                                                            only for its
                                                            specified duration.
                                                            """)
119
120
121
    # URL, if it exists, to the Tapingo page that is associated with this
    # facility
    tapingo_url = models.URLField(blank=True, validators=[RegexValidator(regex='^https:\/\/www.tapingo.com\/',
David Haynes's avatar
David Haynes committed
122
                                                                         message='The link is not a valid tapingo link. Example: https://www.tapingo.com/order/restaurant/starbucks-gmu-johnson/',
123
                                                                         code='invalid_tapingo_url')])
124
125
126
    # A comma seperate list of words that neatly an aptly describe the product
    # that this facility produces. (ex. for Taco Bell: mexican, taco, cheap)
    facility_product_tags = TaggableManager()
David Haynes's avatar
David Haynes committed
127

David Haynes's avatar
David Haynes committed
128
    def is_open(self):
129
        """
130
        Return true if this facility is currently open.
131

David Haynes's avatar
David Haynes committed
132
133
        First checks any valid special schedules and then checks the main,
        default, schedule.
134
        """
David Haynes's avatar
David Haynes committed
135
        # Get the current date
136
        today = datetime.datetime.today().date()
David Haynes's avatar
David Haynes committed
137
        # Check special schedules first, loop through all of them
138
139
140
        for schedule in self.special_schedules.all():
            # Special schedules must have valid_start and valid_end set
            if schedule.valid_start and schedule.valid_end:
David Haynes's avatar
David Haynes committed
141
                # If a special schedule in in effect
142
                if schedule.valid_start <= today <= schedule.valid_end:
David Haynes's avatar
David Haynes committed
143
144
145
146
                    # Check if the facility is open or not based on that 
                    # special schedule
                    if schedule.is_open_now():
                        # Open
147
                        return True
148
                    else:
David Haynes's avatar
David Haynes committed
149
                        # Closed
150
                        return False
David Haynes's avatar
David Haynes committed
151
152
153
154
        # If no special schedule is in effect then check if the facility is
        # open using the main_schedule
        if self.main_schedule.is_open_now():
            # Open
155
            return True
David Haynes's avatar
David Haynes committed
156
157
158
159
        else:
            # Closed
            return False

160
161
162
163
164
165
166
167
168
169
    def clean_special_schedules(self):
        """
        Loop through every special_schedule and remove entries that have
        expired.
        """
        for special_schedule in self.special_schedules.all():
            # If it ends before today
            if special_schedule.valid_end < datetime.date.today():
                self.special_schedules.remove(special_schedule)

David Haynes's avatar
David Haynes committed
170
171
172
173
    class Meta:
        verbose_name = "facility"
        verbose_name_plural = "facilities"
        # Sort by name in admin view
David Haynes's avatar
David Haynes committed
174
        ordering = ['facility_name']
175

176
    def __str__(self):
David Haynes's avatar
David Haynes committed
177
178
179
        """
        String representation of a Facility object.
        """
David Haynes's avatar
David Haynes committed
180
        return self.facility_name
181

Ben Waters's avatar
Ben Waters committed
182
class Schedule(TimeStampedModel):
183
    """
David Haynes's avatar
David Haynes committed
184
185
    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.
186
    """
David Haynes's avatar
David Haynes committed
187
    # The name of the schedule
Tyler Hallada's avatar
Tyler Hallada committed
188
    name = models.CharField(max_length=100)
David Haynes's avatar
David Haynes committed
189
190

    # The start date of the schedule
191
    # (inclusive)
192
    valid_start = models.DateField('Start Date', null=True, blank=True,
193
194
                                   help_text="""Date that this schedule goes
                                                into effect""")
David Haynes's avatar
David Haynes committed
195
196
    # The end date of the schedule
    # (inclusive)
197
    valid_end = models.DateField('End Date', null=True, blank=True,
198
199
200
                                 help_text="""Last day that this schedule is
                                              in effect""")

David Haynes's avatar
David Haynes committed
201
    def is_open_now(self):
202
203
204
        """
        Return true if this schedule is open right now.
        """
David Haynes's avatar
David Haynes committed
205
206
207
208
209
210
        # Loop through all the open times that correspond to this schedule
        for open_time in OpenTime.objects.filter(schedule=self):
            # If the current time we are looking at is open, then the schedule 
            # will say that the facility is open
            if open_time.is_open_now():
                # Open
211
                return True
David Haynes's avatar
David Haynes committed
212
        # Closed (all open times are not open)
213
214
        return False

David Haynes's avatar
David Haynes committed
215
216
217
218
    class Meta:
        # Sort by name in admin view
        ordering = ['name']

219
    def __str__(self):
David Haynes's avatar
David Haynes committed
220
221
222
        """
        String representation of a Schedule object.
        """
223
224
225
        return self.name


Ben Waters's avatar
Ben Waters committed
226
class OpenTime(TimeStampedModel):
227
    """
David Haynes's avatar
David Haynes committed
228
229
230
    Represents a time period when a Facility is open.

    Monday = 0, Sunday = 6.
231
    """
David Haynes's avatar
David Haynes committed
232
    # Define integer constants to represent days of the week
233
234
235
236
237
238
239
240
    MONDAY = 0
    TUESDAY = 1
    WEDNESDAY = 2
    THURSDAY = 3
    FRIDAY = 4
    SATURDAY = 5
    SUNDAY = 6

David Haynes's avatar
David Haynes committed
241
    # Tuple that ties a day of the week with an integer representation
242
243
244
245
246
247
248
249
250
251
    DAY_CHOICES = (
        (MONDAY, 'Monday'),
        (TUESDAY, 'Tuesday'),
        (WEDNESDAY, 'Wednesday'),
        (THURSDAY, 'Thursday'),
        (FRIDAY, 'Friday'),
        (SATURDAY, 'Saturday'),
        (SUNDAY, 'Sunday'),
    )

David Haynes's avatar
David Haynes committed
252
    # The schedule that this period of open time is a part of
253
    schedule = models.ForeignKey('Schedule', related_name='open_times')
David Haynes's avatar
David Haynes committed
254
255

    # The day that the open time begins on
256
    start_day = models.IntegerField(default=0, choices=DAY_CHOICES)
David Haynes's avatar
David Haynes committed
257
    # The day that the open time ends on
258
    end_day = models.IntegerField(default=0, choices=DAY_CHOICES)
David Haynes's avatar
David Haynes committed
259
260
261
262

    # The time of day that the open time begins at
    start_time = models.TimeField()
    # The time of day that the open time ends
263
264
    end_time = models.TimeField()

David Haynes's avatar
David Haynes committed
265
    def is_open_now(self):
266
        """
David Haynes's avatar
David Haynes committed
267
        Return true if the current time is this OpenTime's range.
268
        """
David Haynes's avatar
David Haynes committed
269
        # Get the current datetime
270
        today = datetime.datetime.today()
David Haynes's avatar
David Haynes committed
271
        # Check that the start occurs before the end
272
        if self.start_day <= self.end_day:
David Haynes's avatar
David Haynes committed
273
            # If today is the start_day
274
            if self.start_day == today.weekday():
David Haynes's avatar
David Haynes committed
275
                # If the start_time has not occurred
276
                if self.start_time > today.time():
David Haynes's avatar
David Haynes committed
277
                    # Closed
278
                    return False
David Haynes's avatar
David Haynes committed
279
            # If the start_day has not occurred
280
            elif self.start_day > today.weekday():
David Haynes's avatar
David Haynes committed
281
                # Closed
282
                return False
David Haynes's avatar
David Haynes committed
283
            # If the end_day is today
284
            if self.end_day == today.weekday():
David Haynes's avatar
David Haynes committed
285
                # If the end_time has already occurred
286
                if self.end_time < today.time():
David Haynes's avatar
David Haynes committed
287
                    # Closed
288
                    return False
David Haynes's avatar
David Haynes committed
289
            # If the end_day has already occurred
290
            elif self.end_day < today.weekday():
David Haynes's avatar
David Haynes committed
291
                # Closed
292
                return False
David Haynes's avatar
David Haynes committed
293
        # The end_day > start_day
294
        else:
David Haynes's avatar
David Haynes committed
295
            # If today is the start_day
296
            if self.start_day == today.weekday():
David Haynes's avatar
David Haynes committed
297
                # If the start_time has not occurred
298
                if self.start_time > today.time():
David Haynes's avatar
David Haynes committed
299
                    # Closed
300
                    return False
David Haynes's avatar
David Haynes committed
301
            # If the end_day is today
302
            if self.end_day == today.weekday():
David Haynes's avatar
David Haynes committed
303
                # If the end_time has already occurred
304
                if self.end_time < today.time():
David Haynes's avatar
David Haynes committed
305
                    # Closed
306
                    return False
David Haynes's avatar
David Haynes committed
307
308
            # If the current date takes place after the end_date but before
            # start_day
309
            if self.end_day < today.weekday() < self.start_day:
David Haynes's avatar
David Haynes committed
310
                # Closed
311
                return False
David Haynes's avatar
David Haynes committed
312
        # All checks passed, it's Open
313
        return True
314

315
    def __str__(self):
David Haynes's avatar
David Haynes committed
316
317
318
        """
        String representation of a OpenTime object.
        """
319
        weekdays = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday',
320
                    'Saturday', 'Sunday']
321
        return '%s %s to %s %s' % (weekdays[self.start_day],
322
323
324
325
                                   self.start_time.strftime("%H:%M:%S"),
                                   # to
                                   weekdays[self.end_day],
                                   self.end_time.strftime("%H:%M:%S"))
David Haynes's avatar
David Haynes committed
326
327
328
329
330
331
332
333
334
335

class Alert(TimeStampedModel):
    """
    Some type of notification that is displayed to clients that conveys a
    message. Past examples include: random closings, modified schedules being
    in effect, election reminder, advertising for other SRCT projects.

    Alerts last for a period of time until the information is no longer dank.
    """
    # Define string constants to represent urgency tag levels
336
337
338
339
    INFO = 'info'  # SRCT announcements
    MINOR = 'minor'  # Holiday hours are in effect
    MAJOR = 'major'  # The hungry patriot is closed today
    EMERGENCY = 'emergency'  # Extreme weather
David Haynes's avatar
David Haynes committed
340

David Haynes's avatar
David Haynes committed
341
    # Tuple that ties a urgency tag with a string representation
David Haynes's avatar
David Haynes committed
342
343
    URGENCY_CHOICES = (
        (INFO, 'Info'),
344
        (MINOR, 'Minor'),
David Haynes's avatar
David Haynes committed
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
        (MAJOR, 'Major'),
        (EMERGENCY, 'Emergency'),
    )

    # The urgency tag for this Alert
    urgency_tag = models.CharField(max_length=10, default='Info',
                                   choices=URGENCY_CHOICES)

    # The text that is displayed that describes the Alert
    message = models.CharField(max_length=140)

    # The date + time that the alert will be start being served
    start_datetime = models.DateTimeField()

    # The date + time that the alert will stop being served
    end_datetime = models.DateTimeField()

    def __str__(self):
        """
        String representation of an Alert object.
        """
David Haynes's avatar
David Haynes committed
366
        return "%s" % (self.message)