Skip to content

Commit

Permalink
Merge pull request #140 from maykinmedia/task-337-reinstantiate-pdc-api
Browse files Browse the repository at this point in the history
[#337] Feature/API for categories and products
  • Loading branch information
alextreme authored Mar 18, 2022
2 parents 2773134 + 97b11e7 commit cc6a2f2
Show file tree
Hide file tree
Showing 7 changed files with 386 additions and 0 deletions.
Empty file.
198 changes: 198 additions & 0 deletions src/open_inwoner/api/pdc/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
from typing import List

from drf_spectacular.utils import extend_schema_field
from filer.models import File, Image
from rest_framework import serializers

from open_inwoner.pdc.models import Category, Product, ProductLink, Question, Tag
from open_inwoner.pdc.models.organization import Organization
from open_inwoner.pdc.models.product import (
ProductCondition,
ProductContact,
ProductFile,
ProductLocation,
)


class FilerImageSerializer(serializers.ModelSerializer):
class Meta:
model = Image
fields = (
"name",
"description",
"file",
"subject_location",
)


class FilerFileSerializer(serializers.ModelSerializer):
class Meta:
model = File
fields = (
"name",
"description",
"file",
)


class ProductFileSerializer(serializers.ModelSerializer):
file = FilerFileSerializer(required=False)

class Meta:
model = ProductFile
fields = ("file",)


class TagSerializer(serializers.ModelSerializer):
icon = FilerImageSerializer(required=False)
type = serializers.StringRelatedField()

class Meta:
model = Tag
fields = ("name", "slug", "icon", "type")


class SmallProductSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Product
fields = ("url", "name", "slug", "summary")
extra_kwargs = {
"url": {"view_name": "api:products-detail", "lookup_field": "slug"},
}


class Questionserializer(serializers.ModelSerializer):
class Meta:
model = Question
fields = ("question", "answer")


class SmallCategorySerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Category
fields = ("url", "name", "slug", "description")
extra_kwargs = {
"url": {"view_name": "api:categories-detail", "lookup_field": "slug"},
}


class CategoryWithChildSerializer(serializers.ModelSerializer):
icon = FilerImageSerializer(required=False)
image = FilerImageSerializer(required=False)
products = SmallProductSerializer(required=False, many=True)
questions = Questionserializer(required=False, many=True, source="question_set")
children = serializers.SerializerMethodField()

class Meta:
model = Category
fields = (
"name",
"slug",
"description",
"icon",
"image",
"products",
"questions",
"children",
)

@extend_schema_field(SmallCategorySerializer(many=True))
def get_children(self, obj):
return SmallCategorySerializer(
obj.get_children(), many=True, context=self._context
).data


class OrganizationSerializer(serializers.ModelSerializer):
logo = FilerImageSerializer(required=False)
type = serializers.StringRelatedField()
neighbourhood = serializers.StringRelatedField()

class Meta:
model = Organization
fields = (
"name",
"slug",
"logo",
"type",
"email",
"phonenumber",
"neighbourhood",
)


class ProductContactSerializer(serializers.ModelSerializer):
organization = serializers.StringRelatedField()

class Meta:
model = ProductContact
fields = (
"organization",
"first_name",
"last_name",
"email",
"phonenumber",
"role",
)


class ProductLocationSerializer(serializers.ModelSerializer):
coordinates = serializers.SerializerMethodField()

class Meta:
model = ProductLocation
fields = ("name", "street", "housenumber", "postcode", "city", "coordinates")

@extend_schema_field(List[str])
def get_coordinates(self, obj):
return obj.geometry.coords


class ProductConditionSerializer(serializers.ModelSerializer):
class Meta:
model = ProductCondition
fields = ("name", "question", "positive_text", "negative_text", "rule")


class ProductLinkSerializer(serializers.ModelSerializer):
class Meta:
model = ProductLink
fields = (
"name",
"url",
)


class ProductSerializer(serializers.ModelSerializer):
links = ProductLinkSerializer(many=True, required=False)
categories = SmallCategorySerializer(many=True, required=False)
related_products = SmallProductSerializer(many=True, required=False)
tags = TagSerializer(many=True, required=False)
organizations = OrganizationSerializer(many=True, required=False)
contacts = ProductContactSerializer(many=True, required=False)
locations = ProductLocationSerializer(many=True, required=False)
conditions = ProductConditionSerializer(many=True, required=False)
files = ProductFileSerializer(many=True, required=False)

class Meta:
model = Product
fields = (
"name",
"slug",
"summary",
"link",
"content",
"categories",
"related_products",
"tags",
"costs",
"created_on",
"organizations",
"links",
"keywords",
"uniforme_productnaam",
"contacts",
"locations",
"conditions",
"files",
)
61 changes: 61 additions & 0 deletions src/open_inwoner/api/pdc/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
from rest_framework import viewsets
from rest_framework.generics import get_object_or_404

from open_inwoner.pdc.models import Category, Product

from .serializers import CategoryWithChildSerializer, ProductSerializer


class CategoryViewSet(viewsets.ReadOnlyModelViewSet):
authentication_classes = []
permission_classes = []
serializer_class = CategoryWithChildSerializer
lookup_field = "slug"

def get_object(self):
"""
Returns the object the view is displaying.
You may want to override this if you need to provide non-standard
queryset lookups. Eg if objects are referenced using multiple
keyword arguments in the url conf.
"""
queryset = self.filter_queryset(Category.objects.all())

# Perform the lookup filtering.
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field

assert lookup_url_kwarg in self.kwargs, (
"Expected view %s to be called with a URL keyword argument "
'named "%s". Fix your URL conf, or set the `.lookup_field` '
"attribute on the view correctly."
% (self.__class__.__name__, lookup_url_kwarg)
)

filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]}
obj = get_object_or_404(queryset, **filter_kwargs)

# May raise a permission denied
self.check_object_permissions(self.request, obj)

return obj

def get_queryset(self):
return Category.get_root_nodes()


class ProductViewSet(viewsets.ReadOnlyModelViewSet):
authentication_classes = []
permission_classes = []
serializer_class = ProductSerializer
queryset = Product.objects.prefetch_related(
"links",
"categories",
"related_products",
"tags",
"organizations",
"contacts",
"locations",
"conditions",
)
lookup_field = "slug"
Empty file.
21 changes: 21 additions & 0 deletions src/open_inwoner/api/tests/test_serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from django.urls import reverse

from rest_framework import status
from rest_framework.test import APIClient, APITestCase

from open_inwoner.pdc.tests.factories import ProductFactory, ProductLocationFactory


class TestPDCLocation(APITestCase):
def setUp(self):
self.client = APIClient()

def test_products_endpoint_returns_location_coordinates(self):
location = ProductLocationFactory()
ProductFactory(locations=(location,))

response = self.client.get(reverse("api:products-list"), format="json")

coordinates = response.json()[0]["locations"][0]["coordinates"]
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(coordinates, [5.0, 52.0])
103 changes: 103 additions & 0 deletions src/open_inwoner/api/tests/test_views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
from unicodedata import category

from django.urls import reverse

from rest_framework import status
from rest_framework.test import APIClient, APITestCase

from open_inwoner.pdc.models import Category
from open_inwoner.pdc.tests.factories import CategoryFactory


class TestPDCLocation(APITestCase):
def setUp(self):
self.client = APIClient()

self.root_category_1 = CategoryFactory.build()
self.child_category_1 = CategoryFactory.build()
self.grandchild_category = CategoryFactory.build()
Category.add_root(instance=self.root_category_1)
self.root_category_1.add_child(instance=self.child_category_1)
self.child_category_1.add_child(instance=self.grandchild_category)

self.root_category_2 = CategoryFactory.build()
self.child_category_2 = CategoryFactory.build()
Category.add_root(instance=self.root_category_2)
self.root_category_2.add_child(instance=self.child_category_2)

def test_list_categories_endpoint_returns_both_parent_and_children_categories(self):
response = self.client.get(reverse("api:categories-list"), format="json")

self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEquals(
sorted(response.json(), key=lambda k: k["slug"]),
sorted(
[
{
"name": self.root_category_1.name,
"slug": self.root_category_1.slug,
"description": self.root_category_1.description,
"icon": None,
"image": None,
"products": [],
"questions": [],
"children": [
{
"url": f"http://testserver/api/categories/{self.child_category_1.slug}/",
"name": self.child_category_1.name,
"slug": self.child_category_1.slug,
"description": self.child_category_1.description,
}
],
},
{
"name": self.root_category_2.name,
"slug": self.root_category_2.slug,
"description": self.root_category_2.description,
"icon": None,
"image": None,
"products": [],
"questions": [],
"children": [
{
"url": f"http://testserver/api/categories/{self.child_category_2.slug}/",
"name": self.child_category_2.name,
"slug": self.child_category_2.slug,
"description": self.child_category_2.description,
}
],
},
],
key=lambda k: k["slug"],
),
)

def test_category_detail_endpoint_returns_both_parent_and_children_categories(self):
response = self.client.get(
reverse(
"api:categories-detail", kwargs={"slug": self.child_category_1.slug}
),
format="json",
)

self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEquals(
response.json(),
{
"name": self.child_category_1.name,
"slug": self.child_category_1.slug,
"description": self.child_category_1.description,
"icon": None,
"image": None,
"products": [],
"questions": [],
"children": [
{
"url": f"http://testserver/api/categories/{self.grandchild_category.slug}/",
"name": self.grandchild_category.name,
"slug": self.grandchild_category.slug,
"description": self.grandchild_category.description,
}
],
},
)
Loading

0 comments on commit cc6a2f2

Please sign in to comment.