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
# Future Imports
from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

# Python stdlib Imports
import datetime
15
import re
16
17

# Django Imports
18
from django.db import models
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
Tyler Hallada's avatar
Tyler Hallada committed
25

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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