models.py 12.5 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
    # 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
61
    on_campus = models.BooleanField(default=True)
David Haynes's avatar
David Haynes committed
62
63
    # A GeoJson coordinate pair that represents the physical location
    coordinate_location = PointField()
David Haynes's avatar
David Haynes committed
64

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

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

David Haynes's avatar
David Haynes committed
95
    # The User(s) that claim ownership over this facility
96
    owners = models.ManyToManyField(User)
David Haynes's avatar
David Haynes committed
97
98

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

David Haynes's avatar
David Haynes committed
119
    def is_open(self):
120
        """
121
        Return true if this facility is currently open.
122

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

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

157
    def __str__(self):
David Haynes's avatar
David Haynes committed
158
159
160
        """
        String representation of a Facility object.
        """
161
162
        return self.name

Ben Waters's avatar
Ben Waters committed
163
class Schedule(TimeStampedModel):
164
    """
David Haynes's avatar
David Haynes committed
165
166
    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.
167
    """
David Haynes's avatar
David Haynes committed
168
    # The name of the schedule
Tyler Hallada's avatar
Tyler Hallada committed
169
    name = models.CharField(max_length=100)
David Haynes's avatar
David Haynes committed
170
171

    # The start date of the schedule
172
    # (inclusive)
173
    valid_start = models.DateField('Start Date', null=True, blank=True,
174
175
                                   help_text="""Date that this schedule goes
                                                into effect""")
David Haynes's avatar
David Haynes committed
176
177
    # The end date of the schedule
    # (inclusive)
178
    valid_end = models.DateField('End Date', null=True, blank=True,
179
180
181
                                 help_text="""Last day that this schedule is
                                              in effect""")

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

David Haynes's avatar
David Haynes committed
196
197
198
199
    class Meta:
        # Sort by name in admin view
        ordering = ['name']

200
    def __str__(self):
David Haynes's avatar
David Haynes committed
201
202
203
        """
        String representation of a Schedule object.
        """
204
205
206
        return self.name


Ben Waters's avatar
Ben Waters committed
207
class OpenTime(TimeStampedModel):
208
    """
David Haynes's avatar
David Haynes committed
209
210
211
    Represents a time period when a Facility is open.

    Monday = 0, Sunday = 6.
212
    """
David Haynes's avatar
David Haynes committed
213
    # Define integer constants to represent days of the week
214
215
216
217
218
219
220
221
    MONDAY = 0
    TUESDAY = 1
    WEDNESDAY = 2
    THURSDAY = 3
    FRIDAY = 4
    SATURDAY = 5
    SUNDAY = 6

David Haynes's avatar
David Haynes committed
222
    # Tuple that ties a day of the week with an integer representation
223
224
225
226
227
228
229
230
231
232
    DAY_CHOICES = (
        (MONDAY, 'Monday'),
        (TUESDAY, 'Tuesday'),
        (WEDNESDAY, 'Wednesday'),
        (THURSDAY, 'Thursday'),
        (FRIDAY, 'Friday'),
        (SATURDAY, 'Saturday'),
        (SUNDAY, 'Sunday'),
    )

David Haynes's avatar
David Haynes committed
233
    # The schedule that this period of open time is a part of
234
    schedule = models.ForeignKey('Schedule', related_name='open_times')
David Haynes's avatar
David Haynes committed
235
236

    # The day that the open time begins on
237
    start_day = models.IntegerField(default=0, choices=DAY_CHOICES)
David Haynes's avatar
David Haynes committed
238
    # The day that the open time ends on
239
    end_day = models.IntegerField(default=0, choices=DAY_CHOICES)
David Haynes's avatar
David Haynes committed
240
241
242
243

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

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

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

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
317
318
319
320
    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
321

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