From 6bb52dab362cc55a2111fbc33def23be8dc27790 Mon Sep 17 00:00:00 2001 From: bcgov-brwang Date: Wed, 13 Mar 2024 11:30:16 -0700 Subject: [PATCH 1/4] DBC22-1813: updated events side panel styling according to the design DBC22-1813: updated events side panel styling according to the design --- src/frontend/src/Components/Map.scss | 2 +- src/frontend/src/Components/map/mapPopup.js | 23 +++++++++------- src/frontend/src/Components/map/mapPopup.scss | 26 ++++++++++++++++--- 3 files changed, 37 insertions(+), 14 deletions(-) diff --git a/src/frontend/src/Components/Map.scss b/src/frontend/src/Components/Map.scss index b4206e904..88eb9cf45 100644 --- a/src/frontend/src/Components/Map.scss +++ b/src/frontend/src/Components/Map.scss @@ -99,7 +99,7 @@ right: 1rem; padding: 0; min-width: 0; - line-height: 1.25rem; + line-height: 8.25rem; height: auto; background: none; border: none; diff --git a/src/frontend/src/Components/map/mapPopup.js b/src/frontend/src/Components/map/mapPopup.js index abbcb69fb..2d97acf6e 100644 --- a/src/frontend/src/Components/map/mapPopup.js +++ b/src/frontend/src/Components/map/mapPopup.js @@ -90,17 +90,22 @@ export function getEventPopup(eventFeature) {

{eventData.optimized_description}

-
-

Last update

- -
- - {eventData.next_update && +
-

Next update

- +

Last update

+
- } + + {eventData.next_update && +
+

Next update

+ +
+ } + +
+ + ); diff --git a/src/frontend/src/Components/map/mapPopup.scss b/src/frontend/src/Components/map/mapPopup.scss index 3a0e5d2dd..f531ef01e 100644 --- a/src/frontend/src/Components/map/mapPopup.scss +++ b/src/frontend/src/Components/map/mapPopup.scss @@ -176,17 +176,26 @@ } } &__description { + > p:first-child { + font-weight: bold; + } .friendly-time { margin-bottom: 0.5rem; - &-text { - font-weight: 700; - } - &__tooltip { left: 100%; } } } + + &__block { + display: flex; + justify-content: space-between; + + > div { + flex: 1; + } + + } } &.major, &.closures { @@ -197,6 +206,7 @@ .name { color: #CE3E39; } + } .popup__content { @@ -204,6 +214,10 @@ .name { color: #CE3E39; } + + .location { + color: #9C2024; + } } } } @@ -223,6 +237,10 @@ .name { color: #584215; } + + .location { + color: #474543; + } } } } From d51136dbeb6ceb5bb5b247632bad1a2c881771fb Mon Sep 17 00:00:00 2001 From: bcgov-brwang Date: Thu, 14 Mar 2024 10:23:46 -0700 Subject: [PATCH 2/4] DBC22-1813: decreased spaces between elements and decreased text size according to the design DBC22-1813: decreased spaces between elements and decreased text size according to the design --- src/frontend/src/Components/map/mapPopup.scss | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/frontend/src/Components/map/mapPopup.scss b/src/frontend/src/Components/map/mapPopup.scss index f531ef01e..e1e3845e1 100644 --- a/src/frontend/src/Components/map/mapPopup.scss +++ b/src/frontend/src/Components/map/mapPopup.scss @@ -106,7 +106,7 @@ } &__description { - padding: 1rem 1rem 0.5rem; + padding: 1rem 1rem 0.1rem; } } @@ -173,12 +173,23 @@ .direction { font-size: 0.875rem; color: $Grey70; + margin-bottom: 0; } } &__description { > p:first-child { font-weight: bold; + font-size: 14px; + height: 21px; + margin-bottom: 0; + } + + > p:nth-child(2) { + font-size: 14px; + margin-top: 0; + margin-bottom: 0; } + .friendly-time { margin-bottom: 0.5rem; &__tooltip { @@ -204,7 +215,7 @@ border-top: 4px solid #CE3E39; .name { - color: #CE3E39; + color: #CE3E39; } } @@ -213,10 +224,13 @@ &__title { .name { color: #CE3E39; + font-size: 24px; } .location { color: #9C2024; + font-size: 16px; + margin-top: 0; } } } From 34cf729289972fefa388800268856f22bf02e217 Mon Sep 17 00:00:00 2001 From: bcgov-brwang Date: Fri, 15 Mar 2024 13:19:20 -0700 Subject: [PATCH 3/4] DBC22-1884: implemented rest stop backend api DBC22-1884: implemented rest stop backend api --- src/backend/apps/feed/client.py | 51 +++++++++++++++++++ src/backend/apps/feed/constants.py | 1 + src/backend/apps/feed/serializers.py | 11 ++++ src/backend/apps/feed/tasks.py | 7 ++- src/backend/apps/rest/__init__.py | 0 src/backend/apps/rest/admin.py | 10 ++++ src/backend/apps/rest/apps.py | 6 +++ .../apps/rest/migrations/0001_initial.py | 31 +++++++++++ src/backend/apps/rest/migrations/__init__.py | 0 src/backend/apps/rest/models.py | 19 +++++++ src/backend/apps/rest/serializers.py | 8 +++ src/backend/apps/rest/tasks.py | 38 ++++++++++++++ src/backend/apps/rest/tests.py | 3 ++ src/backend/apps/rest/urls.py | 11 ++++ src/backend/apps/rest/views.py | 22 ++++++++ src/backend/apps/shared/api.py | 3 ++ src/backend/apps/shared/enums.py | 2 + src/backend/config/settings/django.py | 1 + src/backend/config/settings/drivebc.py | 3 +- 19 files changed, 225 insertions(+), 2 deletions(-) create mode 100644 src/backend/apps/rest/__init__.py create mode 100644 src/backend/apps/rest/admin.py create mode 100644 src/backend/apps/rest/apps.py create mode 100644 src/backend/apps/rest/migrations/0001_initial.py create mode 100644 src/backend/apps/rest/migrations/__init__.py create mode 100644 src/backend/apps/rest/models.py create mode 100644 src/backend/apps/rest/serializers.py create mode 100644 src/backend/apps/rest/tasks.py create mode 100644 src/backend/apps/rest/tests.py create mode 100644 src/backend/apps/rest/urls.py create mode 100644 src/backend/apps/rest/views.py diff --git a/src/backend/apps/feed/client.py b/src/backend/apps/feed/client.py index 9b4aa04b0..506ea0af9 100644 --- a/src/backend/apps/feed/client.py +++ b/src/backend/apps/feed/client.py @@ -11,6 +11,7 @@ REGIONAL_WEATHER, REGIONAL_WEATHER_AREAS, WEBCAM, + REST_STOP, ) from apps.feed.serializers import ( CarsEventSerializer, @@ -20,6 +21,7 @@ RegionalWeatherSerializer, WebcamAPISerializer, WebcamFeedSerializer, + RestStopSerializer, ) from django.conf import settings from rest_framework.exceptions import ValidationError @@ -51,6 +53,9 @@ def __init__(self): REGIONAL_WEATHER_AREAS: { "base_url": settings.DRIVEBC_WEATHER_AREAS_API_BASE_URL, }, + REST_STOP: { + "base_url": settings.DRIVEBC_REST_STOP_API_BASE_URL, + }, } def _get_auth_headers(self, resource_type): @@ -322,8 +327,54 @@ def get_regional_weather_list_feed(self, resource_type, resource_name, serialize for field, errors in field_errors.items(): print(f"Field: {field}, Errors: {errors}") + # Rest Stop + def get_rest_stop_list_feed(self, resource_type, resource_name, serializer_cls, params=None): + """Get data feed for list of objects.""" + rest_stop_api_url = settings.DRIVEBC_REST_STOP_API_BASE_URL + + try: + response = requests.get(rest_stop_api_url) + response.raise_for_status() + data = response.json() + json_response = data + json_objects = [] + for entry in json_response["features"]: + rest_stop_id = entry['id'] + geometry = entry["geometry"] + properties = entry["properties"] + bbox = entry["bbox"] + rest_stop_data = { + 'rest_stop_id': rest_stop_id, + 'geometry': geometry, + 'properties': properties, + 'bbox': bbox, + } + + serializer = serializer_cls(data=rest_stop_data, + many=isinstance(rest_stop_data, list)) + json_objects.append(rest_stop_data) + + except requests.RequestException as e: + return Response({"error": f"Error fetching data from rest stop API: {str(e)}"}, status=500) + + try: + serializer.is_valid(raise_exception=True) + return json_objects + + except (KeyError, ValidationError): + field_errors = serializer.errors + for field, errors in field_errors.items(): + print(f"Field: {field}, Errors: {errors}") + + def get_regional_weather_list(self): return self.get_regional_weather_list_feed( REGIONAL_WEATHER, 'regionalweather', RegionalWeatherSerializer, {"format": "json", "limit": 500} ) + + def get_rest_stop_list(self): + return self.get_rest_stop_list_feed( + REST_STOP, 'reststop', RestStopSerializer, + {"format": "json", "limit": 500} + ) \ No newline at end of file diff --git a/src/backend/apps/feed/constants.py b/src/backend/apps/feed/constants.py index 1a897e520..177520636 100644 --- a/src/backend/apps/feed/constants.py +++ b/src/backend/apps/feed/constants.py @@ -5,6 +5,7 @@ INLAND_FERRY = "inland_ferry" REGIONAL_WEATHER = "regional_weather" REGIONAL_WEATHER_AREAS = "regional_weather_areas" +REST_STOP = "rest_stop" DIRECTIONS = { 'in both directions': 'BOTH', diff --git a/src/backend/apps/feed/serializers.py b/src/backend/apps/feed/serializers.py index 87d41aa81..9fb6009eb 100644 --- a/src/backend/apps/feed/serializers.py +++ b/src/backend/apps/feed/serializers.py @@ -17,6 +17,7 @@ WebcamRegionGroupField, ) from apps.weather.models import RegionalWeather +from apps.rest.models import RestStop from rest_framework import serializers @@ -239,3 +240,13 @@ class Meta: 'forecast_group', 'hourly_forecast_group', ) + +class RestStopSerializer(serializers.Serializer): + class Meta: + model = RestStop + fields = ( + 'id', + 'geometry', + 'properties', + 'bbox', + ) diff --git a/src/backend/apps/feed/tasks.py b/src/backend/apps/feed/tasks.py index a11cc38c6..0fe48a9fa 100644 --- a/src/backend/apps/feed/tasks.py +++ b/src/backend/apps/feed/tasks.py @@ -2,6 +2,7 @@ from apps.event.tasks import populate_all_event_data from apps.webcam.tasks import populate_all_webcam_data, update_all_webcam_data from apps.weather.tasks import populate_all_regional_weather_data +from apps.rest.tasks import populate_all_rest_stop_data from django.core.management import call_command from huey import crontab from huey.contrib.djhuey import db_periodic_task @@ -33,4 +34,8 @@ def publish_scheduled(): @db_periodic_task(crontab(minute="*/5")) def populate_regional_weather_task(): - populate_all_regional_weather_data() \ No newline at end of file + populate_all_regional_weather_data() + +@db_periodic_task(crontab(minute="*/5")) +def populate_rest_stop_task(): + populate_all_rest_stop_data() \ No newline at end of file diff --git a/src/backend/apps/rest/__init__.py b/src/backend/apps/rest/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/backend/apps/rest/admin.py b/src/backend/apps/rest/admin.py new file mode 100644 index 000000000..074291304 --- /dev/null +++ b/src/backend/apps/rest/admin.py @@ -0,0 +1,10 @@ +from apps.rest.models import RestStop +from django.contrib import admin +from django.contrib.admin import ModelAdmin + + +class RestStopAdmin(ModelAdmin): + readonly_fields = ('id', ) + + +admin.site.register(RestStop, RestStopAdmin) \ No newline at end of file diff --git a/src/backend/apps/rest/apps.py b/src/backend/apps/rest/apps.py new file mode 100644 index 000000000..ec349db04 --- /dev/null +++ b/src/backend/apps/rest/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class RestConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'apps.rest' diff --git a/src/backend/apps/rest/migrations/0001_initial.py b/src/backend/apps/rest/migrations/0001_initial.py new file mode 100644 index 000000000..ea5f76f66 --- /dev/null +++ b/src/backend/apps/rest/migrations/0001_initial.py @@ -0,0 +1,31 @@ +# Generated by Django 4.2.3 on 2024-03-15 20:15 + +import django.contrib.gis.db.models.fields +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='RestStop', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('modified_at', models.DateTimeField(auto_now=True)), + ('rest_stop_id', models.CharField(max_length=100, null=True)), + ('location', django.contrib.gis.db.models.fields.PointField(null=True, srid=4326)), + ('geometry', models.JSONField(null=True)), + ('properties', models.JSONField(null=True)), + ('bbox', models.JSONField(null=True)), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/src/backend/apps/rest/migrations/__init__.py b/src/backend/apps/rest/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/backend/apps/rest/models.py b/src/backend/apps/rest/models.py new file mode 100644 index 000000000..195a3af12 --- /dev/null +++ b/src/backend/apps/rest/models.py @@ -0,0 +1,19 @@ +from apps.shared.models import BaseModel +from django.contrib.gis.db import models +from django.contrib.gis.geos import Point + + +class RestStop(BaseModel): + rest_stop_id = models.CharField(max_length=100, null=True) + location = models.PointField(null=True) + geometry = models.JSONField(null=True) + properties = models.JSONField(null=True) + bbox = models.JSONField(null=True) + + def __str__(self): + return f"Rest Stop for {self.pk}" + + def save(self, *args, **kwargs): + latitude, longitude = self.geometry.get("coordinates")[0], self.geometry.get("coordinates")[1] + self.location = Point(longitude, latitude) + super().save(*args, **kwargs) diff --git a/src/backend/apps/rest/serializers.py b/src/backend/apps/rest/serializers.py new file mode 100644 index 000000000..703ebd433 --- /dev/null +++ b/src/backend/apps/rest/serializers.py @@ -0,0 +1,8 @@ +from apps.rest.models import RestStop +from rest_framework import serializers + + +class RestStopSerializer(serializers.ModelSerializer): + class Meta: + model = RestStop + exclude = ['geometry'] diff --git a/src/backend/apps/rest/tasks.py b/src/backend/apps/rest/tasks.py new file mode 100644 index 000000000..090cb0f0a --- /dev/null +++ b/src/backend/apps/rest/tasks.py @@ -0,0 +1,38 @@ +import logging + +from apps.feed.client import FeedClient +from apps.shared.enums import CacheKey +from apps.rest.models import RestStop +from django.core.cache import cache + +logger = logging.getLogger(__name__) + + +def populate_rest_stop_from_data(new_rest_stop_data): + rest_stop_id = new_rest_stop_data.get('rest_stop_id') + geometry = new_rest_stop_data.get('geometry') + + existing_record = RestStop.objects.filter(rest_stop_id=rest_stop_id).first() + data = { + 'rest_stop_id': rest_stop_id, + 'geometry': geometry, + 'properties': new_rest_stop_data.get('properties'), + 'bbox': new_rest_stop_data.get('bbox'), + } + + if existing_record: + existing_record.__dict__.update(data) + existing_record.save() + else: + RestStop.objects.create(**data) + + +def populate_all_rest_stop_data(): + client = FeedClient() + feed_data = client.get_rest_stop_list() + + for rest_stop_data in feed_data: + populate_rest_stop_from_data(rest_stop_data) + + # Rebuild cache + cache.delete(CacheKey.REGIONAL_WEATHER_LIST) \ No newline at end of file diff --git a/src/backend/apps/rest/tests.py b/src/backend/apps/rest/tests.py new file mode 100644 index 000000000..7ce503c2d --- /dev/null +++ b/src/backend/apps/rest/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/src/backend/apps/rest/urls.py b/src/backend/apps/rest/urls.py new file mode 100644 index 000000000..ab9de6b2f --- /dev/null +++ b/src/backend/apps/rest/urls.py @@ -0,0 +1,11 @@ +from apps.rest import views as rest_stop_views +from django.urls import include, path +from rest_framework import routers + +rest_stop_router = routers.DefaultRouter() +rest_stop_router.register(r"", rest_stop_views.RestStopViewSet, basename="reststop") + +urlpatterns = [ + path('reststop', rest_stop_views.RestStopViewSet.as_view({'get': 'reststop'}), name='reststop'), + path('', include(rest_stop_router.urls)), +] diff --git a/src/backend/apps/rest/views.py b/src/backend/apps/rest/views.py new file mode 100644 index 000000000..2de885545 --- /dev/null +++ b/src/backend/apps/rest/views.py @@ -0,0 +1,22 @@ +from apps.shared.enums import CacheKey, CacheTimeout +from apps.shared.views import CachedListModelMixin +from apps.rest.models import RestStop +from apps.rest.serializers import RestStopSerializer +from rest_framework import status, viewsets +from rest_framework.decorators import action +from rest_framework.response import Response + + +class RestStopAPI(CachedListModelMixin): + queryset = RestStop.objects.all() + serializer_class = RestStopSerializer + cache_key = CacheKey.REST_STOP_LIST + cache_timeout = CacheTimeout.REST_STOP_LIST + + +class RestStopViewSet(RestStopAPI, viewsets.ReadOnlyModelViewSet): + @action(detail=True, methods=['get']) + def reststop(self, request, pk=None): + rest_stop_objects = RestStop.objects.all() + serializer = RestStopSerializer(rest_stop_objects, many=True) + return Response(serializer.data, status=status.HTTP_200_OK) diff --git a/src/backend/apps/shared/api.py b/src/backend/apps/shared/api.py index 10cd0b7ec..1c03b7179 100644 --- a/src/backend/apps/shared/api.py +++ b/src/backend/apps/shared/api.py @@ -17,6 +17,9 @@ # Weather path("weather/", include("apps.weather.urls")), + # Rest Stop + path("reststop/", include("apps.rest.urls")), + # Others path("feedback/", views.FeedbackView.as_view(), name="feedback"), ] diff --git a/src/backend/apps/shared/enums.py b/src/backend/apps/shared/enums.py index 9054b2be7..46bfeef7e 100644 --- a/src/backend/apps/shared/enums.py +++ b/src/backend/apps/shared/enums.py @@ -47,6 +47,7 @@ class CacheTimeout: EVENT_LIST = 60 * 15 # 5min buffer + (2*5)min twice of task interval FERRY_LIST = 60*60*24 # 24hr REGIONAL_WEATHER_LIST = 60 * 15 # 5min buffer + (2*5)min twice of task interval + REST_STOP_LIST = 60 * 15 # 5min buffer + (2*5)min twice of task interval class CacheKey: @@ -58,6 +59,7 @@ class CacheKey: FERRY_LIST = "ferry_list" TEST_APP_CACHE = "test_app_cache" REGIONAL_WEATHER_LIST = "regional_weather_list" + REST_STOP_LIST = "rest_stop_list" ROUTE_FILTER_TOLERANCE = 25 diff --git a/src/backend/config/settings/django.py b/src/backend/config/settings/django.py index 91afdc737..d9ac2432e 100644 --- a/src/backend/config/settings/django.py +++ b/src/backend/config/settings/django.py @@ -128,6 +128,7 @@ "apps.webcam", "apps.cms", "apps.weather", + "apps.rest", ] INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS diff --git a/src/backend/config/settings/drivebc.py b/src/backend/config/settings/drivebc.py index be81428dd..fe1efe5bf 100644 --- a/src/backend/config/settings/drivebc.py +++ b/src/backend/config/settings/drivebc.py @@ -17,4 +17,5 @@ WEATHER_CLIENT_SECRET=env("WEATHER_CLIENT_SECRET") DRIVEBC_WEATHER_API_BASE_URL=env("DRIVEBC_WEATHER_API_BASE_URL") DRIVEBC_WEATHER_AREAS_API_BASE_URL=env("DRIVEBC_WEATHER_AREAS_API_BASE_URL") -DRIVEBC_WEATHER_API_TOKEN_URL=env("DRIVEBC_WEATHER_API_TOKEN_URL") \ No newline at end of file +DRIVEBC_WEATHER_API_TOKEN_URL=env("DRIVEBC_WEATHER_API_TOKEN_URL") +DRIVEBC_REST_STOP_API_BASE_URL=env("DRIVEBC_REST_STOP_API_BASE_URL") \ No newline at end of file From aca3bf1ba6d4c70c2ab9084e1a9cf05d735c2204 Mon Sep 17 00:00:00 2001 From: bcgov-brwang Date: Mon, 18 Mar 2024 10:46:19 -0700 Subject: [PATCH 4/4] DBC22-1884: added test cases for rest stop DBC22-1884: added test cases for rest stop --- src/backend/apps/rest/tests.py | 3 - src/backend/apps/rest/tests/__init__.py | 0 .../test_data/rest_stop_feed_list_of_one.json | 57 ++++++++ .../test_data/rest_stop_feed_list_of_two.json | 112 ++++++++++++++ .../tests/test_data/rest_stop_parsed_feed.py | 52 +++++++ .../apps/rest/tests/test_rest_stop_api.py | 109 ++++++++++++++ .../rest/tests/test_rest_stop_populate.py | 115 +++++++++++++++ .../rest/tests/test_rest_stop_serializer.py | 138 ++++++++++++++++++ src/backend/apps/shared/tests.py | 3 +- 9 files changed, 585 insertions(+), 4 deletions(-) delete mode 100644 src/backend/apps/rest/tests.py create mode 100644 src/backend/apps/rest/tests/__init__.py create mode 100644 src/backend/apps/rest/tests/test_data/rest_stop_feed_list_of_one.json create mode 100644 src/backend/apps/rest/tests/test_data/rest_stop_feed_list_of_two.json create mode 100644 src/backend/apps/rest/tests/test_data/rest_stop_parsed_feed.py create mode 100644 src/backend/apps/rest/tests/test_rest_stop_api.py create mode 100644 src/backend/apps/rest/tests/test_rest_stop_populate.py create mode 100644 src/backend/apps/rest/tests/test_rest_stop_serializer.py diff --git a/src/backend/apps/rest/tests.py b/src/backend/apps/rest/tests.py deleted file mode 100644 index 7ce503c2d..000000000 --- a/src/backend/apps/rest/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/src/backend/apps/rest/tests/__init__.py b/src/backend/apps/rest/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/backend/apps/rest/tests/test_data/rest_stop_feed_list_of_one.json b/src/backend/apps/rest/tests/test_data/rest_stop_feed_list_of_one.json new file mode 100644 index 000000000..d127f723e --- /dev/null +++ b/src/backend/apps/rest/tests/test_data/rest_stop_feed_list_of_one.json @@ -0,0 +1,57 @@ +[ + { + "id": 2, + "created_at": "2024-03-15T11:10:29.518958-07:00", + "modified_at": "2024-03-15T11:10:29.518958-07:00", + "rest_stop_id": "DBC_RIM_REST_AREA_V.fid-59dfb4f6_18e433c4f15_-52d9", + "geometry": { + "type": "Point", + "coordinates": [ + 54.66828166, + -126.99686259 + ] + }, + "properties": { + "WI_FI": "No", + "OBJECTID": 4381, + "OPEN_DATE": null, + "CLOSE_DATE": null, + "POWER_TYPE": "No Power", + "TOILET_TYPE": "Pit", + "DIRECT_ACCESS": "Yes", + "EVENT_LOCATION": 2.129, + "HIGHWAY_NUMBER": "16", + "REST_AREA_NAME": "BULKLEY VIEW", + "ADMIN_UNIT_CODE": "425", + "ADMIN_UNIT_NAME": "Bulkley Nass SA", + "OPEN_YEAR_ROUND": "Yes", + "REST_AREA_CLASS": "RAM Class C", + "NUMBER_OF_TABLES": 4, + "REST_AREA_NUMBER": "R0146", + "ACCELERATION_LANE": "No", + "DECELERATION_LANE": "Yes", + "NUMBER_OF_TOILETS": 1, + "ACCESS_RESTRICTION": "Westbound", + "CHRIS_REST_AREA_ID": "1532673", + "DIRECTION_OF_TRAFFIC": "Eastbound", + "POWER_RESPONSIBILITY": "Not Applicable", + "EV_STATION_25_KW_DCFC": 0, + "EV_STATION_50_KW_DCFC": 0, + "CROSS_SECTION_POSITION": "Right of Way - Right", + "ACCOM_COMMERCIAL_TRUCKS": "Yes", + "CHRIS_ANCHOR_SECTION_ID": 1313674, + "EV_STATION_LEVEL_2_J1772": 0, + "WHEELCHAIR_ACCESS_TOILET": "Yes", + "ASSOCIATED_NUMBERED_ROUTE": "16", + "DISTANCE_FROM_MUNICIPALITY": "4.5 KM EAST OF TELKWA", + "NUMBER_OF_STANDARD_BARRELS": 0, + "NUMBER_OF_BEAR_PROOF_BARRELS": 6 + }, + "bbox": [ + -126.99686259, + 54.66828166, + -126.99686259, + 54.66828166 + ] + } +] \ No newline at end of file diff --git a/src/backend/apps/rest/tests/test_data/rest_stop_feed_list_of_two.json b/src/backend/apps/rest/tests/test_data/rest_stop_feed_list_of_two.json new file mode 100644 index 000000000..829e56643 --- /dev/null +++ b/src/backend/apps/rest/tests/test_data/rest_stop_feed_list_of_two.json @@ -0,0 +1,112 @@ +[ + { + "id": 2, + "created_at": "2024-03-15T11:10:29.518958-07:00", + "modified_at": "2024-03-15T11:10:29.518958-07:00", + "rest_stop_id": "DBC_RIM_REST_AREA_V.fid-59dfb4f6_18e433c4f15_-52d9", + "geometry": { + "type": "Point", + "coordinates": [ + 54.66828166, + -126.99686259 + ] + }, + "properties": { + "WI_FI": "No", + "OBJECTID": 4381, + "OPEN_DATE": null, + "CLOSE_DATE": null, + "POWER_TYPE": "No Power", + "TOILET_TYPE": "Pit", + "DIRECT_ACCESS": "Yes", + "EVENT_LOCATION": 2.129, + "HIGHWAY_NUMBER": "16", + "REST_AREA_NAME": "BULKLEY VIEW", + "ADMIN_UNIT_CODE": "425", + "ADMIN_UNIT_NAME": "Bulkley Nass SA", + "OPEN_YEAR_ROUND": "Yes", + "REST_AREA_CLASS": "RAM Class C", + "NUMBER_OF_TABLES": 4, + "REST_AREA_NUMBER": "R0146", + "ACCELERATION_LANE": "No", + "DECELERATION_LANE": "Yes", + "NUMBER_OF_TOILETS": 1, + "ACCESS_RESTRICTION": "Westbound", + "CHRIS_REST_AREA_ID": "1532673", + "DIRECTION_OF_TRAFFIC": "Eastbound", + "POWER_RESPONSIBILITY": "Not Applicable", + "EV_STATION_25_KW_DCFC": 0, + "EV_STATION_50_KW_DCFC": 0, + "CROSS_SECTION_POSITION": "Right of Way - Right", + "ACCOM_COMMERCIAL_TRUCKS": "Yes", + "CHRIS_ANCHOR_SECTION_ID": 1313674, + "EV_STATION_LEVEL_2_J1772": 0, + "WHEELCHAIR_ACCESS_TOILET": "Yes", + "ASSOCIATED_NUMBERED_ROUTE": "16", + "DISTANCE_FROM_MUNICIPALITY": "4.5 KM EAST OF TELKWA", + "NUMBER_OF_STANDARD_BARRELS": 0, + "NUMBER_OF_BEAR_PROOF_BARRELS": 6 + }, + "bbox": [ + -126.99686259, + 54.66828166, + -126.99686259, + 54.66828166 + ] + }, + { + "id": 3, + "created_at": "2024-03-15T11:10:29.530958-07:00", + "modified_at": "2024-03-15T11:10:29.530958-07:00", + "rest_stop_id": "DBC_RIM_REST_AREA_V.fid-59dfb4f6_18e433c4f15_-52d8", + "geometry": { + "type": "Point", + "coordinates": [ + 54.84919263, + -127.22078727 + ] + }, + "properties": { + "WI_FI": "Yes", + "OBJECTID": 30, + "OPEN_DATE": null, + "CLOSE_DATE": null, + "POWER_TYPE": "No Power", + "TOILET_TYPE": "Pit", + "DIRECT_ACCESS": "Yes", + "EVENT_LOCATION": 0.986, + "HIGHWAY_NUMBER": "16", + "REST_AREA_NAME": "GLACIER VIEW", + "ADMIN_UNIT_CODE": "425", + "ADMIN_UNIT_NAME": "Bulkley Nass SA", + "OPEN_YEAR_ROUND": "Yes", + "REST_AREA_CLASS": "RAM Class C", + "NUMBER_OF_TABLES": 3, + "REST_AREA_NUMBER": "R0147", + "ACCELERATION_LANE": "No", + "DECELERATION_LANE": "No", + "NUMBER_OF_TOILETS": 1, + "ACCESS_RESTRICTION": "No Restriction", + "CHRIS_REST_AREA_ID": "1532294", + "DIRECTION_OF_TRAFFIC": "Eastbound", + "POWER_RESPONSIBILITY": "Not Applicable", + "EV_STATION_25_KW_DCFC": 0, + "EV_STATION_50_KW_DCFC": 0, + "CROSS_SECTION_POSITION": "Right of Way - Right", + "ACCOM_COMMERCIAL_TRUCKS": "Yes", + "CHRIS_ANCHOR_SECTION_ID": 1313727, + "EV_STATION_LEVEL_2_J1772": 0, + "WHEELCHAIR_ACCESS_TOILET": "Yes", + "ASSOCIATED_NUMBERED_ROUTE": "16", + "DISTANCE_FROM_MUNICIPALITY": "7.0KM NORTH OF SMITHERS", + "NUMBER_OF_STANDARD_BARRELS": 0, + "NUMBER_OF_BEAR_PROOF_BARRELS": 4 + }, + "bbox": [ + -127.22078727, + 54.84919263, + -127.22078727, + 54.84919263 + ] + } +] \ No newline at end of file diff --git a/src/backend/apps/rest/tests/test_data/rest_stop_parsed_feed.py b/src/backend/apps/rest/tests/test_data/rest_stop_parsed_feed.py new file mode 100644 index 000000000..ccf72b4f4 --- /dev/null +++ b/src/backend/apps/rest/tests/test_data/rest_stop_parsed_feed.py @@ -0,0 +1,52 @@ +json_feed = { + "rest_stop_id": "DBC_RIM_REST_AREA_V.fid-59dfb4f6_18e433c4f15_-52d9", + "geometry": { + "type": "Point", + "coordinates": [ + 54.66828166, + -126.99686259 + ] + }, + "properties": { + "WI_FI": "No", + "OBJECTID": 4381, + "OPEN_DATE": None, + "CLOSE_DATE": None, + "POWER_TYPE": "No Power", + "TOILET_TYPE": "Pit", + "DIRECT_ACCESS": "Yes", + "EVENT_LOCATION": 2.129, + "HIGHWAY_NUMBER": "16", + "REST_AREA_NAME": "BULKLEY VIEW", + "ADMIN_UNIT_CODE": "425", + "ADMIN_UNIT_NAME": "Bulkley Nass SA", + "OPEN_YEAR_ROUND": "Yes", + "REST_AREA_CLASS": "RAM Class C", + "NUMBER_OF_TABLES": 4, + "REST_AREA_NUMBER": "R0146", + "ACCELERATION_LANE": "No", + "DECELERATION_LANE": "Yes", + "NUMBER_OF_TOILETS": 1, + "ACCESS_RESTRICTION": "Westbound", + "CHRIS_REST_AREA_ID": "1532673", + "DIRECTION_OF_TRAFFIC": "Eastbound", + "POWER_RESPONSIBILITY": "Not Applicable", + "EV_STATION_25_KW_DCFC": 0, + "EV_STATION_50_KW_DCFC": 0, + "CROSS_SECTION_POSITION": "Right of Way - Right", + "ACCOM_COMMERCIAL_TRUCKS": "Yes", + "CHRIS_ANCHOR_SECTION_ID": 1313674, + "EV_STATION_LEVEL_2_J1772": 0, + "WHEELCHAIR_ACCESS_TOILET": "Yes", + "ASSOCIATED_NUMBERED_ROUTE": "16", + "DISTANCE_FROM_MUNICIPALITY": "4.5 KM EAST OF TELKWA", + "NUMBER_OF_STANDARD_BARRELS": 0, + "NUMBER_OF_BEAR_PROOF_BARRELS": 6 + }, + "bbox": [ + -126.99686259, + 54.66828166, + -126.99686259, + 54.66828166 + ] +} diff --git a/src/backend/apps/rest/tests/test_rest_stop_api.py b/src/backend/apps/rest/tests/test_rest_stop_api.py new file mode 100644 index 000000000..e6d65c53a --- /dev/null +++ b/src/backend/apps/rest/tests/test_rest_stop_api.py @@ -0,0 +1,109 @@ +from apps.shared.enums import CacheKey +from apps.shared.tests import BaseTest +from apps.rest.models import RestStop +from apps.rest.views import RestStopAPI +from django.core.cache import cache +from rest_framework.test import APITestCase + + +class TestRestStopAPI(APITestCase, BaseTest): + def setUp(self): + super().setUp() + + self.rest_stop = RestStop.objects.create( + rest_stop_id="DBC_RIM_REST_AREA_V.fid-59dfb4f6_18e42d1d997_1823", + geometry = { + "type": "Point", + "coordinates": [ + 52.98061363, + -119.31978552 + ] + }, + properties = { + "WI_FI": "Yes", + "OBJECTID": 10, + "OPEN_DATE": None, + "CLOSE_DATE": None, + "POWER_TYPE": "Electrical", + "TOILET_TYPE": "Flush", + "DIRECT_ACCESS": "Yes", + "EVENT_LOCATION": 0.064, + "HIGHWAY_NUMBER": "16", + "REST_AREA_NAME": "MT TERRY FOX 16", + "ADMIN_UNIT_CODE": "420", + "ADMIN_UNIT_NAME": "Robson SA", + "OPEN_YEAR_ROUND": "Yes", + "REST_AREA_CLASS": "RAM Class A", + "NUMBER_OF_TABLES": 10, + "REST_AREA_NUMBER": "R0128", + "ACCELERATION_LANE": "No", + "DECELERATION_LANE": "No", + "NUMBER_OF_TOILETS": 8, + "ACCESS_RESTRICTION": "No Restriction", + "CHRIS_REST_AREA_ID": "1464192", + "DIRECTION_OF_TRAFFIC": "Eastbound", + "POWER_RESPONSIBILITY": "Province", + "EV_STATION_25_KW_DCFC": 0, + "EV_STATION_50_KW_DCFC": 0, + "CROSS_SECTION_POSITION": "Right of Way - Right", + "ACCOM_COMMERCIAL_TRUCKS": "Yes", + "CHRIS_ANCHOR_SECTION_ID": 1345872, + "EV_STATION_LEVEL_2_J1772": 0, + "WHEELCHAIR_ACCESS_TOILET": "Yes", + "ASSOCIATED_NUMBERED_ROUTE": "16", + "DISTANCE_FROM_MUNICIPALITY": "6.5KM EAST OF TETE JAUNE JUNCTION", + "NUMBER_OF_STANDARD_BARRELS": 0, + "NUMBER_OF_BEAR_PROOF_BARRELS": 6 + }, + bbox = [ + -119.31978552, + 52.98061363, + -119.31978552, + 52.98061363 + ] + ) + self.rest_stop.save() + + def test_rest_stop_list_caching(self): + # Empty cache + assert cache.get(CacheKey.REST_STOP_LIST) is None + + # Cache miss + url = "/api/reststop" + response = self.client.get(url, follow=True) + assert len(response.data) == 1 + assert response.status_code == 200 + + RestStopAPI().set_list_data() + assert cache.get(CacheKey.REST_STOP_LIST) is not None + + # Cached result + RestStop.objects.filter(rest_stop_id='DBC_RIM_REST_AREA_V.fid-59dfb4f6_18e42d1d997_1823').delete() + response = self.client.get(url, follow=True) + assert len(response.data) == 1 + assert response.status_code == 200 + + # Updated cached result + RestStopAPI().set_list_data() + response = self.client.get(url, follow=True) + assert len(response.data) == 0 + assert response.status_code == 200 + + def test_rest_stop_list_filtering(self): + # No filtering + url = "/api/reststop" + # response = self.client.get(url, {}) + response = self.client.get(url, follow=True) + assert response.status_code == 200 + assert len(response.data) == 1 + + # Manually update location code + rest_stop = RestStop.objects.get(rest_stop_id='DBC_RIM_REST_AREA_V.fid-59dfb4f6_18e42d1d997_1823') + rest_stop.rest_stop_id = 'DBC_RIM_REST_AREA_V.fid-59dfb4f6_18e42d1d997_1824' + rest_stop.save() + rest_stop = RestStop.objects.get(rest_stop_id='DBC_RIM_REST_AREA_V.fid-59dfb4f6_18e42d1d997_1824') + assert response.status_code == 200 + assert len(response.data) == 1 + + response = self.client.get(url, follow=True) + assert len(response.data) == 1 diff --git a/src/backend/apps/rest/tests/test_rest_stop_populate.py b/src/backend/apps/rest/tests/test_rest_stop_populate.py new file mode 100644 index 000000000..0285f5c9d --- /dev/null +++ b/src/backend/apps/rest/tests/test_rest_stop_populate.py @@ -0,0 +1,115 @@ +import json +from pathlib import Path +from unittest import skip +from unittest.mock import patch +from apps.shared.tests import BaseTest, MockResponse +from apps.rest.models import RestStop +from apps.rest.tasks import ( + populate_all_rest_stop_data, + populate_rest_stop_from_data, +) +from apps.rest.tests.test_data.rest_stop_parsed_feed import json_feed +from unittest import mock +from django.contrib.gis.db import models + +from apps.feed.client import FeedClient + +class TestRestStopModel(BaseTest): + def setUp(self): + super().setUp() + + # Normal feed + rest_stop_feed_data = open( + str(Path(__file__).parent) + + "/test_data/rest_stop_feed_list_of_two.json" + ) + self.mock_rest_stop_feed_result = json.load(rest_stop_feed_data) + self.json_feed = json_feed + + rest_stop_feed_data_rest_stop_one = open( + str(Path(__file__).parent) + + "/test_data/rest_stop_feed_list_of_one.json" + ) + self.mock_rest_stop_feed_rest_stop_one = json.load(rest_stop_feed_data_rest_stop_one) + + def test_populate_rest_stop_function(self): + populate_rest_stop_from_data(self.json_feed) + rest_stop_one = RestStop.objects.get(rest_stop_id="DBC_RIM_REST_AREA_V.fid-59dfb4f6_18e433c4f15_-52d9") + assert rest_stop_one.location.x == -126.99686259 + assert rest_stop_one.location.y == 54.66828166 + + + def test_populate_rest_stop_function_with_existing_data(self): + RestStop.objects.create( + rest_stop_id="DBC_RIM_REST_AREA_V.fid-59dfb4f6_18e42d1d997_1823", + geometry = { + "type": "Point", + "coordinates": [ + 52.98061363, + -119.31978552 + ] + }, + properties = { + "WI_FI": "Yes", + "OBJECTID": 10, + "OPEN_DATE": None, + "CLOSE_DATE": None, + "POWER_TYPE": "Electrical", + "TOILET_TYPE": "Flush", + "DIRECT_ACCESS": "Yes", + "EVENT_LOCATION": 0.064, + "HIGHWAY_NUMBER": "16", + "REST_AREA_NAME": "MT TERRY FOX 16", + "ADMIN_UNIT_CODE": "420", + "ADMIN_UNIT_NAME": "Robson SA", + "OPEN_YEAR_ROUND": "Yes", + "REST_AREA_CLASS": "RAM Class A", + "NUMBER_OF_TABLES": 10, + "REST_AREA_NUMBER": "R0128", + "ACCELERATION_LANE": "No", + "DECELERATION_LANE": "No", + "NUMBER_OF_TOILETS": 8, + "ACCESS_RESTRICTION": "No Restriction", + "CHRIS_REST_AREA_ID": "1464192", + "DIRECTION_OF_TRAFFIC": "Eastbound", + "POWER_RESPONSIBILITY": "Province", + "EV_STATION_25_KW_DCFC": 0, + "EV_STATION_50_KW_DCFC": 0, + "CROSS_SECTION_POSITION": "Right of Way - Right", + "ACCOM_COMMERCIAL_TRUCKS": "Yes", + "CHRIS_ANCHOR_SECTION_ID": 1345872, + "EV_STATION_LEVEL_2_J1772": 0, + "WHEELCHAIR_ACCESS_TOILET": "Yes", + "ASSOCIATED_NUMBERED_ROUTE": "16", + "DISTANCE_FROM_MUNICIPALITY": "6.5KM EAST OF TETE JAUNE JUNCTION", + "NUMBER_OF_STANDARD_BARRELS": 0, + "NUMBER_OF_BEAR_PROOF_BARRELS": 6 + }, + bbox = [ + -119.31978552, + 52.98061363, + -119.31978552, + 52.98061363 + ] + + ) + populate_rest_stop_from_data(self.json_feed) + rest_stop_one = RestStop.objects.get(rest_stop_id="DBC_RIM_REST_AREA_V.fid-59dfb4f6_18e42d1d997_1823") + assert rest_stop_one.location.x == -119.31978552 + assert rest_stop_one.location.y == 52.98061363 + + @patch('apps.feed.client.FeedClient.get_rest_stop_list') + def test_populate_and_update_rest_stop(self, mock_requests_get): + mock_requests_get.side_effect = [ + MockResponse(self.mock_rest_stop_feed_result, status_code=200), + ] + response = self.mock_rest_stop_feed_result + client = FeedClient() + feed_data = client.get_rest_stop_list() + feed_data = response + + for rest_stop_data in feed_data: + populate_rest_stop_from_data(rest_stop_data) + rest_stop_list_length = len(response) + assert rest_stop_list_length == 2 + \ No newline at end of file diff --git a/src/backend/apps/rest/tests/test_rest_stop_serializer.py b/src/backend/apps/rest/tests/test_rest_stop_serializer.py new file mode 100644 index 000000000..da5eb2bdb --- /dev/null +++ b/src/backend/apps/rest/tests/test_rest_stop_serializer.py @@ -0,0 +1,138 @@ +from apps.shared.tests import BaseTest +from apps.rest.models import RestStop +from apps.rest.serializers import RestStopSerializer + + +class TestRestStopSerializer(BaseTest): + def setUp(self): + super().setUp() + + self.rest_stop = RestStop( + rest_stop_id="DBC_RIM_REST_AREA_V.fid-59dfb4f6_18e433c4f15_-52d9", + geometry = { + "type": "Point", + "coordinates": [ + 54.66828166, + -126.99686259 + ] + }, + properties = { + "WI_FI": "No", + "OBJECTID": 4381, + "OPEN_DATE": None, + "CLOSE_DATE": None, + "POWER_TYPE": "No Power", + "TOILET_TYPE": "Pit", + "DIRECT_ACCESS": "Yes", + "EVENT_LOCATION": 2.129, + "HIGHWAY_NUMBER": "16", + "REST_AREA_NAME": "BULKLEY VIEW", + "ADMIN_UNIT_CODE": "425", + "ADMIN_UNIT_NAME": "Bulkley Nass SA", + "OPEN_YEAR_ROUND": "Yes", + "REST_AREA_CLASS": "RAM Class C", + "NUMBER_OF_TABLES": 4, + "REST_AREA_NUMBER": "R0146", + "ACCELERATION_LANE": "No", + "DECELERATION_LANE": "Yes", + "NUMBER_OF_TOILETS": 1, + "ACCESS_RESTRICTION": "Westbound", + "CHRIS_REST_AREA_ID": "1532673", + "DIRECTION_OF_TRAFFIC": "Eastbound", + "POWER_RESPONSIBILITY": "Not Applicable", + "EV_STATION_25_KW_DCFC": 0, + "EV_STATION_50_KW_DCFC": 0, + "CROSS_SECTION_POSITION": "Right of Way - Right", + "ACCOM_COMMERCIAL_TRUCKS": "Yes", + "CHRIS_ANCHOR_SECTION_ID": 1313674, + "EV_STATION_LEVEL_2_J1772": 0, + "WHEELCHAIR_ACCESS_TOILET": "Yes", + "ASSOCIATED_NUMBERED_ROUTE": "16", + "DISTANCE_FROM_MUNICIPALITY": "4.5 KM EAST OF TELKWA", + "NUMBER_OF_STANDARD_BARRELS": 0, + "NUMBER_OF_BEAR_PROOF_BARRELS": 6 + }, + bbox = [ + -126.99686259, + 54.66828166, + -126.99686259, + 54.66828166 + ] + ) + + self.rest_stop_2 = RestStop( + rest_stop_id="DBC_RIM_REST_AREA_V.fid-59dfb4f6_18e433c4f15_-52d8", + geometry = { + "type": "Point", + "coordinates": [ + 54.84919263, + -127.22078727 + ] + }, + properties = { + "WI_FI": "Yes", + "OBJECTID": 30, + "OPEN_DATE": None, + "CLOSE_DATE": None, + "POWER_TYPE": "No Power", + "TOILET_TYPE": "Pit", + "DIRECT_ACCESS": "Yes", + "EVENT_LOCATION": 0.986, + "HIGHWAY_NUMBER": "16", + "REST_AREA_NAME": "GLACIER VIEW", + "ADMIN_UNIT_CODE": "425", + "ADMIN_UNIT_NAME": "Bulkley Nass SA", + "OPEN_YEAR_ROUND": "Yes", + "REST_AREA_CLASS": "RAM Class C", + "NUMBER_OF_TABLES": 3, + "REST_AREA_NUMBER": "R0147", + "ACCELERATION_LANE": "No", + "DECELERATION_LANE": "No", + "NUMBER_OF_TOILETS": 1, + "ACCESS_RESTRICTION": "No Restriction", + "CHRIS_REST_AREA_ID": "1532294", + "DIRECTION_OF_TRAFFIC": "Eastbound", + "POWER_RESPONSIBILITY": "Not Applicable", + "EV_STATION_25_KW_DCFC": 0, + "EV_STATION_50_KW_DCFC": 0, + "CROSS_SECTION_POSITION": "Right of Way - Right", + "ACCOM_COMMERCIAL_TRUCKS": "Yes", + "CHRIS_ANCHOR_SECTION_ID": 1313727, + "EV_STATION_LEVEL_2_J1772": 0, + "WHEELCHAIR_ACCESS_TOILET": "Yes", + "ASSOCIATED_NUMBERED_ROUTE": "16", + "DISTANCE_FROM_MUNICIPALITY": "7.0KM NORTH OF SMITHERS", + "NUMBER_OF_STANDARD_BARRELS": 0, + "NUMBER_OF_BEAR_PROOF_BARRELS": 4 + }, + bbox = [ + -127.22078727, + 54.84919263, + -127.22078727, + 54.84919263 + + ] + + + ) + + self.rest_stop.rest_stop_id = "DBC_RIM_REST_AREA_V.fid-59dfb4f6_18e433c4f15_-52d7" + self.rest_stop.save() + + self.rest_stop_2.rest_stop_id = "DBC_RIM_REST_AREA_V.fid-59dfb4f6_18e433c4f15_-52d6" + self.rest_stop_2.save() + + self.serializer = RestStopSerializer(self.rest_stop) + self.serializer_two = RestStopSerializer(self.rest_stop_2) + + def test_serializer_data(self): + assert len(self.serializer.data) == 7 + assert self.serializer.data['rest_stop_id'] == \ + "DBC_RIM_REST_AREA_V.fid-59dfb4f6_18e433c4f15_-52d7" + assert self.serializer.data['location']['coordinates'][0] == -126.99686259 + assert self.serializer.data['location']['coordinates'][1] == 54.66828166 + + assert self.serializer_two.data['rest_stop_id'] == \ + "DBC_RIM_REST_AREA_V.fid-59dfb4f6_18e433c4f15_-52d6" + assert self.serializer_two.data['location']['coordinates'][0] == -127.22078727 + assert self.serializer_two.data['location']['coordinates'][1] == 54.84919263 diff --git a/src/backend/apps/shared/tests.py b/src/backend/apps/shared/tests.py index b3a4d6cf4..30efb07f7 100644 --- a/src/backend/apps/shared/tests.py +++ b/src/backend/apps/shared/tests.py @@ -11,7 +11,8 @@ from rest_framework.test import APIRequestFactory -from src.backend.apps.shared.views import FeedbackView +# bruce test +# from src.backend.apps.shared.views import FeedbackView logger = logging.getLogger(__name__)