From c87873fcd250c1e00a5ad817020b7c6ac193f8ca Mon Sep 17 00:00:00 2001 From: Morre Date: Mon, 13 Apr 2020 19:15:49 +0200 Subject: [PATCH 1/3] feat: add requirements for Django REST framework --- requirements.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/requirements.txt b/requirements.txt index 51710b2..5165a5d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,9 @@ Django==2.2.10 django-crispy-forms==1.8.0 +django-filter==2.2.0 django-tables2==2.2.1 +djangorestframework==3.11.0 +Markdown==3.1.1 python-decouple==3.1 pytz==2019.3 sqlparse==0.3.0 From c529d46a2dccbe46a373ead32271b872629d5a0d Mon Sep 17 00:00:00 2001 From: Morre Date: Mon, 13 Apr 2020 19:18:11 +0200 Subject: [PATCH 2/3] feat: add trailing commas to isort --- .isort.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/.isort.cfg b/.isort.cfg index 37d2235..425001c 100644 --- a/.isort.cfg +++ b/.isort.cfg @@ -1,3 +1,4 @@ [settings] line_length=88 multi_line_output=3 +include_trailing_comma=True \ No newline at end of file From d44e72c80b35b0a0b526288108af0d891bd3402d Mon Sep 17 00:00:00 2001 From: Morre Date: Mon, 13 Apr 2020 20:24:27 +0200 Subject: [PATCH 3/3] feat: add API with products This commit adds a basic Django REST framework API with support for Products. It is the first step to simplify order creation. --- src/squirrel/api/__init__.py | 5 ++++ src/squirrel/api/serializers.py | 8 ++++++ src/squirrel/api/tests/__init__.py | 0 src/squirrel/api/tests/testApi.py | 39 ++++++++++++++++++++++++++++++ src/squirrel/api/urls.py | 8 ++++++ src/squirrel/api/views.py | 13 ++++++++++ src/squirrel/settings.py | 6 +++++ src/squirrel/urls.py | 6 ++--- 8 files changed, 82 insertions(+), 3 deletions(-) create mode 100644 src/squirrel/api/__init__.py create mode 100644 src/squirrel/api/serializers.py create mode 100644 src/squirrel/api/tests/__init__.py create mode 100644 src/squirrel/api/tests/testApi.py create mode 100644 src/squirrel/api/urls.py create mode 100644 src/squirrel/api/views.py diff --git a/src/squirrel/api/__init__.py b/src/squirrel/api/__init__.py new file mode 100644 index 0000000..c2deb01 --- /dev/null +++ b/src/squirrel/api/__init__.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class ApiConfig(AppConfig): + name = "squirrel.api" diff --git a/src/squirrel/api/serializers.py b/src/squirrel/api/serializers.py new file mode 100644 index 0000000..4f93aec --- /dev/null +++ b/src/squirrel/api/serializers.py @@ -0,0 +1,8 @@ +from rest_framework import serializers +from squirrel.orders.models import Product + + +class ProductSerializer(serializers.ModelSerializer): + class Meta: + model = Product + fields = ["id", "name", "unit"] diff --git a/src/squirrel/api/tests/__init__.py b/src/squirrel/api/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/squirrel/api/tests/testApi.py b/src/squirrel/api/tests/testApi.py new file mode 100644 index 0000000..5bf8de2 --- /dev/null +++ b/src/squirrel/api/tests/testApi.py @@ -0,0 +1,39 @@ +import json + +from django.contrib.auth.models import User +from rest_framework import status +from rest_framework.test import APITestCase +from squirrel.orders.models import Product + + +class ProductTests(APITestCase): + def setUp(self) -> None: + User.objects.create_user(username="any_user", password="test123") + + Product.objects.create(name="Apfel") + Product.objects.create(name="Birne") + Product.objects.create(name="Zurrgurt") + + def test_logged_out_forbidden(self): + response = self.client.get("/api/products/") + self.assertEqual(response.status_code, 403) + + def test_logged_in_ok(self): + self.client.login(username="any_user", password="test123") + self.client.get("/api/products", format="json") + + response = self.client.get("/api/products/", format="json") + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEquals( + json.loads(response.content), + { + "count": 3, + "next": None, + "previous": None, + "results": [ + {"id": 1, "name": "Apfel", "unit": "Stück"}, + {"id": 2, "name": "Birne", "unit": "Stück"}, + {"id": 3, "name": "Zurrgurt", "unit": "Stück"}, + ], + }, + ) diff --git a/src/squirrel/api/urls.py b/src/squirrel/api/urls.py new file mode 100644 index 0000000..6f15fba --- /dev/null +++ b/src/squirrel/api/urls.py @@ -0,0 +1,8 @@ +from django.urls import include, path +from rest_framework import routers +from squirrel.api import views + +router = routers.DefaultRouter() +router.register(r"products", views.ProductViewSet) + +urlpatterns = [path("", include(router.urls))] diff --git a/src/squirrel/api/views.py b/src/squirrel/api/views.py new file mode 100644 index 0000000..a8a74d8 --- /dev/null +++ b/src/squirrel/api/views.py @@ -0,0 +1,13 @@ +from rest_framework import permissions, viewsets +from squirrel.api.serializers import ProductSerializer +from squirrel.orders.models import Product + + +class ProductViewSet(viewsets.ModelViewSet): + """ + Read-Only API endpoint for products + """ + + queryset = Product.objects.all().order_by("name") + serializer_class = ProductSerializer + permission_classes = [permissions.IsAuthenticated] diff --git a/src/squirrel/settings.py b/src/squirrel/settings.py index daf04fb..80cd0e4 100644 --- a/src/squirrel/settings.py +++ b/src/squirrel/settings.py @@ -44,6 +44,7 @@ "squirrel.orders", "crispy_forms", "django_tables2", + "rest_framework", ] # Which templates to use for crispy_forms @@ -163,3 +164,8 @@ "--with-coverage", "--cover-package=orders,squirrel", ] + +REST_FRAMEWORK = { + "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination", + "PAGE_SIZE": 100, +} diff --git a/src/squirrel/urls.py b/src/squirrel/urls.py index 0657bdc..1262121 100644 --- a/src/squirrel/urls.py +++ b/src/squirrel/urls.py @@ -3,11 +3,11 @@ """ from django.contrib import admin from django.contrib.auth import views as auth_views -from django.urls import path - -from .orders import views +from django.urls import include, path +from squirrel.orders import views urlpatterns = [ + path("api/", include("squirrel.api.urls")), path("", views.overview, name="overview"), path("logout", auth_views.LogoutView.as_view(), name="logout"), path(