From 044d1f8c76a0efe61b08c989e24f84898fe892b0 Mon Sep 17 00:00:00 2001 From: Joseph Sykes Date: Tue, 14 Feb 2023 17:06:09 +0000 Subject: [PATCH 01/14] created method of registering a new manager --- answerking_app/models/serializers.py | 73 ++++++++++++++++++++++++++++ answerking_app/urls/auth_url.py | 13 +++++ answerking_app/views/auth_views.py | 19 ++++++++ 3 files changed, 105 insertions(+) create mode 100644 answerking_app/urls/auth_url.py create mode 100644 answerking_app/views/auth_views.py diff --git a/answerking_app/models/serializers.py b/answerking_app/models/serializers.py index 4d1d596b..9822ef8a 100644 --- a/answerking_app/models/serializers.py +++ b/answerking_app/models/serializers.py @@ -1,11 +1,14 @@ +import re from typing import OrderedDict +from django.contrib.auth.models import User from django.core.validators import ( MaxValueValidator, MinValueValidator, RegexValidator, ) from rest_framework import serializers, status +from rest_framework.validators import UniqueValidator from answerking_app.models.models import ( Category, @@ -309,6 +312,76 @@ class Meta: depth = 3 +class ManagerAuthSerializer(serializers.ModelSerializer): + username = serializers.CharField( + required=True, + validators=[UniqueValidator(queryset=User.objects.all())], + max_length=50, + min_length=4, + ) + email = serializers.EmailField( + required=True, + validators=[UniqueValidator(queryset=User.objects.all())], + ) + password = serializers.CharField( + write_only=True, + required=True, + min_length=8, + max_length=255, + ) + password2 = serializers.CharField( + write_only=True, + required=True, + ) + + + class Meta: + model = User, + fields = [ + 'username', + 'password', + 'password2', + 'email', + 'first_name', + 'last_name' + ] + + def validate_password_is_password2(self, attrs): + if attrs['password'] != attrs['password2']: + raise ProblemDetails( + status=status.HTTP_400_BAD_REQUEST, + detail="The passwords supplied do not match", + ) + + def validate_password(self, value): + if not re.fullmatch(r'[A-Za-z0-9]{8,}', value): + raise ProblemDetails( + status=status.HTTP_400_BAD_REQUEST, + detail="Password must be 8 characters long and contain at " + "least 1 capital letter and 1 lower case letter." + ) + + def validate_first_name(self, value: str) -> str: + return compress_white_spaces(value) + + def validate_last_name(self, value: str) -> str: + return compress_white_spaces(value) + + def create(self, validated_data): + user = User.objects.create( + username=validated_data['username'], + email=validated_data['email'], + first_name=validated_data['first_name'], + last_name=validated_data['last_name'], + is_staff=True, + is_superuser=True, + ) + + user.set_password(validated_data['password']) + user.save() + return user + + class ErrorDetailSerializer(serializers.Serializer): name = serializers.CharField() diff --git a/answerking_app/urls/auth_url.py b/answerking_app/urls/auth_url.py new file mode 100644 index 00000000..6c90be01 --- /dev/null +++ b/answerking_app/urls/auth_url.py @@ -0,0 +1,13 @@ +from functools import partial + +from django.urls import path + +from answerking_app.views import auth_views + +urlpatterns: list[partial] = [ + path( + "register/manager", + auth_views.RegisterManagerView.as_view(), + name="register_manager" + ), +] diff --git a/answerking_app/views/auth_views.py b/answerking_app/views/auth_views.py new file mode 100644 index 00000000..5ea5630b --- /dev/null +++ b/answerking_app/views/auth_views.py @@ -0,0 +1,19 @@ +from rest_framework import generics +from rest_framework.permissions import IsAdminUser + +from answerking_app.models.serializers import ManagerAuthSerializer + + +class RegisterCustomerView( + generics.GenericAPIView, +): + permission_classes = [] + serializer_class = [] + + +class RegisterManagerView( + generics.GenericAPIView, +): + permission_classes = [IsAdminUser] + serializer_class = ManagerAuthSerializer + From ceeafc494134ba6a7e0842b1bac8fb79009718da Mon Sep 17 00:00:00 2001 From: Joseph Sykes Date: Wed, 15 Feb 2023 16:30:28 +0000 Subject: [PATCH 02/14] created login and register endpoints + changed structure of Category Views to aid permissions setting --- answerking/settings/base.py | 40 ++++++++++- answerking/urls.py | 1 + answerking_app/models/models.py | 2 + .../models/permissions/auth_permissions.py | 20 ++++++ answerking_app/models/serializers.py | 46 ++++++++----- answerking_app/urls/auth_url.py | 13 ---- answerking_app/urls/auth_urls.py | 24 +++++++ answerking_app/urls/category_urls.py | 12 ++-- .../utils/schema/schema_examples.py | 2 +- answerking_app/views/auth_views.py | 23 ++++--- answerking_app/views/category_views.py | 66 +++++++++++++++--- poetry.lock | 69 +++++++++++++++---- pyproject.toml | 1 + 13 files changed, 249 insertions(+), 70 deletions(-) create mode 100644 answerking_app/models/permissions/auth_permissions.py delete mode 100644 answerking_app/urls/auth_url.py create mode 100644 answerking_app/urls/auth_urls.py diff --git a/answerking/settings/base.py b/answerking/settings/base.py index 34ed281d..509c8461 100644 --- a/answerking/settings/base.py +++ b/answerking/settings/base.py @@ -2,6 +2,7 @@ Django base settings for answerking project.. """ import os +from datetime import timedelta from os.path import join from pathlib import Path @@ -39,6 +40,7 @@ "rest_framework", "corsheaders", "drf_problems", + 'rest_framework_simplejwt.token_blacklist', ] MIDDLEWARE = [ @@ -81,6 +83,9 @@ "EXCEPTION_HANDLER": "answerking_app.utils.exceptions_handler.wrapper", "COERCE_DECIMAL_TO_STRING": False, "DATETIME_FORMAT": "%Y-%m-%dT%H:%M:%S.%fZ", + 'DEFAULT_AUTHENTICATION_CLASSES': ( + 'rest_framework_simplejwt.authentication.JWTAuthentication', + ), } # Database @@ -97,7 +102,7 @@ } } -# Password validation +# Password permissions # https://docs.djangoproject.com/en/4.0/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ @@ -120,6 +125,39 @@ # Internationalization # https://docs.djangoproject.com/en/4.0/topics/i18n/ +SIMPLE_JWT = { + 'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5), + 'REFRESH_TOKEN_LIFETIME': timedelta(days=50), + 'ROTATE_REFRESH_TOKENS': True, + 'BLACKLIST_AFTER_ROTATION': True, + 'UPDATE_LAST_LOGIN': False, + + 'ALGORITHM': 'HS256', + + 'VERIFYING_KEY': None, + 'AUDIENCE': None, + 'ISSUER': None, + 'JWK_URL': None, + 'LEEWAY': 0, + + 'AUTH_HEADER_TYPES': ('Bearer',), + 'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION', + 'USER_ID_FIELD': 'id', + 'USER_ID_CLAIM': 'user_id', + 'USER_AUTHENTICATION_RULE': 'rest_framework_simplejwt.authentication' + '.default_user_authentication_rule', + + 'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',), + 'TOKEN_TYPE_CLAIM': 'token_type', + 'TOKEN_USER_CLASS': 'rest_framework_simplejwt.models.TokenUser', + + 'JTI_CLAIM': 'jti', + + 'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp', + 'SLIDING_TOKEN_LIFETIME': timedelta(minutes=5), + 'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=1), +} + LANGUAGE_CODE = "en-us" TIME_ZONE = "UTC" diff --git a/answerking/urls.py b/answerking/urls.py index ad265f86..df3af7ea 100644 --- a/answerking/urls.py +++ b/answerking/urls.py @@ -7,6 +7,7 @@ path("api/", include("answerking_app.urls.category_urls")), path("api/", include("answerking_app.urls.order_urls")), path("api/", include("answerking_app.urls.tag_urls")), + path("api/", include("answerking_app.urls.auth_urls")), path("admin/", admin.site.urls), path("", include("drf_problems.urls")), ] diff --git a/answerking_app/models/models.py b/answerking_app/models/models.py index ff46d5dd..b6abf8c2 100644 --- a/answerking_app/models/models.py +++ b/answerking_app/models/models.py @@ -1,5 +1,6 @@ from decimal import Decimal +from django.contrib.auth.models import User from django.db import models @@ -72,3 +73,4 @@ def calculate_sub_total(self): class Meta: unique_together = [["order", "product"]] + diff --git a/answerking_app/models/permissions/auth_permissions.py b/answerking_app/models/permissions/auth_permissions.py new file mode 100644 index 00000000..03281bd6 --- /dev/null +++ b/answerking_app/models/permissions/auth_permissions.py @@ -0,0 +1,20 @@ +from rest_framework.permissions import IsAdminUser, BasePermission + + +class IsStaffUser(IsAdminUser): + """ + Allows access to Staff and Managers only. + """ + pass + + +class IsManagerUser(BasePermission): + """ + Allows access to Managers only. + """ + + def has_permission(self, request, view): + return bool( + request.user + and request.user.is_staff + ) diff --git a/answerking_app/models/serializers.py b/answerking_app/models/serializers.py index 9822ef8a..96db0c60 100644 --- a/answerking_app/models/serializers.py +++ b/answerking_app/models/serializers.py @@ -1,7 +1,9 @@ import re +from abc import ABC from typing import OrderedDict from django.contrib.auth.models import User +from django.contrib.auth.password_validation import validate_password from django.core.validators import ( MaxValueValidator, MinValueValidator, @@ -9,6 +11,7 @@ ) from rest_framework import serializers, status from rest_framework.validators import UniqueValidator +from rest_framework_simplejwt.serializers import TokenObtainPairSerializer from answerking_app.models.models import ( Category, @@ -326,40 +329,31 @@ class ManagerAuthSerializer(serializers.ModelSerializer): password = serializers.CharField( write_only=True, required=True, - min_length=8, - max_length=255, + validators=[validate_password] ) password2 = serializers.CharField( write_only=True, required=True, ) - class Meta: - model = User, - fields = [ + model = User + fields = ( 'username', 'password', 'password2', 'email', 'first_name', - 'last_name' - ] + 'last_name', + ) - def validate_password_is_password2(self, attrs): + def validate(self, attrs): if attrs['password'] != attrs['password2']: raise ProblemDetails( status=status.HTTP_400_BAD_REQUEST, detail="The passwords supplied do not match", ) - - def validate_password(self, value): - if not re.fullmatch(r'[A-Za-z0-9]{8,}', value): - raise ProblemDetails( - status=status.HTTP_400_BAD_REQUEST, - detail="Password must be 8 characters long and contain at " - "least 1 capital letter and 1 lower case letter." - ) + return attrs def validate_first_name(self, value: str) -> str: return compress_white_spaces(value) @@ -374,7 +368,7 @@ def create(self, validated_data): first_name=validated_data['first_name'], last_name=validated_data['last_name'], is_staff=True, - is_superuser=True, + is_superuser=False, ) user.set_password(validated_data['password']) @@ -382,6 +376,24 @@ def create(self, validated_data): return user +class LoginSerializer(TokenObtainPairSerializer): + @classmethod + def get_token(cls, user): + token = super().get_token(user) + token['username'] = user.username + token['email'] = user.email + return token + + def validate(self, attrs): + attrs = super().validate(attrs) + return { + "username": self.user.username, + "email": self.user.email, + "first_name": self.user.first_name, + **attrs, + } + + class ErrorDetailSerializer(serializers.Serializer): name = serializers.CharField() diff --git a/answerking_app/urls/auth_url.py b/answerking_app/urls/auth_url.py deleted file mode 100644 index 6c90be01..00000000 --- a/answerking_app/urls/auth_url.py +++ /dev/null @@ -1,13 +0,0 @@ -from functools import partial - -from django.urls import path - -from answerking_app.views import auth_views - -urlpatterns: list[partial] = [ - path( - "register/manager", - auth_views.RegisterManagerView.as_view(), - name="register_manager" - ), -] diff --git a/answerking_app/urls/auth_urls.py b/answerking_app/urls/auth_urls.py new file mode 100644 index 00000000..1a33d667 --- /dev/null +++ b/answerking_app/urls/auth_urls.py @@ -0,0 +1,24 @@ +from functools import partial + +from django.urls import path +from rest_framework_simplejwt.views import TokenRefreshView + +from answerking_app.views import auth_views + +urlpatterns: list[partial] = [ + path( + "register/manager", + auth_views.RegisterManagerView.as_view(), + name="register_manager" + ), + path( + "login", + auth_views.LoginView.as_view(), + name="token_obtain_pair" + ), + path( + 'login/refresh/', + TokenRefreshView.as_view(), + name='token_refresh' + ) +] diff --git a/answerking_app/urls/category_urls.py b/answerking_app/urls/category_urls.py index 08f24ddf..d71466e4 100644 --- a/answerking_app/urls/category_urls.py +++ b/answerking_app/urls/category_urls.py @@ -7,17 +7,17 @@ urlpatterns: list[partial] = [ path( "categories", - category_views.CategoryListView.as_view(), - name="category_list", + category_views.CategoryView.as_view(), + name="category", ), path( "categories/", - category_views.CategoryDetailView.as_view(), - name="category_detail", + category_views.CategoryIdView.as_view(), + name="category_id", ), path( "categories//products", - category_views.CategoryProductListView.as_view(), - name="category_product_list", + category_views.CategoryProductView.as_view(), + name="category_product", ), ] diff --git a/answerking_app/utils/schema/schema_examples.py b/answerking_app/utils/schema/schema_examples.py index 14da99d8..54a8d3ad 100644 --- a/answerking_app/utils/schema/schema_examples.py +++ b/answerking_app/utils/schema/schema_examples.py @@ -118,7 +118,7 @@ problem_detail_example: ProblemDetails = { "errors": {"name": "The name field is required."}, "type": "https://testserver/problems/error/", - "title": "One or more validation errors occurred.", + "title": "One or more permissions errors occurred.", "status": 400, "traceID": "00-f40e09a437a87f4ebcd2f39b128bb8f3-4b2ad798ac046140-00", "detail": "string", diff --git a/answerking_app/views/auth_views.py b/answerking_app/views/auth_views.py index 5ea5630b..039e486f 100644 --- a/answerking_app/views/auth_views.py +++ b/answerking_app/views/auth_views.py @@ -1,19 +1,20 @@ from rest_framework import generics -from rest_framework.permissions import IsAdminUser +from rest_framework.permissions import AllowAny +from rest_framework_simplejwt.views import TokenObtainPairView -from answerking_app.models.serializers import ManagerAuthSerializer - - -class RegisterCustomerView( - generics.GenericAPIView, -): - permission_classes = [] - serializer_class = [] +from answerking_app.models.permissions.auth_permissions import IsManagerUser +from answerking_app.models.serializers import ManagerAuthSerializer, \ + LoginSerializer class RegisterManagerView( - generics.GenericAPIView, + generics.CreateAPIView, ): - permission_classes = [IsAdminUser] + permission_classes = (AllowAny,) serializer_class = ManagerAuthSerializer + +class LoginView( + TokenObtainPairView, +): + serializer_class = LoginSerializer diff --git a/answerking_app/views/category_views.py b/answerking_app/views/category_views.py index 1db956c2..e4aef5e0 100644 --- a/answerking_app/views/category_views.py +++ b/answerking_app/views/category_views.py @@ -21,7 +21,6 @@ ) from answerking_app.utils.schema.schema_examples import ( category_example, - retired_category_example, category_body_example, problem_detail_example, category_products_body_example, @@ -29,11 +28,13 @@ ) -class CategoryListView( +# classes for each endpoint and type of request + +class CategoryGetView( mixins.ListModelMixin, - mixins.CreateModelMixin, generics.GenericAPIView, ): + permission_classes = [] queryset: QuerySet = Category.objects.all() serializer_class: CategorySerializer = CategorySerializer @@ -57,6 +58,15 @@ class CategoryListView( def get(self, request: Request, *args, **kwargs) -> Response: return self.list(request, *args, **kwargs) + +class CategoryPostView( + mixins.CreateModelMixin, + generics.GenericAPIView, +): + permission_classes = [] + queryset: QuerySet = Category.objects.all() + serializer_class: CategorySerializer = CategorySerializer + @extend_schema( tags=["Inventory"], summary="Create a new category.", @@ -94,13 +104,11 @@ def post(self, request: Request, *args, **kwargs) -> Response: return self.create(request, *args, **kwargs) -class CategoryDetailView( +class CategoryIdGetView( mixins.RetrieveModelMixin, - mixins.UpdateModelMixin, - RetireMixin, - mixins.DestroyModelMixin, generics.GenericAPIView, ): + permission_classes = [] queryset: QuerySet = Category.objects.all() serializer_class: CategorySerializer = CategorySerializer @@ -147,6 +155,15 @@ def get(self, request: Request, *args, **kwargs) -> Response: check_url_parameter(kwargs["pk"]) return self.retrieve(request, *args, **kwargs) + +class CategoryIdPutView( + mixins.UpdateModelMixin, + generics.GenericAPIView, +): + permission_classes = [] + queryset: QuerySet = Category.objects.all() + serializer_class: CategorySerializer = CategorySerializer + @extend_schema( tags=["Inventory"], summary="Update an existing category", @@ -197,6 +214,14 @@ def put(self, request: Request, *args, **kwargs) -> Response: check_url_parameter(kwargs["pk"]) return self.update(request, *args, **kwargs) + +class CategoryIdDeleteView( + RetireMixin, + generics.GenericAPIView, +): + permission_classes = [] + queryset: QuerySet = Category.objects.all() + serializer_class: CategorySerializer = CategorySerializer @extend_schema( tags=["Inventory"], summary="Retire an existing category", @@ -242,10 +267,11 @@ def delete(self, request: Request, *args, **kwargs) -> Response: return self.retire(request, *args, **kwargs) -class CategoryProductListView( +class CategoryProductGetView( CategoryProductListMixin, generics.GenericAPIView, ): + permission_classes = [] queryset: QuerySet = Category.objects.all() serializer_class: CategorySerializer = CategorySerializer @@ -291,3 +317,27 @@ class CategoryProductListView( def get(self, request: Request, **kwargs) -> Response: check_url_parameter(kwargs["pk"]) return self.list(**kwargs) + + +# classes grouping all the requests for one endpoint + +class CategoryView( + CategoryGetView, + CategoryPostView +): + pass + + +class CategoryIdView( + CategoryIdGetView, + CategoryIdPutView, + CategoryIdDeleteView +): + pass + + +class CategoryProductView( + CategoryProductGetView +): + pass + diff --git a/poetry.lock b/poetry.lock index 3416eca8..2ea05ab9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -134,7 +134,7 @@ python-versions = "*" [[package]] name = "django" -version = "4.1.7" +version = "4.1.6" description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." category = "main" optional = false @@ -187,6 +187,27 @@ python-versions = ">=3.6" django = ">=3.0" pytz = "*" +[[package]] +name = "djangorestframework-simplejwt" +version = "5.2.2" +description = "A minimal JSON Web Token authentication plugin for Django REST Framework" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +django = "*" +djangorestframework = "*" +pyjwt = ">=1.7.1,<3" + +[package.extras] +crypto = ["cryptography (>=3.3.1)"] +dev = ["Sphinx (>=1.6.5,<2)", "cryptography", "flake8", "ipython", "isort", "pep8", "pytest", "pytest-cov", "pytest-django", "pytest-watch", "pytest-xdist", "python-jose (==3.3.0)", "sphinx-rtd-theme (>=0.1.9)", "tox", "twine", "wheel"] +doc = ["Sphinx (>=1.6.5,<2)", "sphinx-rtd-theme (>=0.1.9)"] +lint = ["flake8", "isort", "pep8"] +python-jose = ["python-jose (==3.3.0)"] +test = ["cryptography", "pytest", "pytest-cov", "pytest-django", "pytest-xdist", "tox"] + [[package]] name = "drf-ignore-slash-middleware" version = "0.0.1" @@ -437,9 +458,23 @@ category = "dev" optional = false python-versions = ">=3.6" +[[package]] +name = "pyjwt" +version = "2.6.0" +description = "JSON Web Token implementation in Python" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +crypto = ["cryptography (>=3.4.0)"] +dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] +docs = ["sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] +tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] + [[package]] name = "pyright" -version = "1.1.294" +version = "1.1.293" description = "Command line wrapper for pyright" category = "dev" optional = false @@ -541,7 +576,7 @@ python-versions = ">=3.5" [[package]] name = "setuptools" -version = "67.3.2" +version = "67.2.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" category = "main" optional = false @@ -607,7 +642,7 @@ python-versions = ">=3.7" [[package]] name = "typing-extensions" -version = "4.5.0" +version = "4.4.0" description = "Backported and Experimental Type Hints for Python 3.7+" category = "main" optional = false @@ -661,7 +696,7 @@ python-versions = "*" [metadata] lock-version = "1.1" python-versions = "^3.10" -content-hash = "913c9062006d9be5e07954128bf6dc09516d215a4c4587e5d22a3db993d74dda" +content-hash = "aa1da511d057c24ebdaf8bcc3d0385da6b92bbcc1f9dbe33f56be9e2ebdcdcd7" [metadata.files] asgiref = [ @@ -856,8 +891,8 @@ ddt = [ {file = "ddt-1.6.0.tar.gz", hash = "sha256:f71b348731b8c78c3100bffbd951a769fbd439088d1fdbb3841eee019af80acd"}, ] django = [ - {file = "Django-4.1.7-py3-none-any.whl", hash = "sha256:f2f431e75adc40039ace496ad3b9f17227022e8b11566f4b363da44c7e44761e"}, - {file = "Django-4.1.7.tar.gz", hash = "sha256:44f714b81c5f190d9d2ddad01a532fe502fa01c4cb8faf1d081f4264ed15dcd8"}, + {file = "Django-4.1.6-py3-none-any.whl", hash = "sha256:c6fe7ebe7c017fe59f1029821dae0acb5a2ddcd6c9a0138fd20a8bfefac914bc"}, + {file = "Django-4.1.6.tar.gz", hash = "sha256:bceb0fe1a386781af0788cae4108622756cd05e7775448deec04a71ddf87685d"}, ] django-cors-headers = [ {file = "django-cors-headers-3.13.0.tar.gz", hash = "sha256:f9dc6b4e3f611c3199700b3e5f3398c28757dcd559c2f82932687f3d0443cfdf"}, @@ -868,6 +903,10 @@ djangorestframework = [ {file = "djangorestframework-3.14.0-py3-none-any.whl", hash = "sha256:eb63f58c9f218e1a7d064d17a70751f528ed4e1d35547fdade9aaf4cd103fd08"}, {file = "djangorestframework-3.14.0.tar.gz", hash = "sha256:579a333e6256b09489cbe0a067e66abe55c6595d8926be6b99423786334350c8"}, ] +djangorestframework-simplejwt = [ + {file = "djangorestframework_simplejwt-5.2.2-py3-none-any.whl", hash = "sha256:4c0d2e2513e12587d93501ac091781684a216c3ee614eb3b5a10586aef5ca845"}, + {file = "djangorestframework_simplejwt-5.2.2.tar.gz", hash = "sha256:d27d4bcac2c6394f678dea8b4d0d511c6e18a7f2eb8aaeeb8a7de601aeb77c42"}, +] drf-ignore-slash-middleware = [ {file = "drf_ignore_slash_middleware-0.0.1-py3-none-any.whl", hash = "sha256:9c17b6c2ce16685455df4be0fe7d34c5f6fa4c61ddb4660ca5f253ea08914469"}, {file = "drf_ignore_slash_middleware-0.0.1.tar.gz", hash = "sha256:3ed51accc471b44c8e7986e3667669f74e7fa303a516f774d7931d069c1d6f0d"}, @@ -1005,9 +1044,13 @@ pycodestyle = [ {file = "pycodestyle-2.10.0-py2.py3-none-any.whl", hash = "sha256:8a4eaf0d0495c7395bdab3589ac2db602797d76207242c17d470186815706610"}, {file = "pycodestyle-2.10.0.tar.gz", hash = "sha256:347187bdb476329d98f695c213d7295a846d1152ff4fe9bacb8a9590b8ee7053"}, ] +pyjwt = [ + {file = "PyJWT-2.6.0-py3-none-any.whl", hash = "sha256:d83c3d892a77bbb74d3e1a2cfa90afaadb60945205d1095d9221f04466f64c14"}, + {file = "PyJWT-2.6.0.tar.gz", hash = "sha256:69285c7e31fc44f68a1feb309e948e0df53259d579295e6cfe2b1792329f05fd"}, +] pyright = [ - {file = "pyright-1.1.294-py3-none-any.whl", hash = "sha256:5b27e28a1cfc60cea707fd3b644769fa6dd0b194481cdcc2399cf2a51cc5a846"}, - {file = "pyright-1.1.294.tar.gz", hash = "sha256:fea5fed3d6a3f02259e622c901e86a7b8bcf237d35e1cdfe01d0e0723768dcb6"}, + {file = "pyright-1.1.293-py3-none-any.whl", hash = "sha256:afc05309e775a9869c864da4e8c0c7a3e3be9d8fe202e780c3bae981bbb13936"}, + {file = "pyright-1.1.293.tar.gz", hash = "sha256:9397fdfcbc684fe5b87abbf9c27f540fe3b8d75999a5f187519cae1d065be38c"}, ] pyrsistent = [ {file = "pyrsistent-0.19.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:20460ac0ea439a3e79caa1dbd560344b64ed75e85d8703943e0b66c2a6150e4a"}, @@ -1136,8 +1179,8 @@ ruamel-yaml-clib = [ {file = "ruamel.yaml.clib-0.2.7.tar.gz", hash = "sha256:1f08fd5a2bea9c4180db71678e850b995d2a5f4537be0e94557668cf0f5f9497"}, ] setuptools = [ - {file = "setuptools-67.3.2-py3-none-any.whl", hash = "sha256:bb6d8e508de562768f2027902929f8523932fcd1fb784e6d573d2cafac995a48"}, - {file = "setuptools-67.3.2.tar.gz", hash = "sha256:95f00380ef2ffa41d9bba85d95b27689d923c93dfbafed4aecd7cf988a25e012"}, + {file = "setuptools-67.2.0-py3-none-any.whl", hash = "sha256:16ccf598aab3b506593c17378473978908a2734d7336755a8769b480906bec1c"}, + {file = "setuptools-67.2.0.tar.gz", hash = "sha256:b440ee5f7e607bb8c9de15259dba2583dd41a38879a7abc1d43a71c59524da48"}, ] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, @@ -1160,8 +1203,8 @@ tomli = [ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] typing-extensions = [ - {file = "typing_extensions-4.5.0-py3-none-any.whl", hash = "sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4"}, - {file = "typing_extensions-4.5.0.tar.gz", hash = "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb"}, + {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, + {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, ] tzdata = [ {file = "tzdata-2022.7-py2.py3-none-any.whl", hash = "sha256:2b88858b0e3120792a3c0635c23daf36a7d7eeeca657c323da299d2094402a0d"}, diff --git a/pyproject.toml b/pyproject.toml index 019a95ef..f182b13a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,6 +24,7 @@ drf-spectacular = "^0.25.1" gunicorn = "^20.1.0" generics = "^6.0.0" drf-ignore-slash-middleware = "^0.0.1" +djangorestframework-simplejwt = "^5.2.2" [tool.poetry.group.dev.dependencies] black = "^22.10.0" From 33dea30b7fc31848cfe3db56b84b0c9f0025fe02 Mon Sep 17 00:00:00 2001 From: michael-osullivan Date: Thu, 16 Feb 2023 15:36:03 +0000 Subject: [PATCH 03/14] order permissions --- answerking/settings/base.py | 42 ++++--------------- answerking_app/models/models.py | 4 +- .../models/permissions/auth_permissions.py | 22 +++++++--- answerking_app/models/serializers.py | 40 +++++++++--------- answerking_app/urls/auth_urls.py | 14 ++----- answerking_app/urls/order_urls.py | 2 +- answerking_app/views/auth_views.py | 6 ++- answerking_app/views/category_views.py | 21 ++++------ answerking_app/views/order_views.py | 30 ++++++++++--- answerking_app/views/product_views.py | 4 ++ poetry.lock | 24 +++++------ 11 files changed, 103 insertions(+), 106 deletions(-) diff --git a/answerking/settings/base.py b/answerking/settings/base.py index 509c8461..d5f2528b 100644 --- a/answerking/settings/base.py +++ b/answerking/settings/base.py @@ -40,7 +40,7 @@ "rest_framework", "corsheaders", "drf_problems", - 'rest_framework_simplejwt.token_blacklist', + "rest_framework_simplejwt.token_blacklist", ] MIDDLEWARE = [ @@ -83,8 +83,8 @@ "EXCEPTION_HANDLER": "answerking_app.utils.exceptions_handler.wrapper", "COERCE_DECIMAL_TO_STRING": False, "DATETIME_FORMAT": "%Y-%m-%dT%H:%M:%S.%fZ", - 'DEFAULT_AUTHENTICATION_CLASSES': ( - 'rest_framework_simplejwt.authentication.JWTAuthentication', + "DEFAULT_AUTHENTICATION_CLASSES": ( + "rest_framework_simplejwt.authentication.JWTAuthentication", ), } @@ -126,38 +126,12 @@ # https://docs.djangoproject.com/en/4.0/topics/i18n/ SIMPLE_JWT = { - 'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5), - 'REFRESH_TOKEN_LIFETIME': timedelta(days=50), - 'ROTATE_REFRESH_TOKENS': True, - 'BLACKLIST_AFTER_ROTATION': True, - 'UPDATE_LAST_LOGIN': False, - - 'ALGORITHM': 'HS256', - - 'VERIFYING_KEY': None, - 'AUDIENCE': None, - 'ISSUER': None, - 'JWK_URL': None, - 'LEEWAY': 0, - - 'AUTH_HEADER_TYPES': ('Bearer',), - 'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION', - 'USER_ID_FIELD': 'id', - 'USER_ID_CLAIM': 'user_id', - 'USER_AUTHENTICATION_RULE': 'rest_framework_simplejwt.authentication' - '.default_user_authentication_rule', - - 'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',), - 'TOKEN_TYPE_CLAIM': 'token_type', - 'TOKEN_USER_CLASS': 'rest_framework_simplejwt.models.TokenUser', - - 'JTI_CLAIM': 'jti', - - 'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp', - 'SLIDING_TOKEN_LIFETIME': timedelta(minutes=5), - 'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=1), + "ACCESS_TOKEN_LIFETIME": timedelta(minutes=120), + "REFRESH_TOKEN_LIFETIME": timedelta(days=50), + "ROTATE_REFRESH_TOKENS": True, + "BLACKLIST_AFTER_ROTATION": True, + "UPDATE_LAST_LOGIN": True, } - LANGUAGE_CODE = "en-us" TIME_ZONE = "UTC" diff --git a/answerking_app/models/models.py b/answerking_app/models/models.py index b6abf8c2..2931cfe8 100644 --- a/answerking_app/models/models.py +++ b/answerking_app/models/models.py @@ -46,6 +46,9 @@ class Status(models.TextChoices): ) created_on = models.DateTimeField(auto_now_add=True) last_updated = models.DateTimeField(auto_now=True) + owner = models.ForeignKey( + "auth.User", related_name="orders", on_delete=models.CASCADE + ) def calculate_total(self): total = Decimal(0.00) @@ -73,4 +76,3 @@ def calculate_sub_total(self): class Meta: unique_together = [["order", "product"]] - diff --git a/answerking_app/models/permissions/auth_permissions.py b/answerking_app/models/permissions/auth_permissions.py index 03281bd6..23b3c5c7 100644 --- a/answerking_app/models/permissions/auth_permissions.py +++ b/answerking_app/models/permissions/auth_permissions.py @@ -1,20 +1,30 @@ from rest_framework.permissions import IsAdminUser, BasePermission -class IsStaffUser(IsAdminUser): +class IsManagerUser(IsAdminUser): """ Allows access to Staff and Managers only. """ + pass -class IsManagerUser(BasePermission): +class IsStaffUser(BasePermission): """ Allows access to Managers only. """ def has_permission(self, request, view): - return bool( - request.user - and request.user.is_staff - ) + return bool(request.user and request.user.is_staff) + + +class IsOwner(BasePermission): + """ + Custom permission to only allow owners of an object to edit it. + """ + + def has_permission(self, request, view): + return bool(request.user and request.user.is_authenticated) + + def has_object_permission(self, request, view, obj): + return bool(obj.owner == request.user) diff --git a/answerking_app/models/serializers.py b/answerking_app/models/serializers.py index 96db0c60..39c5a69e 100644 --- a/answerking_app/models/serializers.py +++ b/answerking_app/models/serializers.py @@ -252,9 +252,10 @@ class OrderSerializer(serializers.ModelSerializer): lineItems = LineItemSerializer( source="lineitem_set", many=True, required=False ) + owner = serializers.ReadOnlyField(source="owner.username") def create(self, validated_data: dict) -> Order: - order: Order = Order.objects.create() + order: Order = Order.objects.create(owner=validated_data["owner"]) if "lineitem_set" in validated_data: line_items_data = validated_data["lineitem_set"] self.create_order_line_items( @@ -304,14 +305,15 @@ def validate_lineItems(self, line_items_data): class Meta: model = Order - fields = ( + fields = [ "id", "createdOn", "lastUpdated", "orderStatus", "orderTotal", "lineItems", - ) + "owner", + ] depth = 3 @@ -327,9 +329,7 @@ class ManagerAuthSerializer(serializers.ModelSerializer): validators=[UniqueValidator(queryset=User.objects.all())], ) password = serializers.CharField( - write_only=True, - required=True, - validators=[validate_password] + write_only=True, required=True, validators=[validate_password] ) password2 = serializers.CharField( write_only=True, @@ -339,16 +339,16 @@ class ManagerAuthSerializer(serializers.ModelSerializer): class Meta: model = User fields = ( - 'username', - 'password', - 'password2', - 'email', - 'first_name', - 'last_name', + "username", + "password", + "password2", + "email", + "first_name", + "last_name", ) def validate(self, attrs): - if attrs['password'] != attrs['password2']: + if attrs["password"] != attrs["password2"]: raise ProblemDetails( status=status.HTTP_400_BAD_REQUEST, detail="The passwords supplied do not match", @@ -363,15 +363,15 @@ def validate_last_name(self, value: str) -> str: def create(self, validated_data): user = User.objects.create( - username=validated_data['username'], - email=validated_data['email'], - first_name=validated_data['first_name'], - last_name=validated_data['last_name'], + username=validated_data["username"], + email=validated_data["email"], + first_name=validated_data["first_name"], + last_name=validated_data["last_name"], is_staff=True, is_superuser=False, ) - user.set_password(validated_data['password']) + user.set_password(validated_data["password"]) user.save() return user @@ -380,8 +380,8 @@ class LoginSerializer(TokenObtainPairSerializer): @classmethod def get_token(cls, user): token = super().get_token(user) - token['username'] = user.username - token['email'] = user.email + token["username"] = user.username + token["email"] = user.email return token def validate(self, attrs): diff --git a/answerking_app/urls/auth_urls.py b/answerking_app/urls/auth_urls.py index 1a33d667..03139575 100644 --- a/answerking_app/urls/auth_urls.py +++ b/answerking_app/urls/auth_urls.py @@ -9,16 +9,8 @@ path( "register/manager", auth_views.RegisterManagerView.as_view(), - name="register_manager" + name="register_manager", ), - path( - "login", - auth_views.LoginView.as_view(), - name="token_obtain_pair" - ), - path( - 'login/refresh/', - TokenRefreshView.as_view(), - name='token_refresh' - ) + path("login", auth_views.LoginView.as_view(), name="token_obtain_pair"), + path("login/refresh/", TokenRefreshView.as_view(), name="token_refresh"), ] diff --git a/answerking_app/urls/order_urls.py b/answerking_app/urls/order_urls.py index 5a09dce2..c47c2158 100644 --- a/answerking_app/urls/order_urls.py +++ b/answerking_app/urls/order_urls.py @@ -5,7 +5,7 @@ from answerking_app.views import order_views urlpatterns: list[partial] = [ - path("orders", order_views.OrderListView.as_view(), name="order_list"), + path("orders", order_views.OrderView.as_view(), name="order_list"), path( "orders/", order_views.OrderDetailView.as_view(), diff --git a/answerking_app/views/auth_views.py b/answerking_app/views/auth_views.py index 039e486f..425c1e58 100644 --- a/answerking_app/views/auth_views.py +++ b/answerking_app/views/auth_views.py @@ -3,8 +3,10 @@ from rest_framework_simplejwt.views import TokenObtainPairView from answerking_app.models.permissions.auth_permissions import IsManagerUser -from answerking_app.models.serializers import ManagerAuthSerializer, \ - LoginSerializer +from answerking_app.models.serializers import ( + ManagerAuthSerializer, + LoginSerializer, +) class RegisterManagerView( diff --git a/answerking_app/views/category_views.py b/answerking_app/views/category_views.py index e4aef5e0..81b6e5b3 100644 --- a/answerking_app/views/category_views.py +++ b/answerking_app/views/category_views.py @@ -30,6 +30,7 @@ # classes for each endpoint and type of request + class CategoryGetView( mixins.ListModelMixin, generics.GenericAPIView, @@ -157,8 +158,8 @@ def get(self, request: Request, *args, **kwargs) -> Response: class CategoryIdPutView( - mixins.UpdateModelMixin, - generics.GenericAPIView, + mixins.UpdateModelMixin, + generics.GenericAPIView, ): permission_classes = [] queryset: QuerySet = Category.objects.all() @@ -222,6 +223,7 @@ class CategoryIdDeleteView( permission_classes = [] queryset: QuerySet = Category.objects.all() serializer_class: CategorySerializer = CategorySerializer + @extend_schema( tags=["Inventory"], summary="Retire an existing category", @@ -321,23 +323,16 @@ def get(self, request: Request, **kwargs) -> Response: # classes grouping all the requests for one endpoint -class CategoryView( - CategoryGetView, - CategoryPostView -): + +class CategoryView(CategoryGetView, CategoryPostView): pass class CategoryIdView( - CategoryIdGetView, - CategoryIdPutView, - CategoryIdDeleteView + CategoryIdGetView, CategoryIdPutView, CategoryIdDeleteView ): pass -class CategoryProductView( - CategoryProductGetView -): +class CategoryProductView(CategoryProductGetView): pass - diff --git a/answerking_app/views/order_views.py b/answerking_app/views/order_views.py index f5d307ac..715fdfc8 100644 --- a/answerking_app/views/order_views.py +++ b/answerking_app/views/order_views.py @@ -1,11 +1,16 @@ from typing import Literal from django.db.models import QuerySet -from rest_framework import generics, mixins +from rest_framework import generics, mixins, status +from rest_framework.permissions import IsAuthenticated from rest_framework.request import Request from rest_framework.response import Response from answerking_app.models.models import Order +from answerking_app.models.permissions.auth_permissions import ( + IsOwner, + IsStaffUser, +) from answerking_app.models.serializers import ( OrderSerializer, ProblemDetailSerializer, @@ -26,13 +31,12 @@ ) -class OrderListView( - mixins.ListModelMixin, - mixins.CreateModelMixin, - generics.GenericAPIView, -): +class OrderListView(mixins.ListModelMixin, generics.GenericAPIView): queryset: QuerySet = Order.objects.all() serializer_class: OrderSerializer = OrderSerializer + permission_classes = [ + IsStaffUser, + ] @extend_schema( tags=["Orders"], @@ -50,6 +54,12 @@ class OrderListView( def get(self, request: Request, *args, **kwargs) -> Response: return self.list(request, *args, **kwargs) + +class OrderPostView(mixins.CreateModelMixin, generics.GenericAPIView): + queryset: QuerySet = Order.objects.all() + serializer_class: OrderSerializer = OrderSerializer + permission_classes = [IsAuthenticated] + @extend_schema( tags=["Orders"], summary="Create a new order.", @@ -86,6 +96,9 @@ def get(self, request: Request, *args, **kwargs) -> Response: def post(self, request: Request, *args, **kwargs) -> Response: return self.create(request) + def perform_create(self, serializer): + serializer.save(owner=self.request.user) + class OrderDetailView( mixins.RetrieveModelMixin, @@ -97,6 +110,7 @@ class OrderDetailView( queryset: QuerySet = Order.objects.all() serializer_class: OrderSerializer = OrderSerializer lookup_url_kwarg: Literal["pk"] = "pk" + permission_classes = [IsOwner] @extend_schema( tags=["Orders"], @@ -212,3 +226,7 @@ def put(self, request: Request, *args, **kwargs) -> Response: def delete(self, request: Request, *args, **kwargs) -> Response: check_url_parameter(kwargs["pk"]) return self.cancel_order(request, *args, **kwargs) + + +class OrderView(OrderListView, OrderPostView): + pass diff --git a/answerking_app/views/product_views.py b/answerking_app/views/product_views.py index cea612ee..6ddd21eb 100644 --- a/answerking_app/views/product_views.py +++ b/answerking_app/views/product_views.py @@ -5,6 +5,8 @@ extend_schema, ) from rest_framework import generics, mixins +from rest_framework.authentication import TokenAuthentication +from rest_framework.permissions import IsAuthenticatedOrReadOnly from rest_framework.request import Request from rest_framework.response import Response @@ -22,6 +24,7 @@ retired_product_example, ) from answerking_app.utils.url_parameter_check import check_url_parameter +from answerking_app.models.permissions.auth_permissions import IsStaffUser class ProductListView( @@ -32,6 +35,7 @@ class ProductListView( queryset: QuerySet = Product.objects.all() serializer_class: ProductSerializer = ProductSerializer + permission_classes = [IsStaffUser] @extend_schema( tags=["Inventory"], diff --git a/poetry.lock b/poetry.lock index 2ea05ab9..facc4464 100644 --- a/poetry.lock +++ b/poetry.lock @@ -134,7 +134,7 @@ python-versions = "*" [[package]] name = "django" -version = "4.1.6" +version = "4.1.7" description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." category = "main" optional = false @@ -474,7 +474,7 @@ tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] [[package]] name = "pyright" -version = "1.1.293" +version = "1.1.294" description = "Command line wrapper for pyright" category = "dev" optional = false @@ -576,7 +576,7 @@ python-versions = ">=3.5" [[package]] name = "setuptools" -version = "67.2.0" +version = "67.3.2" description = "Easily download, build, install, upgrade, and uninstall Python packages" category = "main" optional = false @@ -642,7 +642,7 @@ python-versions = ">=3.7" [[package]] name = "typing-extensions" -version = "4.4.0" +version = "4.5.0" description = "Backported and Experimental Type Hints for Python 3.7+" category = "main" optional = false @@ -891,8 +891,8 @@ ddt = [ {file = "ddt-1.6.0.tar.gz", hash = "sha256:f71b348731b8c78c3100bffbd951a769fbd439088d1fdbb3841eee019af80acd"}, ] django = [ - {file = "Django-4.1.6-py3-none-any.whl", hash = "sha256:c6fe7ebe7c017fe59f1029821dae0acb5a2ddcd6c9a0138fd20a8bfefac914bc"}, - {file = "Django-4.1.6.tar.gz", hash = "sha256:bceb0fe1a386781af0788cae4108622756cd05e7775448deec04a71ddf87685d"}, + {file = "Django-4.1.7-py3-none-any.whl", hash = "sha256:f2f431e75adc40039ace496ad3b9f17227022e8b11566f4b363da44c7e44761e"}, + {file = "Django-4.1.7.tar.gz", hash = "sha256:44f714b81c5f190d9d2ddad01a532fe502fa01c4cb8faf1d081f4264ed15dcd8"}, ] django-cors-headers = [ {file = "django-cors-headers-3.13.0.tar.gz", hash = "sha256:f9dc6b4e3f611c3199700b3e5f3398c28757dcd559c2f82932687f3d0443cfdf"}, @@ -1049,8 +1049,8 @@ pyjwt = [ {file = "PyJWT-2.6.0.tar.gz", hash = "sha256:69285c7e31fc44f68a1feb309e948e0df53259d579295e6cfe2b1792329f05fd"}, ] pyright = [ - {file = "pyright-1.1.293-py3-none-any.whl", hash = "sha256:afc05309e775a9869c864da4e8c0c7a3e3be9d8fe202e780c3bae981bbb13936"}, - {file = "pyright-1.1.293.tar.gz", hash = "sha256:9397fdfcbc684fe5b87abbf9c27f540fe3b8d75999a5f187519cae1d065be38c"}, + {file = "pyright-1.1.294-py3-none-any.whl", hash = "sha256:5b27e28a1cfc60cea707fd3b644769fa6dd0b194481cdcc2399cf2a51cc5a846"}, + {file = "pyright-1.1.294.tar.gz", hash = "sha256:fea5fed3d6a3f02259e622c901e86a7b8bcf237d35e1cdfe01d0e0723768dcb6"}, ] pyrsistent = [ {file = "pyrsistent-0.19.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:20460ac0ea439a3e79caa1dbd560344b64ed75e85d8703943e0b66c2a6150e4a"}, @@ -1179,8 +1179,8 @@ ruamel-yaml-clib = [ {file = "ruamel.yaml.clib-0.2.7.tar.gz", hash = "sha256:1f08fd5a2bea9c4180db71678e850b995d2a5f4537be0e94557668cf0f5f9497"}, ] setuptools = [ - {file = "setuptools-67.2.0-py3-none-any.whl", hash = "sha256:16ccf598aab3b506593c17378473978908a2734d7336755a8769b480906bec1c"}, - {file = "setuptools-67.2.0.tar.gz", hash = "sha256:b440ee5f7e607bb8c9de15259dba2583dd41a38879a7abc1d43a71c59524da48"}, + {file = "setuptools-67.3.2-py3-none-any.whl", hash = "sha256:bb6d8e508de562768f2027902929f8523932fcd1fb784e6d573d2cafac995a48"}, + {file = "setuptools-67.3.2.tar.gz", hash = "sha256:95f00380ef2ffa41d9bba85d95b27689d923c93dfbafed4aecd7cf988a25e012"}, ] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, @@ -1203,8 +1203,8 @@ tomli = [ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] typing-extensions = [ - {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, - {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, + {file = "typing_extensions-4.5.0-py3-none-any.whl", hash = "sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4"}, + {file = "typing_extensions-4.5.0.tar.gz", hash = "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb"}, ] tzdata = [ {file = "tzdata-2022.7-py2.py3-none-any.whl", hash = "sha256:2b88858b0e3120792a3c0635c23daf36a7d7eeeca657c323da299d2094402a0d"}, From af7b95480931028eb8ba60eeef18fd21fb5068aa Mon Sep 17 00:00:00 2001 From: michael-osullivan Date: Thu, 16 Feb 2023 16:07:38 +0000 Subject: [PATCH 04/14] product permissions --- .../models/permissions/auth_permissions.py | 6 +-- answerking_app/urls/product_urls.py | 4 +- answerking_app/views/product_views.py | 40 ++++++++++++++----- 3 files changed, 36 insertions(+), 14 deletions(-) diff --git a/answerking_app/models/permissions/auth_permissions.py b/answerking_app/models/permissions/auth_permissions.py index 23b3c5c7..6461a04b 100644 --- a/answerking_app/models/permissions/auth_permissions.py +++ b/answerking_app/models/permissions/auth_permissions.py @@ -1,7 +1,7 @@ from rest_framework.permissions import IsAdminUser, BasePermission -class IsManagerUser(IsAdminUser): +class IsStaffUser(IsAdminUser): """ Allows access to Staff and Managers only. """ @@ -9,9 +9,9 @@ class IsManagerUser(IsAdminUser): pass -class IsStaffUser(BasePermission): +class IsManagerUser(BasePermission): """ - Allows access to Managers only. + Allows access to Manager's only. """ def has_permission(self, request, view): diff --git a/answerking_app/urls/product_urls.py b/answerking_app/urls/product_urls.py index 7920b4b8..8ad41ce7 100644 --- a/answerking_app/urls/product_urls.py +++ b/answerking_app/urls/product_urls.py @@ -7,12 +7,12 @@ urlpatterns: list[partial] = [ path( "products", - product_views.ProductListView.as_view(), + product_views.ProductView.as_view(), name="product_list", ), path( "products/", - product_views.ProductDetailView.as_view(), + product_views.ProductIdView.as_view(), name="product_detail", ), ] diff --git a/answerking_app/views/product_views.py b/answerking_app/views/product_views.py index 6ddd21eb..a818984a 100644 --- a/answerking_app/views/product_views.py +++ b/answerking_app/views/product_views.py @@ -5,8 +5,7 @@ extend_schema, ) from rest_framework import generics, mixins -from rest_framework.authentication import TokenAuthentication -from rest_framework.permissions import IsAuthenticatedOrReadOnly +from rest_framework.permissions import AllowAny from rest_framework.request import Request from rest_framework.response import Response @@ -21,7 +20,6 @@ product_body_example, product_categories_body_example, product_example, - retired_product_example, ) from answerking_app.utils.url_parameter_check import check_url_parameter from answerking_app.models.permissions.auth_permissions import IsStaffUser @@ -32,10 +30,9 @@ class ProductListView( mixins.CreateModelMixin, generics.GenericAPIView, ): - queryset: QuerySet = Product.objects.all() serializer_class: ProductSerializer = ProductSerializer - permission_classes = [IsStaffUser] + permission_classes = [AllowAny] @extend_schema( tags=["Inventory"], @@ -57,6 +54,15 @@ class ProductListView( def get(self, request: Request, *args, **kwargs) -> Response: return self.list(request, *args, **kwargs) + +class ProductPostView( + mixins.CreateModelMixin, + generics.GenericAPIView, +): + queryset: QuerySet = Product.objects.all() + serializer_class: ProductSerializer = ProductSerializer + permission_classes = [IsStaffUser] + @extend_schema( tags=["Inventory"], summary="Create a new product.", @@ -94,13 +100,12 @@ def post(self, request: Request, *args, **kwargs) -> Response: return self.create(request, *args, **kwargs) -class ProductDetailView( - RetireMixin, - generics.UpdateAPIView, - mixins.RetrieveModelMixin, +class ProductIdGetView( + mixins.RetrieveModelMixin ): queryset: QuerySet = Product.objects.all() serializer_class: ProductSerializer = ProductSerializer + permission_classes = [AllowAny] @extend_schema( tags=["Inventory"], @@ -145,6 +150,15 @@ def get(self, request: Request, *args, **kwargs) -> Response: check_url_parameter(kwargs["pk"]) return self.retrieve(request, *args, **kwargs) + +class ProductUpdateRetireView( + RetireMixin, + generics.UpdateAPIView, +): + queryset: QuerySet = Product.objects.all() + serializer_class: ProductSerializer = ProductSerializer + permission_classes = [IsStaffUser] + @extend_schema( tags=["Inventory"], summary="Update an existing product", @@ -238,3 +252,11 @@ def put(self, request: Request, *args, **kwargs) -> Response: def delete(self, request: Request, *args, **kwargs) -> Response: check_url_parameter(kwargs["pk"]) return self.retire(request, *args, **kwargs) + + +class ProductView(ProductListView, ProductPostView): + pass + + +class ProductIdView(ProductIdGetView, ProductUpdateRetireView): + pass From d9dac784a158dc7b3d70410fdd199d9e3c08660d Mon Sep 17 00:00:00 2001 From: michael-osullivan Date: Thu, 16 Feb 2023 16:08:07 +0000 Subject: [PATCH 05/14] lint --- answerking_app/views/product_views.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/answerking_app/views/product_views.py b/answerking_app/views/product_views.py index a818984a..59d954d2 100644 --- a/answerking_app/views/product_views.py +++ b/answerking_app/views/product_views.py @@ -100,9 +100,7 @@ def post(self, request: Request, *args, **kwargs) -> Response: return self.create(request, *args, **kwargs) -class ProductIdGetView( - mixins.RetrieveModelMixin -): +class ProductIdGetView(mixins.RetrieveModelMixin): queryset: QuerySet = Product.objects.all() serializer_class: ProductSerializer = ProductSerializer permission_classes = [AllowAny] From eaf3fd53e96e9e7ccee0daf7608e5fe143c755a4 Mon Sep 17 00:00:00 2001 From: michael-osullivan Date: Fri, 17 Feb 2023 15:44:42 +0000 Subject: [PATCH 06/14] refactor views --- answerking/settings/base.py | 2 +- answerking_app/urls/auth_urls.py | 5 +-- answerking_app/views/auth_views.py | 1 - answerking_app/views/order_views.py | 46 +++++++++++++++++++++------ answerking_app/views/product_views.py | 27 ++++++++++------ 5 files changed, 58 insertions(+), 23 deletions(-) diff --git a/answerking/settings/base.py b/answerking/settings/base.py index d5f2528b..6a7943bf 100644 --- a/answerking/settings/base.py +++ b/answerking/settings/base.py @@ -126,7 +126,7 @@ # https://docs.djangoproject.com/en/4.0/topics/i18n/ SIMPLE_JWT = { - "ACCESS_TOKEN_LIFETIME": timedelta(minutes=120), + "ACCESS_TOKEN_LIFETIME": timedelta(seconds=30), "REFRESH_TOKEN_LIFETIME": timedelta(days=50), "ROTATE_REFRESH_TOKENS": True, "BLACKLIST_AFTER_ROTATION": True, diff --git a/answerking_app/urls/auth_urls.py b/answerking_app/urls/auth_urls.py index 03139575..51948ed7 100644 --- a/answerking_app/urls/auth_urls.py +++ b/answerking_app/urls/auth_urls.py @@ -1,7 +1,7 @@ from functools import partial from django.urls import path -from rest_framework_simplejwt.views import TokenRefreshView +from rest_framework_simplejwt.views import TokenRefreshView, TokenBlacklistView from answerking_app.views import auth_views @@ -12,5 +12,6 @@ name="register_manager", ), path("login", auth_views.LoginView.as_view(), name="token_obtain_pair"), - path("login/refresh/", TokenRefreshView.as_view(), name="token_refresh"), + path("login/refresh", TokenRefreshView.as_view(), name="token_refresh"), + path("logout", TokenBlacklistView.as_view(), name="token_blacklist"), ] diff --git a/answerking_app/views/auth_views.py b/answerking_app/views/auth_views.py index 425c1e58..d280293f 100644 --- a/answerking_app/views/auth_views.py +++ b/answerking_app/views/auth_views.py @@ -2,7 +2,6 @@ from rest_framework.permissions import AllowAny from rest_framework_simplejwt.views import TokenObtainPairView -from answerking_app.models.permissions.auth_permissions import IsManagerUser from answerking_app.models.serializers import ( ManagerAuthSerializer, LoginSerializer, diff --git a/answerking_app/views/order_views.py b/answerking_app/views/order_views.py index 715fdfc8..89bfc4ff 100644 --- a/answerking_app/views/order_views.py +++ b/answerking_app/views/order_views.py @@ -1,7 +1,7 @@ from typing import Literal from django.db.models import QuerySet -from rest_framework import generics, mixins, status +from rest_framework import generics, mixins from rest_framework.permissions import IsAuthenticated from rest_framework.request import Request from rest_framework.response import Response @@ -34,9 +34,7 @@ class OrderListView(mixins.ListModelMixin, generics.GenericAPIView): queryset: QuerySet = Order.objects.all() serializer_class: OrderSerializer = OrderSerializer - permission_classes = [ - IsStaffUser, - ] + permission_classes = [IsStaffUser] @extend_schema( tags=["Orders"], @@ -55,7 +53,7 @@ def get(self, request: Request, *args, **kwargs) -> Response: return self.list(request, *args, **kwargs) -class OrderPostView(mixins.CreateModelMixin, generics.GenericAPIView): +class OrderCreateView(mixins.CreateModelMixin, generics.GenericAPIView): queryset: QuerySet = Order.objects.all() serializer_class: OrderSerializer = OrderSerializer permission_classes = [IsAuthenticated] @@ -100,13 +98,10 @@ def perform_create(self, serializer): serializer.save(owner=self.request.user) -class OrderDetailView( +class OrderRetrieveView( mixins.RetrieveModelMixin, - mixins.UpdateModelMixin, - CancelOrderMixin, generics.GenericAPIView, ): - queryset: QuerySet = Order.objects.all() serializer_class: OrderSerializer = OrderSerializer lookup_url_kwarg: Literal["pk"] = "pk" @@ -144,6 +139,16 @@ def get(self, request: Request, *args, **kwargs) -> Response: check_url_parameter(kwargs["pk"]) return self.retrieve(request, *args, **kwargs) + +class OrderUpdateView( + mixins.UpdateModelMixin, + generics.GenericAPIView, +): + queryset: QuerySet = Order.objects.all() + serializer_class: OrderSerializer = OrderSerializer + lookup_url_kwarg: Literal["pk"] = "pk" + permission_classes = [IsOwner] + @extend_schema( tags=["Orders"], summary="Update an existing order.", @@ -194,6 +199,16 @@ def put(self, request: Request, *args, **kwargs) -> Response: check_url_parameter(kwargs["pk"]) return self.update(request, *args, **kwargs) + +class OrderCancelView( + CancelOrderMixin, + generics.GenericAPIView, +): + queryset: QuerySet = Order.objects.all() + serializer_class: OrderSerializer = OrderSerializer + lookup_url_kwarg: Literal["pk"] = "pk" + permission_classes = [IsOwner] + @extend_schema( tags=["Orders"], summary="Cancel an existing order", @@ -228,5 +243,16 @@ def delete(self, request: Request, *args, **kwargs) -> Response: return self.cancel_order(request, *args, **kwargs) -class OrderView(OrderListView, OrderPostView): +class OrderView( + OrderListView, + OrderCreateView +): + pass + + +class OrderDetailView( + OrderRetrieveView, + OrderUpdateView, + OrderCancelView +): pass diff --git a/answerking_app/views/product_views.py b/answerking_app/views/product_views.py index 59d954d2..c6d75e10 100644 --- a/answerking_app/views/product_views.py +++ b/answerking_app/views/product_views.py @@ -27,7 +27,6 @@ class ProductListView( mixins.ListModelMixin, - mixins.CreateModelMixin, generics.GenericAPIView, ): queryset: QuerySet = Product.objects.all() @@ -55,7 +54,7 @@ def get(self, request: Request, *args, **kwargs) -> Response: return self.list(request, *args, **kwargs) -class ProductPostView( +class ProductCreateView( mixins.CreateModelMixin, generics.GenericAPIView, ): @@ -100,7 +99,7 @@ def post(self, request: Request, *args, **kwargs) -> Response: return self.create(request, *args, **kwargs) -class ProductIdGetView(mixins.RetrieveModelMixin): +class ProductRetrieveView(mixins.RetrieveModelMixin): queryset: QuerySet = Product.objects.all() serializer_class: ProductSerializer = ProductSerializer permission_classes = [AllowAny] @@ -149,10 +148,7 @@ def get(self, request: Request, *args, **kwargs) -> Response: return self.retrieve(request, *args, **kwargs) -class ProductUpdateRetireView( - RetireMixin, - generics.UpdateAPIView, -): +class ProductUpdateView(generics.UpdateAPIView): queryset: QuerySet = Product.objects.all() serializer_class: ProductSerializer = ProductSerializer permission_classes = [IsStaffUser] @@ -207,6 +203,12 @@ def put(self, request: Request, *args, **kwargs) -> Response: check_url_parameter(kwargs["pk"]) return self.update(request, *args, **kwargs) + +class ProductRetireView(RetireMixin): + queryset: QuerySet = Product.objects.all() + serializer_class: ProductSerializer = ProductSerializer + permission_classes = [IsStaffUser] + @extend_schema( tags=["Inventory"], summary="Retire an existing product", @@ -252,9 +254,16 @@ def delete(self, request: Request, *args, **kwargs) -> Response: return self.retire(request, *args, **kwargs) -class ProductView(ProductListView, ProductPostView): +class ProductView( + ProductListView, + ProductCreateView +): pass -class ProductIdView(ProductIdGetView, ProductUpdateRetireView): +class ProductDetailView( + ProductRetrieveView, + ProductUpdateView, + ProductRetireView +): pass From f2e6b3ff1a29b511c62c952d322c1d39e8cffc37 Mon Sep 17 00:00:00 2001 From: michael-osullivan Date: Fri, 17 Feb 2023 15:46:02 +0000 Subject: [PATCH 07/14] update permissions --- answerking_app/views/order_views.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/answerking_app/views/order_views.py b/answerking_app/views/order_views.py index 89bfc4ff..384b3e0b 100644 --- a/answerking_app/views/order_views.py +++ b/answerking_app/views/order_views.py @@ -105,7 +105,7 @@ class OrderRetrieveView( queryset: QuerySet = Order.objects.all() serializer_class: OrderSerializer = OrderSerializer lookup_url_kwarg: Literal["pk"] = "pk" - permission_classes = [IsOwner] + permission_classes = [IsOwner, IsStaffUser] @extend_schema( tags=["Orders"], @@ -147,7 +147,7 @@ class OrderUpdateView( queryset: QuerySet = Order.objects.all() serializer_class: OrderSerializer = OrderSerializer lookup_url_kwarg: Literal["pk"] = "pk" - permission_classes = [IsOwner] + permission_classes = [IsOwner, IsStaffUser] @extend_schema( tags=["Orders"], @@ -207,7 +207,7 @@ class OrderCancelView( queryset: QuerySet = Order.objects.all() serializer_class: OrderSerializer = OrderSerializer lookup_url_kwarg: Literal["pk"] = "pk" - permission_classes = [IsOwner] + permission_classes = [IsOwner, IsStaffUser] @extend_schema( tags=["Orders"], From 3e35558a0479892c2a01f1820ce319a1bbb191b7 Mon Sep 17 00:00:00 2001 From: MichaelOSullivanAnswer Date: Tue, 21 Feb 2023 09:59:29 +0000 Subject: [PATCH 08/14] update settings --- answerking/settings/base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/answerking/settings/base.py b/answerking/settings/base.py index 6a7943bf..2fc39c44 100644 --- a/answerking/settings/base.py +++ b/answerking/settings/base.py @@ -126,8 +126,8 @@ # https://docs.djangoproject.com/en/4.0/topics/i18n/ SIMPLE_JWT = { - "ACCESS_TOKEN_LIFETIME": timedelta(seconds=30), - "REFRESH_TOKEN_LIFETIME": timedelta(days=50), + "ACCESS_TOKEN_LIFETIME": timedelta(minutes=30), + "REFRESH_TOKEN_LIFETIME": timedelta(days=1), "ROTATE_REFRESH_TOKENS": True, "BLACKLIST_AFTER_ROTATION": True, "UPDATE_LAST_LOGIN": True, From 07b999a332e2239d7ff7b127893915792fa77f4e Mon Sep 17 00:00:00 2001 From: MichaelOSullivanAnswer Date: Fri, 24 Feb 2023 09:27:26 +0000 Subject: [PATCH 09/14] fix urls --- answerking/urls.py | 4 ++-- answerking_app/urls/product_urls.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/answerking/urls.py b/answerking/urls.py index df3af7ea..83e1ff96 100644 --- a/answerking/urls.py +++ b/answerking/urls.py @@ -20,9 +20,9 @@ ) urlpatterns += [ - path("api/schema/", SpectacularAPIView.as_view(), name="schema"), + path("api/schema", SpectacularAPIView.as_view(), name="schema"), path( - "api/schema/swagger-ui/", + "api/schema/swagger-ui", SpectacularSwaggerView.as_view(url_name="schema"), name="swagger-ui", ), diff --git a/answerking_app/urls/product_urls.py b/answerking_app/urls/product_urls.py index 8ad41ce7..d40a995e 100644 --- a/answerking_app/urls/product_urls.py +++ b/answerking_app/urls/product_urls.py @@ -12,7 +12,7 @@ ), path( "products/", - product_views.ProductIdView.as_view(), + product_views.ProductDetailView.as_view(), name="product_detail", ), ] From 945cbf0d5c40b635e6eacfbf0c6f832ac28e76c4 Mon Sep 17 00:00:00 2001 From: MichaelOSullivanAnswer Date: Fri, 24 Feb 2023 09:34:08 +0000 Subject: [PATCH 10/14] update dependencies --- poetry.lock | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/poetry.lock b/poetry.lock index facc4464..f5832d61 100644 --- a/poetry.lock +++ b/poetry.lock @@ -474,7 +474,7 @@ tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] [[package]] name = "pyright" -version = "1.1.294" +version = "1.1.295" description = "Command line wrapper for pyright" category = "dev" optional = false @@ -576,7 +576,7 @@ python-versions = ">=3.5" [[package]] name = "setuptools" -version = "67.3.2" +version = "67.4.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" category = "main" optional = false @@ -696,7 +696,7 @@ python-versions = "*" [metadata] lock-version = "1.1" python-versions = "^3.10" -content-hash = "aa1da511d057c24ebdaf8bcc3d0385da6b92bbcc1f9dbe33f56be9e2ebdcdcd7" +content-hash = "63c0fcd254fd579aa595e4506f831bfc132a24de6011201927e2537e1b055903" [metadata.files] asgiref = [ @@ -1049,8 +1049,8 @@ pyjwt = [ {file = "PyJWT-2.6.0.tar.gz", hash = "sha256:69285c7e31fc44f68a1feb309e948e0df53259d579295e6cfe2b1792329f05fd"}, ] pyright = [ - {file = "pyright-1.1.294-py3-none-any.whl", hash = "sha256:5b27e28a1cfc60cea707fd3b644769fa6dd0b194481cdcc2399cf2a51cc5a846"}, - {file = "pyright-1.1.294.tar.gz", hash = "sha256:fea5fed3d6a3f02259e622c901e86a7b8bcf237d35e1cdfe01d0e0723768dcb6"}, + {file = "pyright-1.1.295-py3-none-any.whl", hash = "sha256:57d6e66381edd38160342abdaea195fc6af16059eabe7e0816f04d42748b2c36"}, + {file = "pyright-1.1.295.tar.gz", hash = "sha256:fa8ef1da35071fe351ee214576665857ceafc96418a4550a48b1f577267d9ac0"}, ] pyrsistent = [ {file = "pyrsistent-0.19.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:20460ac0ea439a3e79caa1dbd560344b64ed75e85d8703943e0b66c2a6150e4a"}, @@ -1179,8 +1179,8 @@ ruamel-yaml-clib = [ {file = "ruamel.yaml.clib-0.2.7.tar.gz", hash = "sha256:1f08fd5a2bea9c4180db71678e850b995d2a5f4537be0e94557668cf0f5f9497"}, ] setuptools = [ - {file = "setuptools-67.3.2-py3-none-any.whl", hash = "sha256:bb6d8e508de562768f2027902929f8523932fcd1fb784e6d573d2cafac995a48"}, - {file = "setuptools-67.3.2.tar.gz", hash = "sha256:95f00380ef2ffa41d9bba85d95b27689d923c93dfbafed4aecd7cf988a25e012"}, + {file = "setuptools-67.4.0-py3-none-any.whl", hash = "sha256:f106dee1b506dee5102cc3f3e9e68137bbad6d47b616be7991714b0c62204251"}, + {file = "setuptools-67.4.0.tar.gz", hash = "sha256:e5fd0a713141a4a105412233c63dc4e17ba0090c8e8334594ac790ec97792330"}, ] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, From ec0d75c5ca63f56a25c573bd6791bc0f581e7d2d Mon Sep 17 00:00:00 2001 From: MichaelOSullivanAnswer Date: Fri, 24 Feb 2023 09:39:52 +0000 Subject: [PATCH 11/14] run black --- answerking_app/views/order_views.py | 11 ++--------- answerking_app/views/product_views.py | 9 ++------- poetry.lock | 2 +- 3 files changed, 5 insertions(+), 17 deletions(-) diff --git a/answerking_app/views/order_views.py b/answerking_app/views/order_views.py index 384b3e0b..b19c3c8b 100644 --- a/answerking_app/views/order_views.py +++ b/answerking_app/views/order_views.py @@ -243,16 +243,9 @@ def delete(self, request: Request, *args, **kwargs) -> Response: return self.cancel_order(request, *args, **kwargs) -class OrderView( - OrderListView, - OrderCreateView -): +class OrderView(OrderListView, OrderCreateView): pass -class OrderDetailView( - OrderRetrieveView, - OrderUpdateView, - OrderCancelView -): +class OrderDetailView(OrderRetrieveView, OrderUpdateView, OrderCancelView): pass diff --git a/answerking_app/views/product_views.py b/answerking_app/views/product_views.py index c6d75e10..1ad812db 100644 --- a/answerking_app/views/product_views.py +++ b/answerking_app/views/product_views.py @@ -254,16 +254,11 @@ def delete(self, request: Request, *args, **kwargs) -> Response: return self.retire(request, *args, **kwargs) -class ProductView( - ProductListView, - ProductCreateView -): +class ProductView(ProductListView, ProductCreateView): pass class ProductDetailView( - ProductRetrieveView, - ProductUpdateView, - ProductRetireView + ProductRetrieveView, ProductUpdateView, ProductRetireView ): pass diff --git a/poetry.lock b/poetry.lock index f5832d61..aaf1f41a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -156,7 +156,7 @@ description = "django-cors-headers is a Django application for handling the serv category = "main" optional = false python-versions = ">=3.7" - +git a [package.dependencies] Django = ">=3.2" From 83a6b93f95696dbb3ff4b7e4e008ccb95277c394 Mon Sep 17 00:00:00 2001 From: MichaelOSullivanAnswer Date: Fri, 24 Feb 2023 09:56:50 +0000 Subject: [PATCH 12/14] fix poetry.lock --- poetry.lock | 59 ++++++++--------------------------------------------- 1 file changed, 8 insertions(+), 51 deletions(-) diff --git a/poetry.lock b/poetry.lock index aaf1f41a..3416eca8 100644 --- a/poetry.lock +++ b/poetry.lock @@ -156,7 +156,7 @@ description = "django-cors-headers is a Django application for handling the serv category = "main" optional = false python-versions = ">=3.7" -git a + [package.dependencies] Django = ">=3.2" @@ -187,27 +187,6 @@ python-versions = ">=3.6" django = ">=3.0" pytz = "*" -[[package]] -name = "djangorestframework-simplejwt" -version = "5.2.2" -description = "A minimal JSON Web Token authentication plugin for Django REST Framework" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -django = "*" -djangorestframework = "*" -pyjwt = ">=1.7.1,<3" - -[package.extras] -crypto = ["cryptography (>=3.3.1)"] -dev = ["Sphinx (>=1.6.5,<2)", "cryptography", "flake8", "ipython", "isort", "pep8", "pytest", "pytest-cov", "pytest-django", "pytest-watch", "pytest-xdist", "python-jose (==3.3.0)", "sphinx-rtd-theme (>=0.1.9)", "tox", "twine", "wheel"] -doc = ["Sphinx (>=1.6.5,<2)", "sphinx-rtd-theme (>=0.1.9)"] -lint = ["flake8", "isort", "pep8"] -python-jose = ["python-jose (==3.3.0)"] -test = ["cryptography", "pytest", "pytest-cov", "pytest-django", "pytest-xdist", "tox"] - [[package]] name = "drf-ignore-slash-middleware" version = "0.0.1" @@ -458,23 +437,9 @@ category = "dev" optional = false python-versions = ">=3.6" -[[package]] -name = "pyjwt" -version = "2.6.0" -description = "JSON Web Token implementation in Python" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.extras] -crypto = ["cryptography (>=3.4.0)"] -dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] -docs = ["sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] -tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] - [[package]] name = "pyright" -version = "1.1.295" +version = "1.1.294" description = "Command line wrapper for pyright" category = "dev" optional = false @@ -576,7 +541,7 @@ python-versions = ">=3.5" [[package]] name = "setuptools" -version = "67.4.0" +version = "67.3.2" description = "Easily download, build, install, upgrade, and uninstall Python packages" category = "main" optional = false @@ -696,7 +661,7 @@ python-versions = "*" [metadata] lock-version = "1.1" python-versions = "^3.10" -content-hash = "63c0fcd254fd579aa595e4506f831bfc132a24de6011201927e2537e1b055903" +content-hash = "913c9062006d9be5e07954128bf6dc09516d215a4c4587e5d22a3db993d74dda" [metadata.files] asgiref = [ @@ -903,10 +868,6 @@ djangorestframework = [ {file = "djangorestframework-3.14.0-py3-none-any.whl", hash = "sha256:eb63f58c9f218e1a7d064d17a70751f528ed4e1d35547fdade9aaf4cd103fd08"}, {file = "djangorestframework-3.14.0.tar.gz", hash = "sha256:579a333e6256b09489cbe0a067e66abe55c6595d8926be6b99423786334350c8"}, ] -djangorestframework-simplejwt = [ - {file = "djangorestframework_simplejwt-5.2.2-py3-none-any.whl", hash = "sha256:4c0d2e2513e12587d93501ac091781684a216c3ee614eb3b5a10586aef5ca845"}, - {file = "djangorestframework_simplejwt-5.2.2.tar.gz", hash = "sha256:d27d4bcac2c6394f678dea8b4d0d511c6e18a7f2eb8aaeeb8a7de601aeb77c42"}, -] drf-ignore-slash-middleware = [ {file = "drf_ignore_slash_middleware-0.0.1-py3-none-any.whl", hash = "sha256:9c17b6c2ce16685455df4be0fe7d34c5f6fa4c61ddb4660ca5f253ea08914469"}, {file = "drf_ignore_slash_middleware-0.0.1.tar.gz", hash = "sha256:3ed51accc471b44c8e7986e3667669f74e7fa303a516f774d7931d069c1d6f0d"}, @@ -1044,13 +1005,9 @@ pycodestyle = [ {file = "pycodestyle-2.10.0-py2.py3-none-any.whl", hash = "sha256:8a4eaf0d0495c7395bdab3589ac2db602797d76207242c17d470186815706610"}, {file = "pycodestyle-2.10.0.tar.gz", hash = "sha256:347187bdb476329d98f695c213d7295a846d1152ff4fe9bacb8a9590b8ee7053"}, ] -pyjwt = [ - {file = "PyJWT-2.6.0-py3-none-any.whl", hash = "sha256:d83c3d892a77bbb74d3e1a2cfa90afaadb60945205d1095d9221f04466f64c14"}, - {file = "PyJWT-2.6.0.tar.gz", hash = "sha256:69285c7e31fc44f68a1feb309e948e0df53259d579295e6cfe2b1792329f05fd"}, -] pyright = [ - {file = "pyright-1.1.295-py3-none-any.whl", hash = "sha256:57d6e66381edd38160342abdaea195fc6af16059eabe7e0816f04d42748b2c36"}, - {file = "pyright-1.1.295.tar.gz", hash = "sha256:fa8ef1da35071fe351ee214576665857ceafc96418a4550a48b1f577267d9ac0"}, + {file = "pyright-1.1.294-py3-none-any.whl", hash = "sha256:5b27e28a1cfc60cea707fd3b644769fa6dd0b194481cdcc2399cf2a51cc5a846"}, + {file = "pyright-1.1.294.tar.gz", hash = "sha256:fea5fed3d6a3f02259e622c901e86a7b8bcf237d35e1cdfe01d0e0723768dcb6"}, ] pyrsistent = [ {file = "pyrsistent-0.19.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:20460ac0ea439a3e79caa1dbd560344b64ed75e85d8703943e0b66c2a6150e4a"}, @@ -1179,8 +1136,8 @@ ruamel-yaml-clib = [ {file = "ruamel.yaml.clib-0.2.7.tar.gz", hash = "sha256:1f08fd5a2bea9c4180db71678e850b995d2a5f4537be0e94557668cf0f5f9497"}, ] setuptools = [ - {file = "setuptools-67.4.0-py3-none-any.whl", hash = "sha256:f106dee1b506dee5102cc3f3e9e68137bbad6d47b616be7991714b0c62204251"}, - {file = "setuptools-67.4.0.tar.gz", hash = "sha256:e5fd0a713141a4a105412233c63dc4e17ba0090c8e8334594ac790ec97792330"}, + {file = "setuptools-67.3.2-py3-none-any.whl", hash = "sha256:bb6d8e508de562768f2027902929f8523932fcd1fb784e6d573d2cafac995a48"}, + {file = "setuptools-67.3.2.tar.gz", hash = "sha256:95f00380ef2ffa41d9bba85d95b27689d923c93dfbafed4aecd7cf988a25e012"}, ] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, From 57dbcb61992409b2a6ee754d08b252011b8c7048 Mon Sep 17 00:00:00 2001 From: MichaelOSullivanAnswer Date: Fri, 24 Feb 2023 10:00:03 +0000 Subject: [PATCH 13/14] fix dependencies --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f182b13a..019a95ef 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,6 @@ drf-spectacular = "^0.25.1" gunicorn = "^20.1.0" generics = "^6.0.0" drf-ignore-slash-middleware = "^0.0.1" -djangorestframework-simplejwt = "^5.2.2" [tool.poetry.group.dev.dependencies] black = "^22.10.0" From 02b0382df879bebbaf3335c3b62551ef02c2eaaa Mon Sep 17 00:00:00 2001 From: MichaelOSullivanAnswer Date: Fri, 24 Feb 2023 10:04:56 +0000 Subject: [PATCH 14/14] update dependencies --- poetry.lock | 45 ++++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 1 + 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index 3416eca8..7657f908 100644 --- a/poetry.lock +++ b/poetry.lock @@ -187,6 +187,27 @@ python-versions = ">=3.6" django = ">=3.0" pytz = "*" +[[package]] +name = "djangorestframework-simplejwt" +version = "5.2.2" +description = "A minimal JSON Web Token authentication plugin for Django REST Framework" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +django = "*" +djangorestframework = "*" +pyjwt = ">=1.7.1,<3" + +[package.extras] +crypto = ["cryptography (>=3.3.1)"] +dev = ["Sphinx (>=1.6.5,<2)", "cryptography", "flake8", "ipython", "isort", "pep8", "pytest", "pytest-cov", "pytest-django", "pytest-watch", "pytest-xdist", "python-jose (==3.3.0)", "sphinx-rtd-theme (>=0.1.9)", "tox", "twine", "wheel"] +doc = ["Sphinx (>=1.6.5,<2)", "sphinx-rtd-theme (>=0.1.9)"] +lint = ["flake8", "isort", "pep8"] +python-jose = ["python-jose (==3.3.0)"] +test = ["cryptography", "pytest", "pytest-cov", "pytest-django", "pytest-xdist", "tox"] + [[package]] name = "drf-ignore-slash-middleware" version = "0.0.1" @@ -437,6 +458,20 @@ category = "dev" optional = false python-versions = ">=3.6" +[[package]] +name = "pyjwt" +version = "2.6.0" +description = "JSON Web Token implementation in Python" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +crypto = ["cryptography (>=3.4.0)"] +dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] +docs = ["sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] +tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] + [[package]] name = "pyright" version = "1.1.294" @@ -661,7 +696,7 @@ python-versions = "*" [metadata] lock-version = "1.1" python-versions = "^3.10" -content-hash = "913c9062006d9be5e07954128bf6dc09516d215a4c4587e5d22a3db993d74dda" +content-hash = "63c0fcd254fd579aa595e4506f831bfc132a24de6011201927e2537e1b055903" [metadata.files] asgiref = [ @@ -868,6 +903,10 @@ djangorestframework = [ {file = "djangorestframework-3.14.0-py3-none-any.whl", hash = "sha256:eb63f58c9f218e1a7d064d17a70751f528ed4e1d35547fdade9aaf4cd103fd08"}, {file = "djangorestframework-3.14.0.tar.gz", hash = "sha256:579a333e6256b09489cbe0a067e66abe55c6595d8926be6b99423786334350c8"}, ] +djangorestframework-simplejwt = [ + {file = "djangorestframework_simplejwt-5.2.2-py3-none-any.whl", hash = "sha256:4c0d2e2513e12587d93501ac091781684a216c3ee614eb3b5a10586aef5ca845"}, + {file = "djangorestframework_simplejwt-5.2.2.tar.gz", hash = "sha256:d27d4bcac2c6394f678dea8b4d0d511c6e18a7f2eb8aaeeb8a7de601aeb77c42"}, +] drf-ignore-slash-middleware = [ {file = "drf_ignore_slash_middleware-0.0.1-py3-none-any.whl", hash = "sha256:9c17b6c2ce16685455df4be0fe7d34c5f6fa4c61ddb4660ca5f253ea08914469"}, {file = "drf_ignore_slash_middleware-0.0.1.tar.gz", hash = "sha256:3ed51accc471b44c8e7986e3667669f74e7fa303a516f774d7931d069c1d6f0d"}, @@ -1005,6 +1044,10 @@ pycodestyle = [ {file = "pycodestyle-2.10.0-py2.py3-none-any.whl", hash = "sha256:8a4eaf0d0495c7395bdab3589ac2db602797d76207242c17d470186815706610"}, {file = "pycodestyle-2.10.0.tar.gz", hash = "sha256:347187bdb476329d98f695c213d7295a846d1152ff4fe9bacb8a9590b8ee7053"}, ] +pyjwt = [ + {file = "PyJWT-2.6.0-py3-none-any.whl", hash = "sha256:d83c3d892a77bbb74d3e1a2cfa90afaadb60945205d1095d9221f04466f64c14"}, + {file = "PyJWT-2.6.0.tar.gz", hash = "sha256:69285c7e31fc44f68a1feb309e948e0df53259d579295e6cfe2b1792329f05fd"}, +] pyright = [ {file = "pyright-1.1.294-py3-none-any.whl", hash = "sha256:5b27e28a1cfc60cea707fd3b644769fa6dd0b194481cdcc2399cf2a51cc5a846"}, {file = "pyright-1.1.294.tar.gz", hash = "sha256:fea5fed3d6a3f02259e622c901e86a7b8bcf237d35e1cdfe01d0e0723768dcb6"}, diff --git a/pyproject.toml b/pyproject.toml index 019a95ef..f182b13a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,6 +24,7 @@ drf-spectacular = "^0.25.1" gunicorn = "^20.1.0" generics = "^6.0.0" drf-ignore-slash-middleware = "^0.0.1" +djangorestframework-simplejwt = "^5.2.2" [tool.poetry.group.dev.dependencies] black = "^22.10.0"