123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186 |
- from datetime import datetime
- from django.conf import settings
- from django.core.validators import RegexValidator
- from django.db import models
- from modelcluster.fields import ParentalKey
- from wagtail.admin.panels import FieldPanel, InlinePanel
- from wagtail.fields import StreamField
- from wagtail.models import Orderable, Page
- from wagtail.search import index
- from bakerydemo.base.blocks import BaseStreamBlock
- from bakerydemo.locations.choices import DAY_CHOICES
- class OperatingHours(models.Model):
- """
- A Django model to capture operating hours for a Location
- """
- day = models.CharField(max_length=3, choices=DAY_CHOICES, default="MON")
- opening_time = models.TimeField(blank=True, null=True)
- closing_time = models.TimeField(blank=True, null=True)
- closed = models.BooleanField(
- "Closed?", blank=True, help_text="Tick if location is closed on this day"
- )
- panels = [
- FieldPanel("day"),
- FieldPanel("opening_time"),
- FieldPanel("closing_time"),
- FieldPanel("closed"),
- ]
- class Meta:
- abstract = True
- def __str__(self):
- if self.opening_time:
- opening = self.opening_time.strftime("%H:%M")
- else:
- opening = "--"
- if self.closing_time:
- closed = self.closing_time.strftime("%H:%M")
- else:
- closed = "--"
- return "{}: {} - {} {}".format(self.day, opening, closed, settings.TIME_ZONE)
- class LocationOperatingHours(Orderable, OperatingHours):
- """
- A model creating a relationship between the OperatingHours and Location
- Note that unlike BlogPersonRelationship we don't include a ForeignKey to
- OperatingHours as we don't need that relationship (e.g. any Location open
- a certain day of the week). The ParentalKey is the minimum required to
- relate the two objects to one another. We use the ParentalKey's related_
- name to access it from the LocationPage admin
- """
- location = ParentalKey(
- "LocationPage", related_name="hours_of_operation", on_delete=models.CASCADE
- )
- class LocationsIndexPage(Page):
- """
- A Page model that creates an index page (a listview)
- """
- introduction = models.TextField(help_text="Text to describe the page", blank=True)
- image = models.ForeignKey(
- "wagtailimages.Image",
- null=True,
- blank=True,
- on_delete=models.SET_NULL,
- related_name="+",
- help_text="Landscape mode only; horizontal width between 1000px and 3000px.",
- )
- # Only LocationPage objects can be added underneath this index page
- subpage_types = ["LocationPage"]
- # Allows children of this indexpage to be accessible via the indexpage
- # object on templates. We use this on the homepage to show featured
- # sections of the site and their child pages
- def children(self):
- return self.get_children().specific().live()
- # Overrides the context to list all child
- # items, that are live, by the title alphabetical order.
- # https://docs.wagtail.org/en/stable/getting_started/tutorial.html#overriding-context
- def get_context(self, request):
- context = super(LocationsIndexPage, self).get_context(request)
- context["locations"] = (
- LocationPage.objects.descendant_of(self).live().order_by("title")
- )
- return context
- content_panels = Page.content_panels + [
- FieldPanel("introduction"),
- FieldPanel("image"),
- ]
- class LocationPage(Page):
- """
- Detail for a specific bakery location.
- """
- introduction = models.TextField(help_text="Text to describe the page", blank=True)
- image = models.ForeignKey(
- "wagtailimages.Image",
- null=True,
- blank=True,
- on_delete=models.SET_NULL,
- related_name="+",
- help_text="Landscape mode only; horizontal width between 1000px and 3000px.",
- )
- body = StreamField(
- BaseStreamBlock(), verbose_name="Page body", blank=True, use_json_field=True
- )
- address = models.TextField()
- lat_long = models.CharField(
- max_length=36,
- help_text="Comma separated lat/long. (Ex. 64.144367, -21.939182) \
- Right click Google Maps and select 'What's Here'",
- validators=[
- RegexValidator(
- regex=r"^(\-?\d+(\.\d+)?),\s*(\-?\d+(\.\d+)?)$",
- message="Lat Long must be a comma-separated numeric lat and long",
- code="invalid_lat_long",
- ),
- ],
- )
- # Search index configuration
- search_fields = Page.search_fields + [
- index.SearchField("address"),
- index.SearchField("body"),
- ]
- # Fields to show to the editor in the admin view
- content_panels = [
- FieldPanel("title"),
- FieldPanel("introduction"),
- FieldPanel("image"),
- FieldPanel("body"),
- FieldPanel("address"),
- FieldPanel("lat_long"),
- InlinePanel("hours_of_operation", heading="Hours of Operation", label="Slot"),
- ]
- def __str__(self):
- return self.title
- @property
- def operating_hours(self):
- hours = self.hours_of_operation.all()
- return hours
- # Determines if the location is currently open. It is timezone naive
- def is_open(self):
- now = datetime.now()
- current_time = now.time()
- current_day = now.strftime("%a").upper()
- try:
- self.operating_hours.get(
- day=current_day,
- opening_time__lte=current_time,
- closing_time__gte=current_time,
- )
- return True
- except LocationOperatingHours.DoesNotExist:
- return False
- # Makes additional context available to the template so that we can access
- # the latitude, longitude and map API key to render the map
- def get_context(self, request):
- context = super(LocationPage, self).get_context(request)
- context["lat"] = self.lat_long.split(",")[0]
- context["long"] = self.lat_long.split(",")[1]
- context["google_map_api_key"] = settings.GOOGLE_MAP_API_KEY
- return context
- # Can only be placed under a LocationsIndexPage object
- parent_page_types = ["LocationsIndexPage"]
|