From 9d3d10d0762e349af025f0dede659e046a260f6f Mon Sep 17 00:00:00 2001 From: Riad Elimemmedov Date: Sat, 3 Feb 2024 16:50:47 +0400 Subject: [PATCH] Added swagger for code documantation and configuration pytest --- .github/workflows/backend.yml | 3 ++ .gitignore | 4 ++ backend/apps/users/admin.py | 50 +++++++++++++++++++++++ backend/apps/users/forms.py | 17 ++++++++ backend/apps/users/models.py | 4 ++ backend/apps/users/urls.py | 6 +++ backend/apps/users/views.py | 9 ++++ backend/config/base.py | 17 ++++++++ backend/config/tests/__init__.py | 0 backend/config/tests/users/__init__.py | 0 backend/config/tests/users/test_models.py | 7 ++++ backend/config/urls.py | 23 ++++++----- backend/schema.yml | 38 +++++++++++++++++ backend/setup.cfg | 1 - commands.md | 4 ++ 15 files changed, 172 insertions(+), 11 deletions(-) create mode 100644 backend/apps/users/forms.py create mode 100644 backend/apps/users/urls.py create mode 100644 backend/config/tests/__init__.py create mode 100644 backend/config/tests/users/__init__.py create mode 100644 backend/config/tests/users/test_models.py create mode 100644 backend/schema.yml diff --git a/.github/workflows/backend.yml b/.github/workflows/backend.yml index 318bef9..4e444d0 100644 --- a/.github/workflows/backend.yml +++ b/.github/workflows/backend.yml @@ -47,6 +47,9 @@ jobs: - name: Run tests run: poetry run manage.py test apps + - name: Run tests with pytest + run: poetry run pytest --cov=./ --cov-report=html + - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 with: diff --git a/.gitignore b/.gitignore index 1f319fb..0f74b7d 100644 --- a/.gitignore +++ b/.gitignore @@ -408,3 +408,7 @@ poetry.toml pyrightconfig.json # End of https://www.toptal.com/developers/gitignore/api/python,django +note.txt + +htmlcov/ +.coverage diff --git a/backend/apps/users/admin.py b/backend/apps/users/admin.py index 8c38f3f..826f7f0 100644 --- a/backend/apps/users/admin.py +++ b/backend/apps/users/admin.py @@ -1,3 +1,53 @@ from django.contrib import admin +from django.contrib.auth.admin import UserAdmin + +from .forms import CustomUserChangeForm, CustomUserCreationForm +from .models import CustomUser # Register your models here. + + +# !CustomUserAdmin +class CustomUserAdmin(UserAdmin): + add_form = CustomUserCreationForm + form = CustomUserChangeForm + model = CustomUser + list_display = ( + "email", + "is_staff", + "is_active", + ) + list_filter = ( + "email", + "is_staff", + "is_active", + ) + fieldsets = ( + (None, {"fields": ("email", "password")}), + ( + "Permissions", + {"fields": ("is_staff", "is_active", "groups", "user_permissions")}, + ), + ) + add_fieldsets = ( + ( + None, + { + "classes": ("wide",), + "fields": ( + "email", + "password1", + "password2", + "is_staff", + "is_active", + "groups", + "user_permissions", + ), + }, + ), + ) + search_fields = ("email",) + ordering = ("email",) + + +admin.site.register(CustomUser, CustomUserAdmin) diff --git a/backend/apps/users/forms.py b/backend/apps/users/forms.py new file mode 100644 index 0000000..2d0e5b7 --- /dev/null +++ b/backend/apps/users/forms.py @@ -0,0 +1,17 @@ +from django.contrib.auth.forms import UserChangeForm, UserCreationForm + +from .models import CustomUser + + +# !CustomUserCreationForm +class CustomUserCreationForm(UserCreationForm): + class Meta: + model = CustomUser + fields = ("email",) + + +# !CustomUserChangeForm +class CustomUserChangeForm(UserChangeForm): + class Meta: + model = CustomUser + fields = ("email",) diff --git a/backend/apps/users/models.py b/backend/apps/users/models.py index da5bbf0..acde724 100644 --- a/backend/apps/users/models.py +++ b/backend/apps/users/models.py @@ -21,5 +21,9 @@ class CustomUser(AbstractBaseUser, PermissionsMixin): objects = CustomUserManager() + class Meta: + verbose_name = "Custom User" + verbose_name_plural = "Custom Users" + def __str__(self): return self.email diff --git a/backend/apps/users/urls.py b/backend/apps/users/urls.py new file mode 100644 index 0000000..2826529 --- /dev/null +++ b/backend/apps/users/urls.py @@ -0,0 +1,6 @@ +from django.urls import path + +from .views import hello_world + +app_name = "users" +urlpatterns = [path("sayhello/", hello_world, name="hello_world")] diff --git a/backend/apps/users/views.py b/backend/apps/users/views.py index 91ea44a..8efbae8 100644 --- a/backend/apps/users/views.py +++ b/backend/apps/users/views.py @@ -1,3 +1,12 @@ from django.shortcuts import render +from rest_framework.decorators import api_view +from rest_framework.response import Response # Create your views here. + + +@api_view(["GET", "POST"]) +def hello_world(request): + if request.method == "POST": + return Response({"message": "Got some data!", "data": request.data}) + return Response({"message": "Hello, world!"}) diff --git a/backend/config/base.py b/backend/config/base.py index 7eeaa3c..4bde2d8 100644 --- a/backend/config/base.py +++ b/backend/config/base.py @@ -47,6 +47,9 @@ THIRD_PARTY_APPS = [ "django_cleanup", "django_extensions", + "rest_framework", + "drf_spectacular", + "djmoney", ] # !Created Apps @@ -205,3 +208,17 @@ # EMAIL_HOST_USER = config("EMAIL_HOST_USER") # EMAIL_HOST_PASSWORD = config("EMAIL_HOST_PASSWORD") # DEFAULT_FROM_EMAIL = config("DEFAULT_FROM_EMAIL") + + +# !Rest Framework +REST_FRAMEWORK = { + "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema", +} + +# !SPECTACULAR_SETTINGS +SPECTACULAR_SETTINGS = { + "TITLE": "Django DRF Ecommerce", + "DESCRIPTION": "This project purpose creating ecommerce api for business company", + "VERSION": "1.0.0", + "SERVE_INCLUDE_SCHEMA": False, +} diff --git a/backend/config/tests/__init__.py b/backend/config/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/config/tests/users/__init__.py b/backend/config/tests/users/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/config/tests/users/test_models.py b/backend/config/tests/users/test_models.py new file mode 100644 index 0000000..743ffb1 --- /dev/null +++ b/backend/config/tests/users/test_models.py @@ -0,0 +1,7 @@ +def add_numbers(a, b): + return a + b + + +def test_add_numbers(): + result = add_numbers(2, 3) + assert result == 5 diff --git a/backend/config/urls.py b/backend/config/urls.py index 61f05e7..e39e65b 100644 --- a/backend/config/urls.py +++ b/backend/config/urls.py @@ -21,6 +21,7 @@ from django.contrib import admin from django.urls import include, path from django.utils.translation import gettext_lazy as _ +from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerSplitView # !Abstract from abstract.constants import AppName @@ -36,18 +37,20 @@ if not settings.APP_NAME or settings.APP_NAME not in [app.value for app in AppName]: raise Exception(_("Please set app correct name same as abstract.constants.AppName")) -# For only admin -urls_admin = [ - path("jet/", include("jet.urls", "jet")), - path("jet/dashboard/", include("jet.dashboard.urls", "jet-dashboard")), -] if settings.APP_NAME == AppName.ADMIN.name: - urlpatterns += urls_admin -else: - urlpatterns += [] - urlpatterns += urls_admin -urlpatterns += i18n_patterns(path("admin/", admin.site.urls)) + # Only for admin + urlpatterns += [ + path("jet/", include("jet.urls", "jet")), + path("jet/dashboard/", include("jet.dashboard.urls", "jet-dashboard")), + path("api/schema", SpectacularAPIView.as_view(), name="schema_api"), + path( + "api/schema/docs", + SpectacularSwaggerSplitView.as_view(url_name="schema_api"), + ), + ] + urlpatterns += i18n_patterns(path("admin/", admin.site.urls)) + urlpatterns += [path("users/", include("apps.users.urls", namespace="users"))] # *Settings Debug diff --git a/backend/schema.yml b/backend/schema.yml new file mode 100644 index 0000000..a1444e5 --- /dev/null +++ b/backend/schema.yml @@ -0,0 +1,38 @@ +openapi: 3.0.3 +info: + title: Django DRF Ecommerce + version: 1.0.0 + description: This project purpose creating ecommerce api for business company +paths: + /users/sayhello/: + get: + operationId: users_sayhello_retrieve + tags: + - users + security: + - cookieAuth: [] + - basicAuth: [] + - {} + responses: + '200': + description: No response body + post: + operationId: users_sayhello_create + tags: + - users + security: + - cookieAuth: [] + - basicAuth: [] + - {} + responses: + '200': + description: No response body +components: + securitySchemes: + basicAuth: + type: http + scheme: basic + cookieAuth: + type: apiKey + in: cookie + name: sessionid diff --git a/backend/setup.cfg b/backend/setup.cfg index 3038e9f..9ae666b 100644 --- a/backend/setup.cfg +++ b/backend/setup.cfg @@ -1,4 +1,3 @@ [tool:pytest] -DJANGO_SETTINGS_MODULE = config.settings python_files = test_*.py addopts = --cov -x diff --git a/commands.md b/commands.md index 8f4683e..9b0c6ca 100644 --- a/commands.md +++ b/commands.md @@ -35,6 +35,8 @@ Backend - `poetry run pytest -x` - Format code using Black: - `poetry run black .` +- Generate html for covering tests: + - `poetry run pytest --cov=./ --cov-report=html` - Check code formatting is needed or not: - `poetry run black . --check` - Sort imports using isort with Black profile: @@ -55,3 +57,5 @@ Backend - `poetry run manage.py makemigrations --dry-run --verbosity 3` - Run pre commit hooks - `poetry run pre-commit run --all-files` +- Generate api schema +- `poetry run manage.py spectacular --file schema.yml`