models.py 12.1 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
18
from django.contrib.auth.models import User
19
from django.core.validators import RegexValidator
David Haynes's avatar
David Haynes committed
20
21

# Other Imports
Ben Waters's avatar
Ben Waters committed
22
from model_utils.models import TimeStampedModel
23
from autoslug import AutoSlugField
Tyler Hallada's avatar
Tyler Hallada committed
24

Ben Waters's avatar
Ben Waters committed
25
class Category(TimeStampedModel):
26
    """
David Haynes's avatar
David Haynes committed
27
28
29
30
31
32
33
    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)
34
    """
David Haynes's avatar
David Haynes committed
35
    # The name of the category
36
37
38
39
40
    name = models.CharField(max_length=100)

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

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

David Haynes's avatar
David Haynes committed
50
51
class Location(TimeStampedModel):
    """
52
    Represents a specific location that a Facility can be found.
David Haynes's avatar
David Haynes committed
53
    """
54
55
56
57
58
    # 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)
    # Boolean for whether or not the location is "on campus" or not.
David Haynes's avatar
David Haynes committed
59
60
    on_campus = models.BooleanField(default=True)

61
62
63
64
65
66
67
68
69
70
71
72
    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
73
class Facility(TimeStampedModel):
David Haynes's avatar
David Haynes committed
74
    """
David Haynes's avatar
David Haynes committed
75
76
77
    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
78
79
    """
    # The name of the Facility
Tyler Hallada's avatar
Tyler Hallada committed
80
    name = models.CharField(max_length=100)
David Haynes's avatar
David Haynes committed
81
82
    # Instead of id
    slug = AutoSlugField(populate_from='name', unique=True)
83

David Haynes's avatar
David Haynes committed
84
    # The category that this facility can be grouped with
85
    facility_category = models.ForeignKey('Category',
David Haynes's avatar
David Haynes committed
86
                                          related_name="categories")
David Haynes's avatar
David Haynes committed
87
    # The location object that relates to this facility
88
89
    facility_location = models.ForeignKey('Location',
                                          related_name="facilities")
90

David Haynes's avatar
David Haynes committed
91
    # The User(s) that claim ownership over this facility
92
    owners = models.ManyToManyField(User)
David Haynes's avatar
David Haynes committed
93
94

    # The schedule that is defaulted to if no special schedule is in effect
95
    main_schedule = models.ForeignKey('Schedule',
David Haynes's avatar
David Haynes committed
96
97
                                      related_name='facility_main')
    # A schedule that has a specific start and end date
98
    special_schedules = models.ManyToManyField('Schedule',
David Haynes's avatar
David Haynes committed
99
100
                                               related_name='facility_special',
                                               blank=True,
101
102
103
104
105
                                               help_text="""This schedule will
                                                            come into effect
                                                            only for its
                                                            specified duration.
                                                            """)
106
107
108
    # 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
109
                                                                         message='The link is not a valid tapingo link. Example: https://www.tapingo.com/order/restaurant/starbucks-gmu-johnson/',
110
                                                                         code='invalid_tapingo_url')])
David Haynes's avatar
David Haynes committed
111

David Haynes's avatar
David Haynes committed
112
    def is_open(self):
113
        """
114
        Return true if this facility is currently open.
115

David Haynes's avatar
David Haynes committed
116
117
        First checks any valid special schedules and then checks the main,
        default, schedule.
118
        """
David Haynes's avatar
David Haynes committed
119
        # Get the current date
120
        today = datetime.datetime.today().date()
David Haynes's avatar
David Haynes committed
121
        # Check special schedules first, loop through all of them
122
123
124
        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
125
                # If a special schedule in in effect
126
                if schedule.valid_start <= today <= schedule.valid_end:
David Haynes's avatar
David Haynes committed
127
128
129
130
                    # Check if the facility is open or not based on that 
                    # special schedule
                    if schedule.is_open_now():
                        # Open
131
                        return True
132
                    else:
David Haynes's avatar
David Haynes committed
133
                        # Closed
134
                        return False
David Haynes's avatar
David Haynes committed
135
136
137
138
        # 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
139
            return True
David Haynes's avatar
David Haynes committed
140
141
142
143
144
145
146
147
148
        else:
            # Closed
            return False

    class Meta:
        verbose_name = "facility"
        verbose_name_plural = "facilities"
        # Sort by name in admin view
        ordering = ['name']
149

150
    def __str__(self):
David Haynes's avatar
David Haynes committed
151
152
153
        """
        String representation of a Facility object.
        """
154
155
        return self.name

Ben Waters's avatar
Ben Waters committed
156
class Schedule(TimeStampedModel):
157
    """
David Haynes's avatar
David Haynes committed
158
159
    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.
160
    """
David Haynes's avatar
David Haynes committed
161
    # The name of the schedule
Tyler Hallada's avatar
Tyler Hallada committed
162
    name = models.CharField(max_length=100)
David Haynes's avatar
David Haynes committed
163
164

    # The start date of the schedule
165
    # (inclusive)
166
    valid_start = models.DateField('Start Date', null=True, blank=True,
167
168
                                   help_text="""Date that this schedule goes
                                                into effect""")
David Haynes's avatar
David Haynes committed
169
170
    # The end date of the schedule
    # (inclusive)
171
    valid_end = models.DateField('End Date', null=True, blank=True,
172
173
174
                                 help_text="""Last day that this schedule is
                                              in effect""")

David Haynes's avatar
David Haynes committed
175
    def is_open_now(self):
176
177
178
        """
        Return true if this schedule is open right now.
        """
David Haynes's avatar
David Haynes committed
179
180
181
182
183
184
        # 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
185
                return True
David Haynes's avatar
David Haynes committed
186
        # Closed (all open times are not open)
187
188
        return False

David Haynes's avatar
David Haynes committed
189
190
191
192
    class Meta:
        # Sort by name in admin view
        ordering = ['name']

193
    def __str__(self):
David Haynes's avatar
David Haynes committed
194
195
196
        """
        String representation of a Schedule object.
        """
197
198
199
        return self.name


Ben Waters's avatar
Ben Waters committed
200
class OpenTime(TimeStampedModel):
201
    """
David Haynes's avatar
David Haynes committed
202
203
204
    Represents a time period when a Facility is open.

    Monday = 0, Sunday = 6.
205
    """
David Haynes's avatar
David Haynes committed
206
    # Define integer constants to represent days of the week
207
208
209
210
211
212
213
214
    MONDAY = 0
    TUESDAY = 1
    WEDNESDAY = 2
    THURSDAY = 3
    FRIDAY = 4
    SATURDAY = 5
    SUNDAY = 6

David Haynes's avatar
David Haynes committed
215
    # Tuple that ties a day of the week with an integer representation
216
217
218
219
220
221
222
223
224
225
    DAY_CHOICES = (
        (MONDAY, 'Monday'),
        (TUESDAY, 'Tuesday'),
        (WEDNESDAY, 'Wednesday'),
        (THURSDAY, 'Thursday'),
        (FRIDAY, 'Friday'),
        (SATURDAY, 'Saturday'),
        (SUNDAY, 'Sunday'),
    )

David Haynes's avatar
David Haynes committed
226
    # The schedule that this period of open time is a part of
227
    schedule = models.ForeignKey('Schedule', related_name='open_times')
David Haynes's avatar
David Haynes committed
228
229

    # The day that the open time begins on
230
    start_day = models.IntegerField(default=0, choices=DAY_CHOICES)
David Haynes's avatar
David Haynes committed
231
    # The day that the open time ends on
232
    end_day = models.IntegerField(default=0, choices=DAY_CHOICES)
David Haynes's avatar
David Haynes committed
233
234
235
236

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

David Haynes's avatar
David Haynes committed
239
    def is_open_now(self):
240
        """
David Haynes's avatar
David Haynes committed
241
        Return true if the current time is this OpenTime's range.
242
        """
David Haynes's avatar
David Haynes committed
243
        # Get the current datetime
244
        today = datetime.datetime.today()
David Haynes's avatar
David Haynes committed
245
        # Check that the start occurs before the end
246
        if self.start_day <= self.end_day:
David Haynes's avatar
David Haynes committed
247
            # If today is the start_day
248
            if self.start_day == today.weekday():
David Haynes's avatar
David Haynes committed
249
                # If the start_time has not occurred
250
                if self.start_time > today.time():
David Haynes's avatar
David Haynes committed
251
                    # Closed
252
                    return False
David Haynes's avatar
David Haynes committed
253
            # If the start_day has not occurred
254
            elif self.start_day > today.weekday():
David Haynes's avatar
David Haynes committed
255
                # Closed
256
                return False
David Haynes's avatar
David Haynes committed
257
            # If the end_day is today
258
            if self.end_day == today.weekday():
David Haynes's avatar
David Haynes committed
259
                # If the end_time has already occurred
260
                if self.end_time < today.time():
David Haynes's avatar
David Haynes committed
261
                    # Closed
262
                    return False
David Haynes's avatar
David Haynes committed
263
            # If the end_day has already occurred
264
            elif self.end_day < today.weekday():
David Haynes's avatar
David Haynes committed
265
                # Closed
266
                return False
David Haynes's avatar
David Haynes committed
267
        # The end_day > start_day
268
        else:
David Haynes's avatar
David Haynes committed
269
            # If today is the start_day
270
            if self.start_day == today.weekday():
David Haynes's avatar
David Haynes committed
271
                # If the start_time has not occurred
272
                if self.start_time > today.time():
David Haynes's avatar
David Haynes committed
273
                    # Closed
274
                    return False
David Haynes's avatar
David Haynes committed
275
            # If the end_day is today
276
            if self.end_day == today.weekday():
David Haynes's avatar
David Haynes committed
277
                # If the end_time has already occurred
278
                if self.end_time < today.time():
David Haynes's avatar
David Haynes committed
279
                    # Closed
280
                    return False
David Haynes's avatar
David Haynes committed
281
282
            # If the current date takes place after the end_date but before
            # start_day
283
            if self.end_day < today.weekday() < self.start_day:
David Haynes's avatar
David Haynes committed
284
                # Closed
285
                return False
David Haynes's avatar
David Haynes committed
286
        # All checks passed, it's Open
287
        return True
288

289
    def __str__(self):
David Haynes's avatar
David Haynes committed
290
291
292
        """
        String representation of a OpenTime object.
        """
293
        weekdays = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday',
294
                    'Saturday', 'Sunday']
295
        return '%s %s to %s %s' % (weekdays[self.start_day],
296
297
298
299
                                   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
300
301
302
303
304
305
306
307
308
309

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
310
311
312
313
    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
314

David Haynes's avatar
David Haynes committed
315
    # Tuple that ties a urgency tag with a string representation
David Haynes's avatar
David Haynes committed
316
317
    URGENCY_CHOICES = (
        (INFO, 'Info'),
318
        (MINOR, 'Minor'),
David Haynes's avatar
David Haynes committed
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
        (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
340
        return "%s" % (self.message)