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

[#337] Feature/API for categories and products #140

Merged
merged 1 commit into from
Mar 18, 2022
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
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