Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DBC22-1261: Ferry layer #199

Merged
merged 5 commits into from
Nov 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ REACT_APP_GEOCODER_HOST=<geocoder url>
REACT_APP_GEOCODER_API_AUTH_KEY=<api auth key>

# API
DRIVEBC_INLAND_FERRY_API_BASE_URL=<inland ferry api url>
DRIVEBC_IMAGE_API_BASE_URL=<image api url>
DRIVEBC_IMAGE_PROXY_URL=<image proxy url>
DRIVEBC_WEBCAM_API_BASE_URL=<camera api url>
Expand Down
55 changes: 55 additions & 0 deletions src/backend/apps/cms/migrations/0009_ferry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Generated by Django 4.2.3 on 2023-11-22 00:48

import django.contrib.gis.db.models.fields
import django.db.models.deletion
import wagtail.fields
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('wagtailcore', '0089_log_entry_data_json_null_to_object'),
('wagtailimages', '0025_alter_image_file_alter_rendition_file'),
('cms', '0008_bulletin_image'),
]

operations = [
migrations.CreateModel(
name='Ferry',
fields=[
('page_ptr', models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True, primary_key=True, serialize=False,
to='wagtailcore.page'
)),
('created_at', models.DateTimeField(auto_now_add=True)),
('modified_at', models.DateTimeField(auto_now=True)),
('location', django.contrib.gis.db.models.fields.GeometryField(
srid=4326
)),
('url', models.URLField(blank=True)),
('description', wagtail.fields.RichTextField(
blank=True, max_length=750
)),
('seasonal_description', wagtail.fields.RichTextField(
blank=True, max_length=100
)),
('service_hours', wagtail.fields.RichTextField(
blank=True, max_length=750
)),
('feed_created_at', models.DateTimeField()),
('feed_modified_at', models.DateTimeField()),
('image', models.ForeignKey(
blank=True, null=True,
on_delete=django.db.models.deletion.CASCADE,
to='wagtailimages.image'
)),
],
options={
'abstract': False,
},
bases=('wagtailcore.page', models.Model),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 4.2.3 on 2023-11-22 03:58

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('cms', '0009_ferry'),
]

operations = [
migrations.AlterField(
model_name='ferry',
name='feed_created_at',
field=models.DateTimeField(auto_now_add=True),
),
migrations.AlterField(
model_name='ferry',
name='feed_modified_at',
field=models.DateTimeField(auto_now=True),
),
]
21 changes: 21 additions & 0 deletions src/backend/apps/cms/migrations/0011_alter_ferry_location.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Generated by Django 4.2.3 on 2023-11-24 01:05

import django.contrib.gis.db.models.fields
from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('cms', '0010_alter_ferry_feed_created_at_and_more'),
]

operations = [
migrations.AlterField(
model_name='ferry',
name='location',
field=django.contrib.gis.db.models.fields.GeometryField(
blank=True, null=True, srid=4326
),
),
]
19 changes: 19 additions & 0 deletions src/backend/apps/cms/migrations/0012_ferry_feed_id.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Generated by Django 4.2.3 on 2023-11-25 00:48

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('cms', '0011_alter_ferry_location'),
]

operations = [
migrations.AddField(
model_name='ferry',
name='feed_id',
field=models.PositiveIntegerField(default=None, unique=True),
preserve_default=False,
),
]
53 changes: 53 additions & 0 deletions src/backend/apps/cms/models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from apps.shared.enums import CacheKey
from apps.shared.models import BaseModel
from django.contrib.gis.db import models
from django.contrib.gis.forms import OSMWidget
from django.core.cache import cache
from wagtail.admin.panels import FieldPanel
from wagtail.api import APIField
from wagtail.fields import RichTextField
Expand Down Expand Up @@ -33,6 +35,7 @@ def rendered_body(self):

def save(self, *args, **kwargs):
super().save(*args, **kwargs)
cache.delete(CacheKey.ADVISORY_LIST)

# Editor panels configuration
content_panels = [
Expand Down Expand Up @@ -61,6 +64,7 @@ def rendered_body(self):

def save(self, *args, **kwargs):
super().save(*args, **kwargs)
cache.delete(CacheKey.BULLETIN_LIST)

# Editor panels configuration
content_panels = [
Expand All @@ -72,3 +76,52 @@ def save(self, *args, **kwargs):
promote_panels = []

template = 'cms/bulletin.html'


class Ferry(Page, BaseModel):
page_body = "Use this page to create or update ferry entries."

feed_id = models.PositiveIntegerField(unique=True)

location = models.GeometryField(blank=True, null=True)

url = models.URLField(blank=True)
image = models.ForeignKey(Image, on_delete=models.CASCADE, blank=True, null=True)

description = RichTextField(max_length=750, blank=True)
seasonal_description = RichTextField(max_length=100, blank=True)
service_hours = RichTextField(max_length=750, blank=True)

feed_created_at = models.DateTimeField(auto_now_add=True)
feed_modified_at = models.DateTimeField(auto_now=True)

def rendered_description(self):
return wagtailcore_tags.richtext(self.description)

def rendered_seasonal_description(self):
return wagtailcore_tags.richtext(self.seasonal_description)

def rendered_service_hours(self):
return wagtailcore_tags.richtext(self.service_hours)

api_fields = [
APIField('rendered_description'),
APIField('rendered_seasonal_description'),
APIField('rendered_service_hours'),
]

def save(self, *args, **kwargs):
super().save(*args, **kwargs)
cache.delete(CacheKey.FERRY_LIST)

# Editor panels configuration
content_panels = [
FieldPanel("title"),
FieldPanel("image"),
FieldPanel("description"),
FieldPanel("seasonal_description"),
FieldPanel("service_hours"),
]
promote_panels = []

template = 'cms/ferry.html'
43 changes: 37 additions & 6 deletions src/backend/apps/cms/serializers.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from apps.cms.models import Advisory, Bulletin
from apps.cms.models import Advisory, Bulletin, Ferry
from rest_framework import serializers
from wagtail.templatetags import wagtailcore_tags

Expand All @@ -16,16 +16,14 @@


class CMSSerializer(serializers.ModelSerializer):
body = serializers.SerializerMethodField()

def get_host(self):
request = self.context.get("request")
prefix = "https://" if request and request.is_secure() else "http://"
return prefix + request.get_host() if request else 'localhost:8000'

# get rendered html elements for body and access static media foder
def get_body(self, obj):
res = wagtailcore_tags.richtext(obj.body)
# get rendered html elements and access static media folder
def get_richtext(self, text):
res = wagtailcore_tags.richtext(text)
res = res.replace(
'href="/drivebc-cms',
'href="' + self.get_host() + '/drivebc-cms'
Expand All @@ -38,12 +36,18 @@ def get_body(self, obj):


class AdvisorySerializer(CMSSerializer):
body = serializers.SerializerMethodField()

class Meta:
model = Advisory
fields = "__all__"

def get_body(self, obj):
return self.get_richtext(obj.body)


class BulletinSerializer(CMSSerializer):
body = serializers.SerializerMethodField()
image_url = serializers.SerializerMethodField('get_image_url')

class Meta:
Expand All @@ -53,3 +57,30 @@ class Meta:
def get_image_url(self, obj):
request = self.context.get("request")
return request.build_absolute_uri(obj.image.file.url) if obj.image else ''

def get_body(self, obj):
return self.get_richtext(obj.body)


class FerrySerializer(CMSSerializer):
description = serializers.SerializerMethodField()
seasonal_description = serializers.SerializerMethodField()
service_hours = serializers.SerializerMethodField()
image_url = serializers.SerializerMethodField('get_image_url')

class Meta:
model = Ferry
fields = "__all__"

def get_image_url(self, obj):
request = self.context.get("request")
return request.build_absolute_uri(obj.image.file.url) if obj.image else ''

def get_description(self, obj):
return self.get_richtext(obj.description)

def get_seasonal_description(self, obj):
return self.get_richtext(obj.seasonal_description)

def get_service_hours(self, obj):
return self.get_richtext(obj.service_hours)
52 changes: 52 additions & 0 deletions src/backend/apps/cms/tasks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import logging

from apps.cms.models import Ferry
from apps.feed.client import FeedClient
from apps.shared.enums import CacheKey
from django.contrib.contenttypes.models import ContentType
from django.contrib.gis.geos import Point
from django.core.cache import cache
from django.core.exceptions import ObjectDoesNotExist
from django.utils.text import slugify
from wagtail.models import Page

logger = logging.getLogger(__name__)


def populate_ferry_from_data(ferry_data):
ferry_id = ferry_data.get('id')

try:
ferry = Ferry.objects.get(feed_id=ferry_id)

except ObjectDoesNotExist:
# Generate Page associated with ferry obj
root_page = Page.get_root_nodes()[0]

# New ferry obj
ferry = Ferry(
feed_id=ferry_data['id'],
title=ferry_data['title'],
slug=slugify(ferry_data['title']),
content_type=ContentType.objects.get_for_model(Ferry),
)

root_page.add_child(instance=ferry)
ferry.save_revision().publish()

point = Point(ferry_data['location']['coordinates'], srid=4326)

ferry.location = point
ferry.url = ferry_data['url']
ferry.feed_created_at = ferry_data['feed_created_at']
ferry.feed_modified_at = ferry_data['feed_modified_at']
ferry.save()


def populate_all_ferry_data():
feed_data = FeedClient().get_ferries_list()['features']
for ferry_data in feed_data:
populate_ferry_from_data(ferry_data)

# Reset
cache.delete(CacheKey.FERRY_LIST)
22 changes: 22 additions & 0 deletions src/backend/apps/cms/templates/cms/ferry.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{% load wagtailcore_tags %}

{% block body_class %}template-blogindexpage{% endblock %}

{% load static %}

{% block styles %}
<link rel="stylesheet" type="text/css" href="{% static '../cms/templates/styles.css' %}">
{% endblock %}

{% block content %}
<div class="title">
<h1>{{ page.title|safe }}</h1>
<h2>Published at {{ page.created_at|safe }}</h2>
<h2>Last updated at {{ page.modified_at|safe }}</h2>
<div class="description">
{{ page.seasonal_description|safe }}
{{ page.description|safe }}
{{ page.service_hours|safe }}
</div>
</div>
{% endblock %}
14 changes: 7 additions & 7 deletions src/backend/apps/cms/tests/test_advisory_api.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from apps.cms.models import Advisory
from apps.event.views import EventAPI
from apps.cms.views import AdvisoryAPI
from apps.shared.enums import CacheKey
from apps.shared.tests import BaseTest
from django.contrib.contenttypes.models import ContentType
Expand Down Expand Up @@ -35,20 +35,20 @@ def setUp(self):

def test_advisory_list_caching(self):
# Empty cache
assert cache.get(CacheKey.DELAY_LIST) is None
assert cache.get(CacheKey.ADVISORY_LIST) is None

# Cache miss
url = "/api/cms/advisories/"
response = self.client.get(url, {})
assert len(response.data) == 2
assert cache.get(CacheKey.DELAY_LIST) is None
assert cache.get(CacheKey.ADVISORY_LIST) is not None

# Cached result
Advisory.objects.filter(id__gte=2).delete()
Advisory.objects.first().delete()
response = self.client.get(url, {})
assert len(response.data) == 0
assert len(response.data) == 2

# Updated cached result
EventAPI().set_list_data()
AdvisoryAPI().set_list_data()
response = self.client.get(url, {})
assert len(response.data) == 0
assert len(response.data) == 1
Loading