models.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. from datetime import datetime
  2. from django.conf import settings
  3. from django.core.validators import RegexValidator
  4. from django.db import models
  5. from modelcluster.fields import ParentalKey
  6. from wagtail.wagtailadmin.edit_handlers import FieldPanel, InlinePanel
  7. from wagtail.wagtailcore.models import Orderable, Page
  8. from wagtail.wagtailsearch import index
  9. from wagtail.wagtailimages.edit_handlers import ImageChooserPanel
  10. from bakerydemo.base.models import BasePageFieldsMixin
  11. from bakerydemo.locations.choices import DAY_CHOICES
  12. class OperatingHours(models.Model):
  13. """
  14. A Django model to capture operating hours for a Location
  15. """
  16. day = models.CharField(
  17. max_length=4,
  18. choices=DAY_CHOICES,
  19. default='MON'
  20. )
  21. opening_time = models.TimeField(
  22. blank=True,
  23. null=True
  24. )
  25. closing_time = models.TimeField(
  26. blank=True,
  27. null=True
  28. )
  29. closed = models.BooleanField(
  30. "Closed?",
  31. blank=True,
  32. help_text='Tick if location is closed on this day'
  33. )
  34. panels = [
  35. FieldPanel('day'),
  36. FieldPanel('opening_time'),
  37. FieldPanel('closing_time'),
  38. FieldPanel('closed'),
  39. ]
  40. class Meta:
  41. abstract = True
  42. def __str__(self):
  43. if self.opening_time:
  44. opening = self.opening_time.strftime('%H:%M')
  45. else:
  46. opening = '--'
  47. if self.closing_time:
  48. closed = self.opening_time.strftime('%H:%M')
  49. else:
  50. closed = '--'
  51. return '{}: {} - {} {}'.format(
  52. self.day,
  53. opening,
  54. closed,
  55. settings.TIME_ZONE
  56. )
  57. class LocationOperatingHours(Orderable, OperatingHours):
  58. """
  59. A model creating a relationship between the OperatingHours and Location
  60. Note that unlike BlogPeopleRelationship we don't include a ForeignKey to
  61. OperatingHours as we don't need that relationship (e.g. any Location open
  62. a certain day of the week). The ParentalKey is the minimum required to
  63. relate the two objects to one another. We use the ParentalKey's related_
  64. name to access it from the LocationPage admin
  65. """
  66. location = ParentalKey(
  67. 'LocationPage',
  68. related_name='hours_of_operation'
  69. )
  70. class LocationsIndexPage(BasePageFieldsMixin, Page):
  71. """
  72. A Page model that creates an index page (a listview)
  73. """
  74. # Only LocationPage objects can be added underneath this index page
  75. subpage_types = ['LocationPage']
  76. # Allows children of this indexpage to be accessible via the indexpage
  77. # object on templates. We use this on the homepage to show featured
  78. # sections of the site and their child pages
  79. def children(self):
  80. return self.get_children().specific().live()
  81. # Overrides the context to list all child
  82. # items, that are live, by the date that they were published
  83. # http://docs.wagtail.io/en/v1.9/getting_started/tutorial.html#overriding-context
  84. def get_context(self, request):
  85. context = super(LocationsIndexPage, self).get_context(request)
  86. context['locations'] = LocationPage.objects.descendant_of(
  87. self).live().order_by(
  88. 'title')
  89. return context
  90. content_panels = Page.content_panels + [
  91. FieldPanel('introduction', classname="full"),
  92. ImageChooserPanel('image'),
  93. ]
  94. class LocationPage(BasePageFieldsMixin, Page):
  95. """
  96. Detail for a specific bakery location.
  97. """
  98. address = models.TextField()
  99. lat_long = models.CharField(
  100. max_length=36,
  101. help_text="Comma separated lat/long. (Ex. 64.144367, -21.939182) \
  102. Right click Google Maps and select 'What\'s Here'",
  103. validators=[
  104. RegexValidator(
  105. regex='^(\-?\d+(\.\d+)?),\s*(\-?\d+(\.\d+)?)$',
  106. message='Lat Long must be a comma-separated numeric lat and long',
  107. code='invalid_lat_long'
  108. ),
  109. ]
  110. )
  111. # Search index configuration
  112. search_fields = Page.search_fields + [
  113. index.SearchField('address'),
  114. index.SearchField('body'),
  115. ]
  116. # Fields to show to the editor in the admin view
  117. content_panels = [
  118. FieldPanel('title', classname="full"),
  119. FieldPanel('introduction', classname="full"),
  120. ImageChooserPanel('image'),
  121. FieldPanel('address', classname="full"),
  122. FieldPanel('lat_long'),
  123. InlinePanel('hours_of_operation', label="Hours of Operation"),
  124. ]
  125. def __str__(self):
  126. return self.title
  127. @property
  128. def operating_hours(self):
  129. hours = self.hours_of_operation.all()
  130. return hours
  131. # Determines if the location is currently open. It is timezone naive
  132. def is_open(self):
  133. now = datetime.now()
  134. current_time = now.time()
  135. current_day = now.strftime('%a').upper()
  136. try:
  137. self.operating_hours.get(
  138. day=current_day,
  139. opening_time__lte=current_time,
  140. closing_time__gte=current_time
  141. )
  142. return True
  143. except LocationOperatingHours.DoesNotExist:
  144. return False
  145. # Makes additional context available to the template so that we can access
  146. # the latitude, longitude and map API key to render the map
  147. def get_context(self, request):
  148. context = super(LocationPage, self).get_context(request)
  149. context['lat'] = self.lat_long.split(",")[0]
  150. context['long'] = self.lat_long.split(",")[1]
  151. context['google_map_api_key'] = settings.GOOGLE_MAP_API_KEY
  152. return context
  153. # Can only be placed under a LocationsIndexPage object
  154. parent_page_types = ['LocationsIndexPage']