diff --git a/src/open_producten/locations/router.py b/src/open_producten/locations/router.py new file mode 100644 index 0000000..a059c9b --- /dev/null +++ b/src/open_producten/locations/router.py @@ -0,0 +1,27 @@ +from django.urls import include, path + +from rest_framework.routers import DefaultRouter + +from open_producten.locations.views import ( + ContactViewSet, + LocationViewSet, + NeighbourhoodViewSet, + OrganisationTypeViewSet, + OrganisationViewSet, +) + +LocationRouter = DefaultRouter() + +LocationRouter.register("locations", LocationViewSet, basename="locations") +LocationRouter.register("organisations", OrganisationViewSet, basename="organisations") +LocationRouter.register("contacts", ContactViewSet, basename="contacts") +LocationRouter.register( + "neighbourhoods", NeighbourhoodViewSet, basename="neighbourhoods" +) +LocationRouter.register( + "organisationtypes", OrganisationTypeViewSet, basename="organisationtypes" +) + +location_urlpatterns = [ + path("", include(LocationRouter.urls)), +] diff --git a/src/open_producten/locations/serializers/__init__.py b/src/open_producten/locations/serializers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/open_producten/locations/serializers/location.py b/src/open_producten/locations/serializers/location.py new file mode 100644 index 0000000..8c72641 --- /dev/null +++ b/src/open_producten/locations/serializers/location.py @@ -0,0 +1,55 @@ +from rest_framework import serializers + +from open_producten.locations.models import ( + Contact, + Location, + Neighbourhood, + Organisation, + OrganisationType, +) + + +class LocationSerializer(serializers.ModelSerializer): + class Meta: + model = Location + fields = "__all__" + + +class NeighbourhoodSerializer(serializers.ModelSerializer): + class Meta: + model = Neighbourhood + fields = "__all__" + + +class OrganisationTypeSerializer(serializers.ModelSerializer): + class Meta: + model = OrganisationType + fields = "__all__" + + +class OrganisationSerializer(serializers.ModelSerializer): + neighbourhood = NeighbourhoodSerializer(read_only=True) + type = OrganisationTypeSerializer(read_only=True) + + neighbourhood_id = serializers.PrimaryKeyRelatedField( + write_only=True, queryset=Neighbourhood.objects.all(), source="neighbourhood" + ) + + type_id = serializers.PrimaryKeyRelatedField( + write_only=True, queryset=OrganisationType.objects.all(), source="type" + ) + + class Meta: + model = Organisation + fields = "__all__" + + +class ContactSerializer(serializers.ModelSerializer): + organisation = OrganisationSerializer(read_only=True) + organisation_id = serializers.PrimaryKeyRelatedField( + write_only=True, queryset=Organisation.objects.all(), source="organisation" + ) + + class Meta: + model = Contact + fields = "__all__" diff --git a/src/open_producten/locations/tests/api/__init__.py b/src/open_producten/locations/tests/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/open_producten/locations/tests/api/contact.py b/src/open_producten/locations/tests/api/contact.py new file mode 100644 index 0000000..930aa3e --- /dev/null +++ b/src/open_producten/locations/tests/api/contact.py @@ -0,0 +1,79 @@ +from rest_framework.test import APIClient + +from open_producten.locations.models import Contact +from open_producten.utils.tests.cases import BaseApiTestCase +from open_producten.utils.tests.helpers import model_to_dict_with_id + +from ..factories import ContactFactory, OrganisationFactory + + +def contact_to_dict(contact): + contact_dict = model_to_dict_with_id(contact) + contact_dict["organisation"] = model_to_dict_with_id(contact.organisation) + contact_dict["organisation"]["type"] = model_to_dict_with_id( + contact.organisation.type + ) + contact_dict["organisation"]["neighbourhood"] = model_to_dict_with_id( + contact.organisation.neighbourhood + ) + return contact_dict + + +class TestContact(BaseApiTestCase): + + def setUp(self): + super().setUp() + organisation = OrganisationFactory.create() + self.data = { + "first_name": "bob", + "last_name": "de vries", + "organisation_id": organisation.id, + } + self.path = "/api/v1/contacts/" + + self.contact = ContactFactory.create() + + def test_read_contact_without_credentials_returns_error(self): + response = APIClient().get(self.path) + self.assertEqual(response.status_code, 401) + + def test_create_contact(self): + response = self.post(self.data) + + self.assertEqual(response.status_code, 201) + self.assertEqual(Contact.objects.count(), 2) + + def test_update_contact(self): + data = self.data | {"first_name": "updated"} + response = self.put(self.contact.id, data) + + self.assertEqual(response.status_code, 200) + self.assertEqual(Contact.objects.count(), 1) + self.assertEqual(Contact.objects.first().first_name, "updated") + + def test_partial_update_contact(self): + data = {"first_name": "updated"} + response = self.patch(self.contact.id, data) + + self.assertEqual(response.status_code, 200) + self.assertEqual(Contact.objects.count(), 1) + self.assertEqual(Contact.objects.first().first_name, "updated") + + def test_read_contacts(self): + response = self.get() + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.data["count"], 1) + self.assertEqual(response.data["results"], [contact_to_dict(self.contact)]) + + def test_read_contact(self): + response = self.get(self.contact.id) + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.data, contact_to_dict(self.contact)) + + def test_delete_contact(self): + response = self.delete(self.contact.id) + + self.assertEqual(response.status_code, 204) + self.assertEqual(Contact.objects.count(), 0) diff --git a/src/open_producten/locations/tests/api/location.py b/src/open_producten/locations/tests/api/location.py new file mode 100644 index 0000000..4c42c50 --- /dev/null +++ b/src/open_producten/locations/tests/api/location.py @@ -0,0 +1,66 @@ +from rest_framework.test import APIClient + +from open_producten.locations.models import Location +from open_producten.utils.tests.cases import BaseApiTestCase +from open_producten.utils.tests.helpers import model_to_dict_with_id + +from ..factories import LocationFactory + + +def location_to_dict(location): + return model_to_dict_with_id(location) + + +class TestLocation(BaseApiTestCase): + + def setUp(self): + super().setUp() + self.data = {"name": "locatie", "postcode": "1111 AA", "city": "Amsterdam"} + self.path = "/api/v1/locations/" + + self.location = LocationFactory.create() + + def test_read_location_without_credentials_returns_error(self): + response = APIClient().get(self.path) + self.assertEqual(response.status_code, 401) + + def test_create_location(self): + response = self.post(self.data) + + self.assertEqual(response.status_code, 201) + self.assertEqual(Location.objects.count(), 2) + + def test_update_location(self): + data = self.data | {"name": "updated"} + response = self.put(self.location.id, data) + + self.assertEqual(response.status_code, 200) + self.assertEqual(Location.objects.count(), 1) + self.assertEqual(Location.objects.first().name, "updated") + + def test_partial_update_location(self): + data = {"name": "updated"} + response = self.patch(self.location.id, data) + + self.assertEqual(response.status_code, 200) + self.assertEqual(Location.objects.count(), 1) + self.assertEqual(Location.objects.first().name, "updated") + + def test_read_locations(self): + response = self.get() + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.data["count"], 1) + self.assertEqual(response.data["results"], [location_to_dict(self.location)]) + + def test_read_location(self): + response = self.get(self.location.id) + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.data, location_to_dict(self.location)) + + def test_delete_location(self): + response = self.delete(self.location.id) + + self.assertEqual(response.status_code, 204) + self.assertEqual(Location.objects.count(), 0) diff --git a/src/open_producten/locations/tests/api/neighbourhood.py b/src/open_producten/locations/tests/api/neighbourhood.py new file mode 100644 index 0000000..6d089d0 --- /dev/null +++ b/src/open_producten/locations/tests/api/neighbourhood.py @@ -0,0 +1,68 @@ +from rest_framework.test import APIClient + +from open_producten.locations.models import Neighbourhood +from open_producten.utils.tests.cases import BaseApiTestCase +from open_producten.utils.tests.helpers import model_to_dict_with_id + +from ..factories import NeighbourhoodFactory + + +def neighbourhood_to_dict(neighbourhood): + return model_to_dict_with_id(neighbourhood) + + +class TestNeighbourhood(BaseApiTestCase): + + def setUp(self): + super().setUp() + self.data = {"name": "buurt"} + self.path = "/api/v1/neighbourhoods/" + + self.neighbourhood = NeighbourhoodFactory.create() + + def test_read_neighbourhood_without_credentials_returns_error(self): + response = APIClient().get(self.path) + self.assertEqual(response.status_code, 401) + + def test_create_neighbourhood(self): + response = self.post(self.data) + + self.assertEqual(response.status_code, 201) + self.assertEqual(Neighbourhood.objects.count(), 2) + + def test_update_neighbourhood(self): + data = self.data | {"name": "updated"} + response = self.put(self.neighbourhood.id, data) + + self.assertEqual(response.status_code, 200) + self.assertEqual(Neighbourhood.objects.count(), 1) + self.assertEqual(Neighbourhood.objects.first().name, "updated") + + def test_partial_update_neighbourhood(self): + data = {"name": "updated"} + response = self.patch(self.neighbourhood.id, data) + + self.assertEqual(response.status_code, 200) + self.assertEqual(Neighbourhood.objects.count(), 1) + self.assertEqual(Neighbourhood.objects.first().name, "updated") + + def test_read_neighbourhoods(self): + response = self.get() + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.data["count"], 1) + self.assertEqual( + response.data["results"], [neighbourhood_to_dict(self.neighbourhood)] + ) + + def test_read_neighbourhood(self): + response = self.get(self.neighbourhood.id) + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.data, neighbourhood_to_dict(self.neighbourhood)) + + def test_delete_neighbourhood(self): + response = self.delete(self.neighbourhood.id) + + self.assertEqual(response.status_code, 204) + self.assertEqual(Neighbourhood.objects.count(), 0) diff --git a/src/open_producten/locations/tests/api/organisation.py b/src/open_producten/locations/tests/api/organisation.py new file mode 100644 index 0000000..c1edeb9 --- /dev/null +++ b/src/open_producten/locations/tests/api/organisation.py @@ -0,0 +1,85 @@ +from rest_framework.test import APIClient + +from open_producten.locations.models import Organisation +from open_producten.utils.tests.cases import BaseApiTestCase +from open_producten.utils.tests.helpers import model_to_dict_with_id + +from ..factories import ( + NeighbourhoodFactory, + OrganisationFactory, + OrganisationTypeFactory, +) + + +def organisation_to_dict(organisation): + organisation_dict = model_to_dict_with_id(organisation) + organisation_dict["type"] = model_to_dict_with_id(organisation.type) + organisation_dict["neighbourhood"] = model_to_dict_with_id( + organisation.neighbourhood + ) + return organisation_dict + + +class TestOrganisation(BaseApiTestCase): + + def setUp(self): + super().setUp() + organisation_type = OrganisationTypeFactory.create() + neighbourhood = NeighbourhoodFactory.create() + self.data = { + "name": "locatie", + "postcode": "1111 AA", + "city": "Amsterdam", + "type_id": organisation_type.id, + "neighbourhood_id": neighbourhood.id, + } + self.path = "/api/v1/organisations/" + + self.organisation = OrganisationFactory.create() + + def test_read_organisation_without_credentials_returns_error(self): + response = APIClient().get(self.path) + self.assertEqual(response.status_code, 401) + + def test_create_organisation(self): + response = self.post(self.data) + + self.assertEqual(response.status_code, 201) + self.assertEqual(Organisation.objects.count(), 2) + + def test_update_organisation(self): + data = self.data | {"name": "updated"} + response = self.put(self.organisation.id, data) + + self.assertEqual(response.status_code, 200) + self.assertEqual(Organisation.objects.count(), 1) + self.assertEqual(Organisation.objects.first().name, "updated") + + def test_partial_update_organisation(self): + data = {"name": "updated"} + response = self.patch(self.organisation.id, data) + + self.assertEqual(response.status_code, 200) + self.assertEqual(Organisation.objects.count(), 1) + self.assertEqual(Organisation.objects.first().name, "updated") + + def test_read_organisations(self): + response = self.get() + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.data["count"], 1) + self.assertEqual( + response.data["results"], [organisation_to_dict(self.organisation)] + ) + + def test_read_organisation(self): + response = self.get(self.organisation.id) + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.data, organisation_to_dict(self.organisation)) + + def test_delete_organisation(self): + response = self.delete(self.organisation.id) + + self.assertEqual(response.status_code, 204) + self.assertEqual(Organisation.objects.count(), 0) diff --git a/src/open_producten/locations/tests/api/organisation_type.py b/src/open_producten/locations/tests/api/organisation_type.py new file mode 100644 index 0000000..b95e084 --- /dev/null +++ b/src/open_producten/locations/tests/api/organisation_type.py @@ -0,0 +1,71 @@ +from rest_framework.test import APIClient + +from open_producten.locations.models import OrganisationType +from open_producten.utils.tests.cases import BaseApiTestCase +from open_producten.utils.tests.helpers import model_to_dict_with_id + +from ..factories import OrganisationTypeFactory + + +def organisation_type_to_dict(organisation_type): + return model_to_dict_with_id(organisation_type) + + +class TestOrganisationType(BaseApiTestCase): + + def setUp(self): + super().setUp() + self.data = {"name": "type"} + self.path = "/api/v1/organisationtypes/" + + self.organisation_type = OrganisationTypeFactory.create() + + def test_read_organisation_type_without_credentials_returns_error(self): + response = APIClient().get(self.path) + self.assertEqual(response.status_code, 401) + + def test_create_organisation_type(self): + response = self.post(self.data) + + self.assertEqual(response.status_code, 201) + self.assertEqual(OrganisationType.objects.count(), 2) + + def test_update_organisation_type(self): + data = self.data | {"name": "updated"} + response = self.put(self.organisation_type.id, data) + + self.assertEqual(response.status_code, 200) + self.assertEqual(OrganisationType.objects.count(), 1) + self.assertEqual(OrganisationType.objects.first().name, "updated") + + def test_partial_update_organisation_type(self): + data = {"name": "updated"} + response = self.patch(self.organisation_type.id, data) + + self.assertEqual(response.status_code, 200) + self.assertEqual(OrganisationType.objects.count(), 1) + self.assertEqual(OrganisationType.objects.first().name, "updated") + + def test_read_organisation_types(self): + response = self.get() + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.data["count"], 1) + self.assertEqual( + response.data["results"], + [organisation_type_to_dict(self.organisation_type)], + ) + + def test_read_organisation_type(self): + response = self.get(self.organisation_type.id) + + self.assertEqual(response.status_code, 200) + self.assertEqual( + response.data, organisation_type_to_dict(self.organisation_type) + ) + + def test_delete_organisation_type(self): + response = self.delete(self.organisation_type.id) + + self.assertEqual(response.status_code, 204) + self.assertEqual(OrganisationType.objects.count(), 0) diff --git a/src/open_producten/locations/views.py b/src/open_producten/locations/views.py index e69de29..33997fb 100644 --- a/src/open_producten/locations/views.py +++ b/src/open_producten/locations/views.py @@ -0,0 +1,45 @@ +from open_producten.locations.models import ( + Contact, + Location, + Neighbourhood, + Organisation, + OrganisationType, +) +from open_producten.locations.serializers.location import ( + ContactSerializer, + LocationSerializer, + NeighbourhoodSerializer, + OrganisationSerializer, + OrganisationTypeSerializer, +) +from open_producten.utils.views import OrderedModelViewSet + + +class LocationViewSet(OrderedModelViewSet): + queryset = Location.objects.all() + serializer_class = LocationSerializer + lookup_field = "id" + + +class ContactViewSet(OrderedModelViewSet): + queryset = Contact.objects.all() + serializer_class = ContactSerializer + lookup_field = "id" + + +class OrganisationViewSet(OrderedModelViewSet): + queryset = Organisation.objects.all() + serializer_class = OrganisationSerializer + lookup_field = "id" + + +class NeighbourhoodViewSet(OrderedModelViewSet): + queryset = Neighbourhood.objects.all() + serializer_class = NeighbourhoodSerializer + lookup_field = "id" + + +class OrganisationTypeViewSet(OrderedModelViewSet): + queryset = OrganisationType.objects.all() + serializer_class = OrganisationTypeSerializer + lookup_field = "id" diff --git a/src/open_producten/products/tests/api/test_product.py b/src/open_producten/products/tests/api/test_product.py index 309b882..338151b 100644 --- a/src/open_producten/products/tests/api/test_product.py +++ b/src/open_producten/products/tests/api/test_product.py @@ -27,7 +27,15 @@ def product_to_dict(product): product_dict["product_type"] = model_to_dict_with_id( product.product_type, - exclude=("categories", "conditions", "tags", "related_product_types"), + exclude=( + "categories", + "conditions", + "tags", + "related_product_types", + "contacts", + "locations", + "organisations", + ), ) product_dict["product_type"][ "uniform_product_name" @@ -225,6 +233,7 @@ def test_update_product(self): self.assertEqual(response.status_code, 200) self.assertEqual(Product.objects.count(), 1) + self.assertEqual(Product.objects.first().end_date, data["end_date"]) def test_update_product_without_bsn_or_kvk(self): product = self._create_product() diff --git a/src/open_producten/producttypes/serializers/category.py b/src/open_producten/producttypes/serializers/category.py index 47354dd..c075218 100644 --- a/src/open_producten/producttypes/serializers/category.py +++ b/src/open_producten/producttypes/serializers/category.py @@ -16,7 +16,15 @@ class SimpleProductTypeSerializer(serializers.ModelSerializer): class Meta: model = ProductType - exclude = ("categories", "conditions", "tags", "related_product_types") + exclude = ( + "categories", + "conditions", + "tags", + "related_product_types", + "organisations", + "locations", + "contacts", + ) class CategorySerializer(serializers.ModelSerializer): @@ -39,7 +47,7 @@ class Meta: model = Category exclude = ("path", "depth", "numchild") - def _handle_relations(self, instance, product_types): + def _handle_relations(self, instance, product_types: list[ProductType]): errors = dict() if product_types is not None: build_array_duplicates_error_message( diff --git a/src/open_producten/producttypes/serializers/children.py b/src/open_producten/producttypes/serializers/children.py index a1ec37c..217773f 100644 --- a/src/open_producten/producttypes/serializers/children.py +++ b/src/open_producten/producttypes/serializers/children.py @@ -32,7 +32,7 @@ class Meta: model = Price exclude = ("product_type",) - def validate_options(self, options): + def validate_options(self, options: list[PriceOption]) -> list[PriceOption]: if len(options) == 0: raise serializers.ValidationError("At least one option is required") return options diff --git a/src/open_producten/producttypes/serializers/producttype.py b/src/open_producten/producttypes/serializers/producttype.py index cff3548..1480689 100644 --- a/src/open_producten/producttypes/serializers/producttype.py +++ b/src/open_producten/producttypes/serializers/producttype.py @@ -2,6 +2,12 @@ from rest_framework import serializers +from open_producten.locations.models import Contact, Location, Organisation +from open_producten.locations.serializers.location import ( + ContactSerializer, + LocationSerializer, + OrganisationSerializer, +) from open_producten.utils.serializers import build_array_duplicates_error_message from ..models import Category, Condition, ProductType, Tag, UniformProductName @@ -57,6 +63,33 @@ class ProductTypeSerializer(serializers.ModelSerializer): source="categories", ) + locations = LocationSerializer(many=True, read_only=True) + location_ids = serializers.PrimaryKeyRelatedField( + many=True, + write_only=True, + queryset=Location.objects.all(), + default=[], + source="locations", + ) + + organisations = OrganisationSerializer(many=True, read_only=True) + organisation_ids = serializers.PrimaryKeyRelatedField( + many=True, + write_only=True, + queryset=Organisation.objects.all(), + default=[], + source="organisations", + ) + + contacts = ContactSerializer(many=True, read_only=True) + contact_ids = serializers.PrimaryKeyRelatedField( + many=True, + write_only=True, + queryset=Contact.objects.all(), + default=[], + source="contacts", + ) + questions = QuestionSerializer(many=True, read_only=True) fields = FieldSerializer(many=True, read_only=True) prices = PriceSerializer(many=True, read_only=True) @@ -67,14 +100,23 @@ class Meta: model = ProductType fields = "__all__" - def validate_category_ids(self, category_ids): - if len(category_ids) == 0: + def validate_category_ids(self, categories: list[Category]) -> list[Category]: + if len(categories) == 0: raise serializers.ValidationError("At least one category is required") - return category_ids + return categories def _handle_relations( - self, instance, related_product_types, categories, tags, conditions - ): + self, + *, + instance, + related_product_types: list[ProductType], + categories: list[Category], + tags: list[Tag], + conditions: list[Condition], + locations: list[Condition], + organisations: list[Organisation], + contacts: list[Contact], + ) -> None: errors = dict() if related_product_types is not None: build_array_duplicates_error_message( @@ -90,6 +132,17 @@ def _handle_relations( if conditions is not None: build_array_duplicates_error_message(conditions, "condition_ids", errors) instance.conditions.set(conditions) + if locations is not None: + build_array_duplicates_error_message(locations, "location_ids", errors) + instance.locations.set(locations) + if organisations is not None: + build_array_duplicates_error_message( + organisations, "organisation_ids", errors + ) + instance.organisations.set(organisations) + if contacts is not None: + build_array_duplicates_error_message(contacts, "contact_ids", errors) + instance.contacts.set(contacts) if errors: raise serializers.ValidationError(errors) @@ -100,11 +153,21 @@ def create(self, validated_data): categories = validated_data.pop("categories") conditions = validated_data.pop("conditions") tags = validated_data.pop("tags") + locations = validated_data.pop("locations") + organisations = validated_data.pop("organisations") + contacts = validated_data.pop("contacts") product_type = ProductType.objects.create(**validated_data) self._handle_relations( - product_type, related_product_types, categories, tags, conditions + instance=product_type, + related_product_types=related_product_types, + categories=categories, + tags=tags, + conditions=conditions, + locations=locations, + organisations=organisations, + contacts=contacts, ) product_type.save() @@ -116,10 +179,20 @@ def update(self, instance, validated_data): categories = validated_data.pop("categories", None) conditions = validated_data.pop("conditions", None) tags = validated_data.pop("tags", None) + locations = validated_data.pop("locations", None) + organisations = validated_data.pop("organisations", None) + contacts = validated_data.pop("contacts", None) instance = super().update(instance, validated_data) self._handle_relations( - instance, related_product_types, categories, tags, conditions + instance=instance, + related_product_types=related_product_types, + categories=categories, + tags=tags, + conditions=conditions, + locations=locations, + organisations=organisations, + contacts=contacts, ) instance.save() diff --git a/src/open_producten/producttypes/tests/api/test_category.py b/src/open_producten/producttypes/tests/api/test_category.py index 71e8434..b1b9e0d 100644 --- a/src/open_producten/producttypes/tests/api/test_category.py +++ b/src/open_producten/producttypes/tests/api/test_category.py @@ -74,7 +74,10 @@ def test_create_category_with_product_type(self): self.assertEqual(response.status_code, 201) self.assertEqual(Category.objects.count(), 1) - self.assertEqual(Category.objects.first().product_types.first(), product_type) + self.assertEqual( + list(Category.objects.values_list("product_types", flat=True)), + [product_type.id], + ) def test_create_parent_with_duplicate_product_types_returns_error(self): product_type = ProductTypeFactory.create() diff --git a/src/open_producten/producttypes/tests/api/test_category_question.py b/src/open_producten/producttypes/tests/api/test_category_question.py index 14c4b2a..a5f6102 100644 --- a/src/open_producten/producttypes/tests/api/test_category_question.py +++ b/src/open_producten/producttypes/tests/api/test_category_question.py @@ -17,12 +17,11 @@ class TestCategoryQuestion(BaseApiTestCase): def setUp(self): super().setUp() - self.category = CategoryFactory.create() + category = CategoryFactory.create() self.data = {"question": "18?", "answer": "eligible"} - self.path = f"/api/v1/categories/{self.category.id}/questions/" + self.path = f"/api/v1/categories/{category.id}/questions/" - def _create_question(self): - return QuestionFactory.create(category=self.category) + self.question = QuestionFactory.create(category=category) def test_read_question_without_credentials_returns_error(self): response = APIClient().get(self.path) @@ -32,49 +31,39 @@ def test_create_question(self): response = self.post(self.data) self.assertEqual(response.status_code, 201) - self.assertEqual(Question.objects.count(), 1) - self.assertEqual(self.category.questions.first().question, "18?") + self.assertEqual(Question.objects.count(), 2) def test_update_question(self): - question = self._create_question() - data = self.data | {"question": "21?"} - response = self.put(question.id, data) + response = self.put(self.question.id, data) self.assertEqual(response.status_code, 200) self.assertEqual(Question.objects.count(), 1) self.assertEqual(Category.objects.first().questions.first().question, "21?") def test_partial_update_question(self): - question = self._create_question() - data = {"question": "21?"} - response = self.patch(question.id, data) + response = self.patch(self.question.id, data) self.assertEqual(response.status_code, 200) self.assertEqual(Question.objects.count(), 1) self.assertEqual(Category.objects.first().questions.first().question, "21?") def test_read_questions(self): - question = self._create_question() - response = self.get() self.assertEqual(response.status_code, 200) self.assertEqual(response.data["count"], 1) - self.assertEqual(response.data["results"], [question_to_dict(question)]) + self.assertEqual(response.data["results"], [question_to_dict(self.question)]) def test_read_question(self): - question = self._create_question() - - response = self.get(question.id) + response = self.get(self.question.id) self.assertEqual(response.status_code, 200) - self.assertEqual(response.data, question_to_dict(question)) + self.assertEqual(response.data, question_to_dict(self.question)) def test_delete_question(self): - question = self._create_question() - response = self.delete(question.id) + response = self.delete(self.question.id) self.assertEqual(response.status_code, 204) self.assertEqual(Question.objects.count(), 0) diff --git a/src/open_producten/producttypes/tests/api/test_product_type_field.py b/src/open_producten/producttypes/tests/api/test_product_type_field.py index 603c209..6cfd4d9 100644 --- a/src/open_producten/producttypes/tests/api/test_product_type_field.py +++ b/src/open_producten/producttypes/tests/api/test_product_type_field.py @@ -19,8 +19,7 @@ def setUp(self): self.data = {"name": "test field", "description": "test", "type": "textfield"} self.path = f"/api/v1/producttypes/{self.product_type.id}/fields/" - def _create_field(self): - return FieldFactory.create(product_type=self.product_type) + self.field = FieldFactory.create(product_type=self.product_type) def test_read_field_without_credentials_returns_error(self): response = APIClient().get(self.path) @@ -30,8 +29,7 @@ def test_create_field(self): response = self.post(self.data) self.assertEqual(response.status_code, 201) - self.assertEqual(Field.objects.count(), 1) - self.assertEqual(ProductType.objects.first().fields.first().name, "test field") + self.assertEqual(Field.objects.count(), 2) def test_create_normal_field_with_choices_returns_error(self): response = self.post(self.data | {"choices": ["a", "b"]}) @@ -62,20 +60,16 @@ def test_create_choice_field_without_choices_returns_error(self): ) def test_update_field(self): - field = self._create_field() - data = self.data | {"name": "updated"} - response = self.put(field.id, data) + response = self.put(self.field.id, data) self.assertEqual(response.status_code, 200) self.assertEqual(Field.objects.count(), 1) self.assertEqual(ProductType.objects.first().fields.first().name, "updated") def test_partial_update_field(self): - field = self._create_field() - data = {"name": "updated"} - response = self.patch(field.id, data) + response = self.patch(self.field.id, data) self.assertEqual(response.status_code, 200) self.assertEqual(Field.objects.count(), 1) @@ -90,29 +84,26 @@ def test_partial_update_change_choices(self): response = self.patch(field.id, data) self.assertEqual(response.status_code, 200) - self.assertEqual(Field.objects.count(), 1) - self.assertEqual(ProductType.objects.first().fields.first().choices, ["a"]) + self.assertEqual(Field.objects.count(), 2) + self.assertEqual( + ProductType.objects.first().fields.get(id=field.id).choices, ["a"] + ) def test_read_fields(self): - field = self._create_field() - response = self.get() self.assertEqual(response.status_code, 200) self.assertEqual(response.data["count"], 1) - self.assertEqual(response.data["results"], [field_to_dict(field)]) + self.assertEqual(response.data["results"], [field_to_dict(self.field)]) def test_read_field(self): - field = self._create_field() - - response = self.get(field.id) + response = self.get(self.field.id) self.assertEqual(response.status_code, 200) - self.assertEqual(response.data, field_to_dict(field)) + self.assertEqual(response.data, field_to_dict(self.field)) def test_delete_field(self): - field = self._create_field() - response = self.delete(field.id) + response = self.delete(self.field.id) self.assertEqual(response.status_code, 204) self.assertEqual(Field.objects.count(), 0) diff --git a/src/open_producten/producttypes/tests/api/test_product_type_link.py b/src/open_producten/producttypes/tests/api/test_product_type_link.py index b5617d1..219e52b 100644 --- a/src/open_producten/producttypes/tests/api/test_product_type_link.py +++ b/src/open_producten/producttypes/tests/api/test_product_type_link.py @@ -15,12 +15,11 @@ class TestProductTypeLink(BaseApiTestCase): def setUp(self): super().setUp() - self.product_type = ProductTypeFactory.create() + product_type = ProductTypeFactory.create() self.data = {"name": "test link", "url": "https://www.google.com"} - self.path = f"/api/v1/producttypes/{self.product_type.id}/links/" + self.path = f"/api/v1/producttypes/{product_type.id}/links/" - def _create_link(self): - return LinkFactory.create(product_type=self.product_type) + self.link = LinkFactory.create(product_type=product_type) def test_read_link_without_credentials_returns_error(self): response = APIClient().get(self.path) @@ -30,49 +29,39 @@ def test_create_link(self): response = self.post(self.data) self.assertEqual(response.status_code, 201) - self.assertEqual(Link.objects.count(), 1) - self.assertEqual(ProductType.objects.first().links.first().name, "test link") + self.assertEqual(Link.objects.count(), 2) def test_update_link(self): - link = self._create_link() - data = self.data | {"name": "updated"} - response = self.put(link.id, data) + response = self.put(self.link.id, data) self.assertEqual(response.status_code, 200) self.assertEqual(Link.objects.count(), 1) self.assertEqual(ProductType.objects.first().links.first().name, "updated") def test_partial_update_link(self): - link = self._create_link() - data = {"name": "updated"} - response = self.patch(link.id, data) + response = self.patch(self.link.id, data) self.assertEqual(response.status_code, 200) self.assertEqual(Link.objects.count(), 1) self.assertEqual(ProductType.objects.first().links.first().name, "updated") def test_read_links(self): - link = self._create_link() - response = self.get() self.assertEqual(response.status_code, 200) self.assertEqual(response.data["count"], 1) - self.assertEqual(response.data["results"], [link_to_dict(link)]) + self.assertEqual(response.data["results"], [link_to_dict(self.link)]) def test_read_link(self): - link = self._create_link() - - response = self.get(link.id) + response = self.get(self.link.id) self.assertEqual(response.status_code, 200) - self.assertEqual(response.data, link_to_dict(link)) + self.assertEqual(response.data, link_to_dict(self.link)) def test_delete_link(self): - link = self._create_link() - response = self.delete(link.id) + response = self.delete(self.link.id) self.assertEqual(response.status_code, 204) self.assertEqual(Link.objects.count(), 0) diff --git a/src/open_producten/producttypes/tests/api/test_product_type_question.py b/src/open_producten/producttypes/tests/api/test_product_type_question.py index 3e9616d..9a7c66f 100644 --- a/src/open_producten/producttypes/tests/api/test_product_type_question.py +++ b/src/open_producten/producttypes/tests/api/test_product_type_question.py @@ -24,8 +24,7 @@ def setUp(self): self.data = {"question": "18?", "answer": "eligible"} self.path = f"/api/v1/producttypes/{self.product_type.id}/questions/" - def _create_question(self): - return QuestionFactory.create(product_type=self.product_type) + self.question = QuestionFactory.create(product_type=self.product_type) def test_read_question_without_credentials_returns_error(self): response = APIClient().get(self.path) @@ -35,49 +34,39 @@ def test_create_question(self): response = self.post(self.data) self.assertEqual(response.status_code, 201) - self.assertEqual(Question.objects.count(), 1) - self.assertEqual(self.product_type.questions.first().question, "18?") + self.assertEqual(Question.objects.count(), 2) def test_update_question(self): - question = self._create_question() - data = self.data | {"question": "21?"} - response = self.put(question.id, data) + response = self.put(self.question.id, data) self.assertEqual(response.status_code, 200) self.assertEqual(Question.objects.count(), 1) self.assertEqual(ProductType.objects.first().questions.first().question, "21?") def test_partial_update_question(self): - question = self._create_question() - data = {"question": "21?"} - response = self.patch(question.id, data) + response = self.patch(self.question.id, data) self.assertEqual(response.status_code, 200) self.assertEqual(Question.objects.count(), 1) self.assertEqual(ProductType.objects.first().questions.first().question, "21?") def test_read_questions(self): - question = self._create_question() - response = self.get() self.assertEqual(response.status_code, 200) self.assertEqual(response.data["count"], 1) - self.assertEqual(response.data["results"], [question_to_dict(question)]) + self.assertEqual(response.data["results"], [question_to_dict(self.question)]) def test_read_question(self): - question = self._create_question() - - response = self.get(question.id) + response = self.get(self.question.id) self.assertEqual(response.status_code, 200) - self.assertEqual(response.data, question_to_dict(question)) + self.assertEqual(response.data, question_to_dict(self.question)) def test_delete_question(self): - question = self._create_question() - response = self.delete(question.id) + response = self.delete(self.question.id) self.assertEqual(response.status_code, 204) self.assertEqual(Question.objects.count(), 0) diff --git a/src/open_producten/producttypes/tests/api/test_producttype.py b/src/open_producten/producttypes/tests/api/test_producttype.py index 841b955..be1e828 100644 --- a/src/open_producten/producttypes/tests/api/test_producttype.py +++ b/src/open_producten/producttypes/tests/api/test_producttype.py @@ -3,6 +3,11 @@ from rest_framework.exceptions import ErrorDetail from rest_framework.test import APIClient +from open_producten.locations.tests.factories import ( + ContactFactory, + LocationFactory, + OrganisationFactory, +) from open_producten.producttypes.models import Link, ProductType, Tag from open_producten.producttypes.tests.factories import ( CategoryFactory, @@ -127,10 +132,8 @@ def test_create_product_type_with_category(self): self.assertEqual(response.status_code, 201) self.assertEqual(ProductType.objects.count(), 1) self.assertEqual( - ProductType.objects.first().categories.first().name, category.name - ) - self.assertEqual( - response.data, product_type_to_dict(ProductType.objects.first()) + list(ProductType.objects.values_list("categories__name", flat=True)), + [category.name], ) def test_create_product_type_with_tag(self): @@ -141,7 +144,9 @@ def test_create_product_type_with_tag(self): self.assertEqual(response.status_code, 201) self.assertEqual(ProductType.objects.count(), 1) - self.assertEqual(ProductType.objects.first().tags.first().name, tag.name) + self.assertEqual( + list(ProductType.objects.values_list("tags__name", flat=True)), [tag.name] + ) def test_create_product_type_with_condition(self): condition = ConditionFactory.create() @@ -152,7 +157,47 @@ def test_create_product_type_with_condition(self): self.assertEqual(response.status_code, 201) self.assertEqual(ProductType.objects.count(), 1) self.assertEqual( - ProductType.objects.first().conditions.first().name, condition.name + list(ProductType.objects.values_list("conditions__name", flat=True)), + [condition.name], + ) + + def test_create_product_type_with_location(self): + location = LocationFactory.create() + + data = self.data | {"location_ids": [location.id]} + response = self.post(data) + + self.assertEqual(response.status_code, 201) + self.assertEqual(ProductType.objects.count(), 1) + self.assertEqual( + list(ProductType.objects.values_list("locations__name", flat=True)), + [location.name], + ) + + def test_create_product_type_with_organisation(self): + organisation = OrganisationFactory.create() + + data = self.data | {"organisation_ids": [organisation.id]} + response = self.post(data) + + self.assertEqual(response.status_code, 201) + self.assertEqual(ProductType.objects.count(), 1) + self.assertEqual( + list(ProductType.objects.values_list("organisations__name", flat=True)), + [organisation.name], + ) + + def test_create_product_type_with_contact(self): + contact = ContactFactory.create() + + data = self.data | {"contact_ids": [contact.id]} + response = self.post(data) + + self.assertEqual(response.status_code, 201) + self.assertEqual(ProductType.objects.count(), 1) + self.assertEqual( + list(ProductType.objects.values_list("contacts__first_name", flat=True)), + [contact.first_name], ) def test_create_product_type_with_duplicate_ids_returns_error(self): @@ -233,7 +278,10 @@ def test_update_product_type_with_category(self): self.assertEqual(response.status_code, 200) self.assertEqual(ProductType.objects.count(), 1) - self.assertEqual(product_type.categories.first().name, category.name) + self.assertEqual( + list(ProductType.objects.values_list("categories__name", flat=True)), + [category.name], + ) def test_update_product_type_with_tag(self): product_type = ProductTypeFactory.create() @@ -244,7 +292,9 @@ def test_update_product_type_with_tag(self): self.assertEqual(response.status_code, 200) self.assertEqual(ProductType.objects.count(), 1) - self.assertEqual(product_type.tags.first().name, tag.name) + self.assertEqual( + list(ProductType.objects.values_list("tags__name", flat=True)), [tag.name] + ) def test_update_product_type_with_condition(self): product_type = ProductTypeFactory.create() @@ -255,7 +305,52 @@ def test_update_product_type_with_condition(self): self.assertEqual(response.status_code, 200) self.assertEqual(ProductType.objects.count(), 1) - self.assertEqual(product_type.conditions.first().name, condition.name) + self.assertEqual( + list(ProductType.objects.values_list("conditions__name", flat=True)), + [condition.name], + ) + + def test_update_product_type_with_location(self): + product_type = ProductTypeFactory.create() + location = LocationFactory.create() + + data = self.data | {"location_ids": [location.id]} + response = self.put(product_type.id, data) + + self.assertEqual(response.status_code, 200) + self.assertEqual(ProductType.objects.count(), 1) + self.assertEqual( + list(ProductType.objects.values_list("locations__name", flat=True)), + [location.name], + ) + + def test_update_product_type_with_organisation(self): + product_type = ProductTypeFactory.create() + organisation = OrganisationFactory.create() + + data = self.data | {"organisation_ids": [organisation.id]} + response = self.put(product_type.id, data) + + self.assertEqual(response.status_code, 200) + self.assertEqual(ProductType.objects.count(), 1) + self.assertEqual( + list(ProductType.objects.values_list("organisations__name", flat=True)), + [organisation.name], + ) + + def test_update_product_type_with_contact(self): + product_type = ProductTypeFactory.create() + contact = ContactFactory.create() + + data = self.data | {"contact_ids": [contact.id]} + response = self.put(product_type.id, data) + + self.assertEqual(response.status_code, 200) + self.assertEqual(ProductType.objects.count(), 1) + self.assertEqual( + list(ProductType.objects.values_list("contacts__first_name", flat=True)), + [contact.first_name], + ) def test_update_product_type_with_duplicate_ids_returns_error(self): product_type = ProductTypeFactory.create() diff --git a/src/open_producten/urls.py b/src/open_producten/urls.py index 38e1390..1376961 100644 --- a/src/open_producten/urls.py +++ b/src/open_producten/urls.py @@ -16,6 +16,7 @@ from maykin_2fa.urls import urlpatterns, webauthn_urlpatterns from open_producten.accounts.views.password_reset import PasswordResetView +from open_producten.locations.router import location_urlpatterns from open_producten.products.router import product_urlpatterns from open_producten.producttypes.router import product_type_urlpatterns @@ -78,6 +79,7 @@ ), path("", include(product_type_urlpatterns)), path("", include(product_urlpatterns)), + path("", include(location_urlpatterns)), ] ), ), diff --git a/src/openapi.yaml b/src/openapi.yaml index 7acf493..f83ea91 100644 --- a/src/openapi.yaml +++ b/src/openapi.yaml @@ -439,6 +439,666 @@ paths: responses: '204': description: No response body + /api/v1/contacts/: + get: + operationId: contacts_list + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + tags: + - contacts + security: + - tokenAuth: [ ] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedContactList' + description: '' + post: + operationId: contacts_create + tags: + - contacts + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Contact' + required: true + security: + - tokenAuth: [ ] + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/Contact' + description: '' + /api/v1/contacts/{id}/: + get: + operationId: contacts_retrieve + parameters: + - in: path + name: id + schema: + type: string + format: uuid + description: A UUID string identifying this Contact. + required: true + tags: + - contacts + security: + - tokenAuth: [ ] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Contact' + description: '' + put: + operationId: contacts_update + parameters: + - in: path + name: id + schema: + type: string + format: uuid + description: A UUID string identifying this Contact. + required: true + tags: + - contacts + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Contact' + required: true + security: + - tokenAuth: [ ] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Contact' + description: '' + patch: + operationId: contacts_partial_update + parameters: + - in: path + name: id + schema: + type: string + format: uuid + description: A UUID string identifying this Contact. + required: true + tags: + - contacts + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedContact' + security: + - tokenAuth: [ ] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Contact' + description: '' + delete: + operationId: contacts_destroy + parameters: + - in: path + name: id + schema: + type: string + format: uuid + description: A UUID string identifying this Contact. + required: true + tags: + - contacts + security: + - tokenAuth: [ ] + responses: + '204': + description: No response body + /api/v1/locations/: + get: + operationId: locations_list + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + tags: + - locations + security: + - tokenAuth: [ ] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedLocationList' + description: '' + post: + operationId: locations_create + tags: + - locations + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Location' + required: true + security: + - tokenAuth: [ ] + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/Location' + description: '' + /api/v1/locations/{id}/: + get: + operationId: locations_retrieve + parameters: + - in: path + name: id + schema: + type: string + format: uuid + description: A UUID string identifying this Location. + required: true + tags: + - locations + security: + - tokenAuth: [ ] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Location' + description: '' + put: + operationId: locations_update + parameters: + - in: path + name: id + schema: + type: string + format: uuid + description: A UUID string identifying this Location. + required: true + tags: + - locations + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Location' + required: true + security: + - tokenAuth: [ ] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Location' + description: '' + patch: + operationId: locations_partial_update + parameters: + - in: path + name: id + schema: + type: string + format: uuid + description: A UUID string identifying this Location. + required: true + tags: + - locations + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedLocation' + security: + - tokenAuth: [ ] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Location' + description: '' + delete: + operationId: locations_destroy + parameters: + - in: path + name: id + schema: + type: string + format: uuid + description: A UUID string identifying this Location. + required: true + tags: + - locations + security: + - tokenAuth: [ ] + responses: + '204': + description: No response body + /api/v1/neighbourhoods/: + get: + operationId: neighbourhoods_list + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + tags: + - neighbourhoods + security: + - tokenAuth: [ ] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedNeighbourhoodList' + description: '' + post: + operationId: neighbourhoods_create + tags: + - neighbourhoods + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Neighbourhood' + required: true + security: + - tokenAuth: [ ] + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/Neighbourhood' + description: '' + /api/v1/neighbourhoods/{id}/: + get: + operationId: neighbourhoods_retrieve + parameters: + - in: path + name: id + schema: + type: string + format: uuid + description: A UUID string identifying this Neighbourhood. + required: true + tags: + - neighbourhoods + security: + - tokenAuth: [ ] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Neighbourhood' + description: '' + put: + operationId: neighbourhoods_update + parameters: + - in: path + name: id + schema: + type: string + format: uuid + description: A UUID string identifying this Neighbourhood. + required: true + tags: + - neighbourhoods + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Neighbourhood' + required: true + security: + - tokenAuth: [ ] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Neighbourhood' + description: '' + patch: + operationId: neighbourhoods_partial_update + parameters: + - in: path + name: id + schema: + type: string + format: uuid + description: A UUID string identifying this Neighbourhood. + required: true + tags: + - neighbourhoods + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedNeighbourhood' + security: + - tokenAuth: [ ] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Neighbourhood' + description: '' + delete: + operationId: neighbourhoods_destroy + parameters: + - in: path + name: id + schema: + type: string + format: uuid + description: A UUID string identifying this Neighbourhood. + required: true + tags: + - neighbourhoods + security: + - tokenAuth: [ ] + responses: + '204': + description: No response body + /api/v1/organisations/: + get: + operationId: organisations_list + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + tags: + - organisations + security: + - tokenAuth: [ ] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedOrganisationList' + description: '' + post: + operationId: organisations_create + tags: + - organisations + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Organisation' + required: true + security: + - tokenAuth: [ ] + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/Organisation' + description: '' + /api/v1/organisations/{id}/: + get: + operationId: organisations_retrieve + parameters: + - in: path + name: id + schema: + type: string + format: uuid + description: A UUID string identifying this Organisation. + required: true + tags: + - organisations + security: + - tokenAuth: [ ] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Organisation' + description: '' + put: + operationId: organisations_update + parameters: + - in: path + name: id + schema: + type: string + format: uuid + description: A UUID string identifying this Organisation. + required: true + tags: + - organisations + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Organisation' + required: true + security: + - tokenAuth: [ ] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Organisation' + description: '' + patch: + operationId: organisations_partial_update + parameters: + - in: path + name: id + schema: + type: string + format: uuid + description: A UUID string identifying this Organisation. + required: true + tags: + - organisations + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedOrganisation' + security: + - tokenAuth: [ ] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Organisation' + description: '' + delete: + operationId: organisations_destroy + parameters: + - in: path + name: id + schema: + type: string + format: uuid + description: A UUID string identifying this Organisation. + required: true + tags: + - organisations + security: + - tokenAuth: [ ] + responses: + '204': + description: No response body + /api/v1/organisationtypes/: + get: + operationId: organisationtypes_list + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + tags: + - organisationtypes + security: + - tokenAuth: [ ] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedOrganisationTypeList' + description: '' + post: + operationId: organisationtypes_create + tags: + - organisationtypes + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/OrganisationType' + required: true + security: + - tokenAuth: [ ] + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/OrganisationType' + description: '' + /api/v1/organisationtypes/{id}/: + get: + operationId: organisationtypes_retrieve + parameters: + - in: path + name: id + schema: + type: string + format: uuid + description: A UUID string identifying this Organisation type. + required: true + tags: + - organisationtypes + security: + - tokenAuth: [ ] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/OrganisationType' + description: '' + put: + operationId: organisationtypes_update + parameters: + - in: path + name: id + schema: + type: string + format: uuid + description: A UUID string identifying this Organisation type. + required: true + tags: + - organisationtypes + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/OrganisationType' + required: true + security: + - tokenAuth: [ ] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/OrganisationType' + description: '' + patch: + operationId: organisationtypes_partial_update + parameters: + - in: path + name: id + schema: + type: string + format: uuid + description: A UUID string identifying this Organisation type. + required: true + tags: + - organisationtypes + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedOrganisationType' + security: + - tokenAuth: [ ] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/OrganisationType' + description: '' + delete: + operationId: organisationtypes_destroy + parameters: + - in: path + name: id + schema: + type: string + format: uuid + description: A UUID string identifying this Organisation type. + required: true + tags: + - organisationtypes + security: + - tokenAuth: [ ] + responses: + '204': + description: No response body /api/v1/products/: get: operationId: products_list @@ -1769,6 +2429,50 @@ components: - negative_text - positive_text - question + Contact: + type: object + properties: + id: + type: string + format: uuid + readOnly: true + organisation: + allOf: + - $ref: '#/components/schemas/Organisation' + readOnly: true + organisation_id: + type: string + format: uuid + writeOnly: true + first_name: + type: string + description: First name of the contact + maxLength: 255 + last_name: + type: string + description: Last name of the contact + maxLength: 255 + email: + type: string + format: email + title: Email address + description: The email address of the contact + maxLength: 254 + phone_number: + type: string + description: The phone number of the contact + maxLength: 15 + role: + type: string + title: Rol + description: The role/function of the contact + maxLength: 100 + required: + - first_name + - id + - last_name + - organisation + - organisation_id Data: type: object properties: @@ -1874,24 +2578,268 @@ components: Link: type: object properties: - id: - type: string - format: uuid - readOnly: true - name: + id: + type: string + format: uuid + readOnly: true + name: + type: string + description: Name for the link + maxLength: 100 + url: + type: string + format: uri + description: Url of the link + maxLength: 200 + required: + - id + - name + - url + Location: + type: object + properties: + id: + type: string + format: uuid + readOnly: true + name: + type: string + maxLength: 100 + email: + type: string + format: email + title: Email address + maxLength: 254 + phone_number: + type: string + maxLength: 15 + street: + type: string + description: Address street + maxLength: 250 + house_number: + type: string + maxLength: 250 + postcode: + type: string + description: Address postcode + pattern: ^[1-9][0-9]{3} ?[a-zA-Z]{2}$ + maxLength: 7 + city: + type: string + description: Address city + maxLength: 250 + required: + - city + - id + - postcode + Neighbourhood: + type: object + properties: + id: + type: string + format: uuid + readOnly: true + name: + type: string + description: Neighbourhood name + maxLength: 100 + required: + - id + - name + Organisation: + type: object + properties: + id: + type: string + format: uuid + readOnly: true + neighbourhood: + allOf: + - $ref: '#/components/schemas/Neighbourhood' + readOnly: true + type: + allOf: + - $ref: '#/components/schemas/OrganisationType' + readOnly: true + neighbourhood_id: + type: string + format: uuid + writeOnly: true + type_id: + type: string + format: uuid + writeOnly: true + name: + type: string + maxLength: 100 + email: + type: string + format: email + title: Email address + maxLength: 254 + phone_number: + type: string + maxLength: 15 + street: + type: string + description: Address street + maxLength: 250 + house_number: + type: string + maxLength: 250 + postcode: + type: string + description: Address postcode + pattern: ^[1-9][0-9]{3} ?[a-zA-Z]{2}$ + maxLength: 7 + city: + type: string + description: Address city + maxLength: 250 + logo: + type: string + format: uri + nullable: true + description: Logo of the organisation + required: + - city + - id + - neighbourhood + - neighbourhood_id + - postcode + - type + - type_id + OrganisationType: + type: object + properties: + id: + type: string + format: uuid + readOnly: true + name: + type: string + description: Organisation type + maxLength: 100 + required: + - id + - name + PaginatedCategoryList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/Category' + PaginatedConditionList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/Condition' + PaginatedContactList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/Contact' + PaginatedFieldList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/Field' + PaginatedLinkList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: type: string - description: Name for the link - maxLength: 100 - url: + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: type: string + nullable: true format: uri - description: Url of the link - maxLength: 200 - required: - - id - - name - - url - PaginatedCategoryList: + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/Link' + PaginatedLocationList: type: object required: - count @@ -1913,8 +2861,8 @@ components: results: type: array items: - $ref: '#/components/schemas/Category' - PaginatedConditionList: + $ref: '#/components/schemas/Location' + PaginatedNeighbourhoodList: type: object required: - count @@ -1936,8 +2884,8 @@ components: results: type: array items: - $ref: '#/components/schemas/Condition' - PaginatedFieldList: + $ref: '#/components/schemas/Neighbourhood' + PaginatedOrganisationList: type: object required: - count @@ -1959,8 +2907,8 @@ components: results: type: array items: - $ref: '#/components/schemas/Field' - PaginatedLinkList: + $ref: '#/components/schemas/Organisation' + PaginatedOrganisationTypeList: type: object required: - count @@ -1982,7 +2930,7 @@ components: results: type: array items: - $ref: '#/components/schemas/Link' + $ref: '#/components/schemas/OrganisationType' PaginatedPriceList: type: object required: @@ -2199,6 +3147,44 @@ components: negative_text: type: string description: Description how not to meet the condition + PatchedContact: + type: object + properties: + id: + type: string + format: uuid + readOnly: true + organisation: + allOf: + - $ref: '#/components/schemas/Organisation' + readOnly: true + organisation_id: + type: string + format: uuid + writeOnly: true + first_name: + type: string + description: First name of the contact + maxLength: 255 + last_name: + type: string + description: Last name of the contact + maxLength: 255 + email: + type: string + format: email + title: Email address + description: The email address of the contact + maxLength: 254 + phone_number: + type: string + description: The phone number of the contact + maxLength: 15 + role: + type: string + title: Rol + description: The role/function of the contact + maxLength: 100 PatchedField: type: object properties: @@ -2265,6 +3251,117 @@ components: format: uri description: Url of the link maxLength: 200 + PatchedLocation: + type: object + properties: + id: + type: string + format: uuid + readOnly: true + name: + type: string + maxLength: 100 + email: + type: string + format: email + title: Email address + maxLength: 254 + phone_number: + type: string + maxLength: 15 + street: + type: string + description: Address street + maxLength: 250 + house_number: + type: string + maxLength: 250 + postcode: + type: string + description: Address postcode + pattern: ^[1-9][0-9]{3} ?[a-zA-Z]{2}$ + maxLength: 7 + city: + type: string + description: Address city + maxLength: 250 + PatchedNeighbourhood: + type: object + properties: + id: + type: string + format: uuid + readOnly: true + name: + type: string + description: Neighbourhood name + maxLength: 100 + PatchedOrganisation: + type: object + properties: + id: + type: string + format: uuid + readOnly: true + neighbourhood: + allOf: + - $ref: '#/components/schemas/Neighbourhood' + readOnly: true + type: + allOf: + - $ref: '#/components/schemas/OrganisationType' + readOnly: true + neighbourhood_id: + type: string + format: uuid + writeOnly: true + type_id: + type: string + format: uuid + writeOnly: true + name: + type: string + maxLength: 100 + email: + type: string + format: email + title: Email address + maxLength: 254 + phone_number: + type: string + maxLength: 15 + street: + type: string + description: Address street + maxLength: 250 + house_number: + type: string + maxLength: 250 + postcode: + type: string + description: Address postcode + pattern: ^[1-9][0-9]{3} ?[a-zA-Z]{2}$ + maxLength: 7 + city: + type: string + description: Address city + maxLength: 250 + logo: + type: string + format: uri + nullable: true + description: Logo of the organisation + PatchedOrganisationType: + type: object + properties: + id: + type: string + format: uuid + readOnly: true + name: + type: string + description: Organisation type + maxLength: 100 PatchedPrice: type: object properties: @@ -2307,14 +3404,10 @@ components: type: string format: uuid default: [ ] - uniform_product_name_id: - type: string - format: uuid - writeOnly: true uniform_product_name: - allOf: - - $ref: '#/components/schemas/UniformProductName' - readOnly: true + type: string + format: uri + description: Uri to the upn definition. conditions: type: array items: @@ -2338,6 +3431,42 @@ components: type: string format: uuid writeOnly: true + locations: + type: array + items: + $ref: '#/components/schemas/Location' + readOnly: true + location_ids: + type: array + items: + type: string + format: uuid + writeOnly: true + default: [ ] + organisations: + type: array + items: + $ref: '#/components/schemas/Organisation' + readOnly: true + organisation_ids: + type: array + items: + type: string + format: uuid + writeOnly: true + default: [ ] + contacts: + type: array + items: + $ref: '#/components/schemas/Contact' + readOnly: true + contact_ids: + type: array + items: + type: string + format: uuid + writeOnly: true + default: [ ] questions: type: array items: @@ -2408,24 +3537,6 @@ components: type: string maxLength: 100 description: List of keywords for search - organisations: - type: array - items: - type: string - format: uuid - description: Organisations which provides this product - contacts: - type: array - items: - type: string - format: uuid - description: The contacts responsible for the product - locations: - type: array - items: - type: string - format: uuid - description: Locations where the product is available at. PatchedProductUpdate: type: object properties: @@ -2644,14 +3755,10 @@ components: type: string format: uuid default: [ ] - uniform_product_name_id: - type: string - format: uuid - writeOnly: true uniform_product_name: - allOf: - - $ref: '#/components/schemas/UniformProductName' - readOnly: true + type: string + format: uri + description: Uri to the upn definition. conditions: type: array items: @@ -2675,6 +3782,42 @@ components: type: string format: uuid writeOnly: true + locations: + type: array + items: + $ref: '#/components/schemas/Location' + readOnly: true + location_ids: + type: array + items: + type: string + format: uuid + writeOnly: true + default: [ ] + organisations: + type: array + items: + $ref: '#/components/schemas/Organisation' + readOnly: true + organisation_ids: + type: array + items: + type: string + format: uuid + writeOnly: true + default: [ ] + contacts: + type: array + items: + $ref: '#/components/schemas/Contact' + readOnly: true + contact_ids: + type: array + items: + type: string + format: uuid + writeOnly: true + default: [ ] questions: type: array items: @@ -2745,40 +3888,24 @@ components: type: string maxLength: 100 description: List of keywords for search - organisations: - type: array - items: - type: string - format: uuid - description: Organisations which provides this product - contacts: - type: array - items: - type: string - format: uuid - description: The contacts responsible for the product - locations: - type: array - items: - type: string - format: uuid - description: Locations where the product is available at. required: - categories - category_ids - conditions + - contacts - content - created_on - fields - files - id - links + - locations - name + - organisations - prices - questions - tags - uniform_product_name - - uniform_product_name_id - updated_on ProductTypeCurrentPrice: type: object @@ -2798,7 +3925,7 @@ components: upl_uri: type: string format: uri - description: Url to the upn definition. + description: Uri to the upn definition. readOnly: true current_price: allOf: @@ -2928,7 +4055,9 @@ components: format: uuid readOnly: true uniform_product_name: - $ref: '#/components/schemas/UniformProductName' + type: string + format: uri + description: Uri to the upn definition. published: type: boolean description: Whether the object is accessible through the API. @@ -3086,25 +4215,6 @@ components: * `signature` - Signature * `textfield` - Textfield * `time` - Time - UniformProductName: - type: object - properties: - id: - type: string - format: uuid - readOnly: true - name: - type: string - description: Uniform product name - maxLength: 100 - url: - type: string - format: uri - description: Url to the upn definition. - maxLength: 200 - required: - - id - - name securitySchemes: tokenAuth: type: apiKey