-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Add get all campaigns endpoint - Add get campaign by resource id
- Loading branch information
Showing
8 changed files
with
310 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
from rest_framework import serializers | ||
|
||
from safe_locking_service.campaigns.models import Campaign | ||
|
||
|
||
class ActivityMetadataSerializer(serializers.Serializer): | ||
name = serializers.CharField() | ||
description = serializers.CharField() | ||
max_points = serializers.IntegerField() | ||
|
||
|
||
class CampaignSerializer(serializers.Serializer): | ||
resource_id = serializers.SerializerMethodField() | ||
name = serializers.CharField() | ||
description = serializers.CharField() | ||
start_date = serializers.DateTimeField() | ||
end_date = serializers.DateTimeField() | ||
last_updated = serializers.SerializerMethodField() | ||
activities_metadata = ActivityMetadataSerializer( | ||
many=True, source="activity_metadata" | ||
) | ||
|
||
def get_resource_id(self, obj: Campaign): | ||
return obj.uuid | ||
|
||
def get_last_updated(self, obj: Campaign): | ||
return obj.last_updated |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,35 @@ | ||
import factory | ||
from django.utils import timezone | ||
|
||
from factory import Faker, LazyFunction, SubFactory | ||
from factory.django import DjangoModelFactory | ||
|
||
from ..models import Campaign | ||
from ..models import ActivityMetadata, Campaign, Period | ||
|
||
|
||
class CampaignFactory(DjangoModelFactory): | ||
class Meta: | ||
model = Campaign | ||
|
||
name = factory.Faker("catch_phrase") | ||
description = factory.Faker("bs") | ||
start_date = factory.Faker("date_time") | ||
end_date = factory.Faker("date_time") | ||
name = Faker("catch_phrase") | ||
description = Faker("bs") | ||
start_date = LazyFunction(timezone.now) | ||
end_date = LazyFunction(timezone.now) | ||
|
||
|
||
class PeriodFactory(DjangoModelFactory): | ||
class Meta: | ||
model = Period | ||
|
||
campaign = SubFactory(CampaignFactory) | ||
start_date = LazyFunction(lambda: timezone.now().date()) | ||
end_date = LazyFunction(lambda: timezone.now().date()) | ||
|
||
|
||
class ActivityMetadataFactory(DjangoModelFactory): | ||
class Meta: | ||
model = ActivityMetadata | ||
|
||
campaign = SubFactory(CampaignFactory) | ||
name = Faker("catch_phrase") | ||
description = Faker("bs") | ||
max_points = Faker("pyint") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
import uuid | ||
from datetime import timedelta | ||
|
||
from django.test import TestCase | ||
from django.urls import reverse | ||
from django.utils import timezone | ||
|
||
from rest_framework import status | ||
|
||
from ...campaigns.tests.factories import ( | ||
ActivityMetadataFactory, | ||
CampaignFactory, | ||
PeriodFactory, | ||
) | ||
from ...utils.timestamp_helper import get_formated_timestamp | ||
|
||
|
||
class TestCampaignViews(TestCase): | ||
def test_empty_campaigns_view(self): | ||
url = reverse("v1:locking_campaigns:list-campaigns") | ||
response = self.client.get(url, format="json") | ||
self.assertEqual(response.status_code, status.HTTP_200_OK) | ||
# No campaigns | ||
response_json = response.json() | ||
self.assertEqual(response_json["count"], 0) | ||
|
||
def test_campaign_view(self): | ||
url = reverse("v1:locking_campaigns:list-campaigns") | ||
# Add campaign, one activity and 2 periods | ||
campaign_expected = CampaignFactory() | ||
activity = ActivityMetadataFactory(campaign=campaign_expected) | ||
previous_day = timezone.now().date() - timedelta(days=1) | ||
PeriodFactory( | ||
campaign=campaign_expected, start_date=previous_day, end_date=previous_day | ||
) | ||
period_last = PeriodFactory(campaign=campaign_expected) | ||
response = self.client.get(url, format="json") | ||
response_json = response.json() | ||
self.assertEqual(len(response_json["results"]), 1) | ||
campaign_response = response_json["results"][0] | ||
self.assertEqual( | ||
campaign_response.get("resourceId"), str(campaign_expected.uuid) | ||
) | ||
self.assertEqual(campaign_response.get("name"), campaign_expected.name) | ||
self.assertEqual( | ||
campaign_response.get("description"), campaign_expected.description | ||
) | ||
self.assertEqual( | ||
campaign_response.get("startDate"), | ||
get_formated_timestamp(campaign_expected.start_date), | ||
) | ||
self.assertEqual( | ||
campaign_response.get("endDate"), | ||
get_formated_timestamp(campaign_expected.end_date), | ||
) | ||
|
||
# LastUpdated should be the end_date of the last period | ||
self.assertEqual( | ||
campaign_response.get("lastUpdated"), | ||
get_formated_timestamp(period_last.end_date), | ||
) | ||
self.assertEqual(len(campaign_response.get("activitiesMetadata")), 1) | ||
self.assertEqual( | ||
campaign_response.get("activitiesMetadata")[0]["name"], activity.name | ||
) | ||
|
||
def test_no_activities_periods_campaigns_view(self): | ||
# Add a campaign without activities and without period | ||
url = reverse("v1:locking_campaigns:list-campaigns") | ||
campaign_expected = CampaignFactory() | ||
response = self.client.get(url, format="json") | ||
response_json = response.json() | ||
self.assertEqual(len(response_json["results"]), 1) | ||
campaign_response = response_json["results"][0] | ||
self.assertEqual( | ||
campaign_response.get("resourceId"), str(campaign_expected.uuid) | ||
) | ||
self.assertEqual(campaign_response.get("name"), campaign_expected.name) | ||
self.assertEqual( | ||
campaign_response.get("description"), campaign_expected.description | ||
) | ||
self.assertEqual( | ||
campaign_response.get("startDate"), | ||
get_formated_timestamp(campaign_expected.start_date), | ||
) | ||
self.assertEqual( | ||
campaign_response.get("endDate"), | ||
get_formated_timestamp(campaign_expected.end_date), | ||
) | ||
self.assertIsNone(campaign_response.get("lastUpdated")) | ||
self.assertIsInstance(campaign_response.get("activitiesMetadata"), list) | ||
self.assertEqual(len(campaign_response.get("activitiesMetadata")), 0) | ||
|
||
def test_sort_campaign_view(self): | ||
url = reverse("v1:locking_campaigns:list-campaigns") | ||
previous_day = timezone.now().date() - timedelta(days=1) | ||
CampaignFactory(start_date=previous_day, end_date=previous_day) | ||
# Last campaign should be at the beginning | ||
last_campaign = CampaignFactory() | ||
response = self.client.get(url, format="json") | ||
response_json = response.json() | ||
self.assertEqual(len(response_json["results"]), 2) | ||
campaign_response = response_json["results"][0] | ||
self.assertEqual(campaign_response.get("resourceId"), str(last_campaign.uuid)) | ||
self.assertEqual(campaign_response.get("name"), last_campaign.name) | ||
self.assertEqual( | ||
campaign_response.get("description"), last_campaign.description | ||
) | ||
self.assertEqual( | ||
campaign_response.get("startDate"), | ||
get_formated_timestamp(last_campaign.start_date), | ||
) | ||
self.assertEqual( | ||
campaign_response.get("endDate"), | ||
get_formated_timestamp(last_campaign.end_date), | ||
) | ||
self.assertIsNone(campaign_response.get("lastUpdated")) | ||
self.assertIsInstance(campaign_response.get("activitiesMetadata"), list) | ||
self.assertEqual(len(campaign_response.get("activitiesMetadata")), 0) | ||
|
||
def test_retrieve_campaign_view(self): | ||
resource_id = uuid.uuid4() | ||
response = self.client.get( | ||
reverse("v1:locking_campaigns:retrieve-campaign", args=(resource_id,)), | ||
format="json", | ||
) | ||
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) | ||
|
||
campaign_expected = CampaignFactory() | ||
activity_expected = ActivityMetadataFactory(campaign=campaign_expected) | ||
period_expected = PeriodFactory(campaign=campaign_expected) | ||
resource_id = campaign_expected.uuid | ||
|
||
response = self.client.get( | ||
reverse("v1:locking_campaigns:retrieve-campaign", args=(resource_id,)), | ||
format="json", | ||
) | ||
self.assertEqual(response.status_code, status.HTTP_200_OK) | ||
campaign_response = response.json() | ||
self.assertEqual( | ||
campaign_response.get("resourceId"), str(campaign_expected.uuid) | ||
) | ||
self.assertEqual(campaign_response.get("name"), campaign_expected.name) | ||
self.assertEqual( | ||
campaign_response.get("description"), campaign_expected.description | ||
) | ||
self.assertEqual( | ||
campaign_response.get("startDate"), | ||
get_formated_timestamp(campaign_expected.start_date), | ||
) | ||
self.assertEqual( | ||
campaign_response.get("endDate"), | ||
get_formated_timestamp(campaign_expected.end_date), | ||
) | ||
self.assertEqual( | ||
campaign_response.get("lastUpdated"), | ||
get_formated_timestamp(period_expected.end_date), | ||
) | ||
self.assertEqual(len(campaign_response.get("activitiesMetadata")), 1) | ||
self.assertEqual( | ||
campaign_response.get("activitiesMetadata")[0]["name"], | ||
activity_expected.name, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
from django.urls import path | ||
|
||
from . import views | ||
|
||
app_name = "campaigns" | ||
|
||
urlpatterns = [ | ||
path("campaigns/", views.CampaignsView.as_view(), name="list-campaigns"), | ||
path( | ||
"campaigns/<str:resource_id>/", | ||
views.RetrieveCampaignView.as_view(), | ||
name="retrieve-campaign", | ||
), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
from django.db.models import Max | ||
from django.utils.decorators import method_decorator | ||
from django.views.decorators.cache import cache_page | ||
|
||
from rest_framework import status | ||
from rest_framework.generics import ListAPIView, RetrieveAPIView | ||
from rest_framework.response import Response | ||
|
||
from safe_locking_service.campaigns.serializers import CampaignSerializer | ||
from safe_locking_service.locking_events.pagination import SmallPagination | ||
|
||
from .models import Campaign | ||
|
||
|
||
class CampaignsView(ListAPIView): | ||
""" | ||
Returns a paginated list of campaigns. | ||
""" | ||
|
||
pagination_class = SmallPagination | ||
serializer_class = CampaignSerializer | ||
|
||
def get_queryset(self): | ||
return ( | ||
Campaign.objects.prefetch_related("activity_metadata") | ||
.annotate(last_updated=Max("periods__end_date")) | ||
.order_by("-start_date") | ||
) | ||
|
||
@method_decorator(cache_page(1 * 60)) # 1 minute | ||
def list(self, request, *args, **kwargs): | ||
queryset = self.get_queryset() | ||
serializer = self.serializer_class(queryset, many=True) | ||
paginated_data = self.paginate_queryset(serializer.data) | ||
return self.get_paginated_response(paginated_data) | ||
|
||
|
||
class RetrieveCampaignView(RetrieveAPIView): | ||
""" | ||
Returns a campaign for the provided campaign_id. | ||
""" | ||
|
||
serializer_class = CampaignSerializer | ||
|
||
def get_queryset(self, resource_id: int): | ||
return ( | ||
Campaign.objects.filter(uuid=resource_id) | ||
.prefetch_related("activity_metadata") | ||
.annotate(last_updated=Max("periods__end_date")) | ||
) | ||
|
||
@method_decorator(cache_page(1 * 60)) # 1 minute | ||
def get(self, request, *args, **kwargs): | ||
resource_id = kwargs["resource_id"] | ||
queryset = self.get_queryset(resource_id) | ||
if not queryset: | ||
return Response( | ||
status=status.HTTP_404_NOT_FOUND, | ||
) | ||
|
||
serializer = self.serializer_class(queryset[0]) | ||
return Response(status=status.HTTP_200_OK, data=serializer.data) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import datetime | ||
|
||
|
||
def get_formated_timestamp(timestamp: datetime): | ||
""" | ||
:param timestamp: | ||
:return: return formatted timestamp YYYY-MM-DDTHH:MM:SSZ | ||
""" | ||
return timestamp.isoformat().replace("+00:00", "Z") |