From 2e17d0cd44f26f8726ace646c8f9af26a2da9f68 Mon Sep 17 00:00:00 2001 From: Mojtaba-z Date: Tue, 29 Apr 2025 15:43:53 +0330 Subject: [PATCH] initial rasaddam backend project --- .dockerignore | 0 .env.example | 15 + .pre-commit-config.yaml | 66 ++ README.md | 217 ++++++ Rasaddam_Backend/__init__.py | 0 Rasaddam_Backend/asgi.py | 16 + Rasaddam_Backend/settings.py | 122 ++++ Rasaddam_Backend/urls.py | 22 + Rasaddam_Backend/wsgi.py | 16 + apps/core/__init__.py | 0 apps/core/apps.py | 6 + apps/core/constants.py | 1 + apps/core/exceptions.py | 6 + apps/core/helpers.py | 0 apps/core/models.py | 0 apps/core/urls.py | 3 + apps/example/__init__.py | 0 apps/example/api/__init__.py | 0 apps/example/api/v1/__init__.py | 0 apps/example/api/v1/serializers.py | 0 apps/example/api/v1/urls.py | 0 apps/example/api/v1/views.py | 0 apps/example/api/v2/__init__.py | 0 apps/example/api/v2/serializers.py | 0 apps/example/api/v2/urls.py | 0 apps/example/api/v2/views.py | 0 apps/example/apps.py | 0 apps/example/fixtures/.gitkeep | 0 apps/example/management/__init__.py | 0 apps/example/management/commands/__init__.py | 0 apps/example/management/commands/command.py | 1 + apps/example/migrations/__init__.py | 0 apps/example/models.py | 0 apps/example/services.py | 1 + apps/example/tests/test_common_services.py | 0 apps/example/urls.py | 1 + common/constants.py | 1 + common/generics.py | 0 common/helpers.py | 0 common/mixins.py | 0 common/models.py | 84 +++ common/serializers.py | 43 ++ config/__init__.py | 0 config/asgi.py | 16 + config/settings.py | 151 ++++ config/urls.py | 8 + config/wsgi.py | 16 + deployments/django-project/Dockerfile | 0 deployments/docker-compose.yml | 0 deployments/nginx/Dockerfile | 0 deployments/nginx/default.conf | 33 + docs/CHANGELOG.md | 0 docs/CONTRIBUTING.md | 0 docs/deployment.md | 0 docs/swagger.yaml | 728 +++++++++++++++++++ entrypoint.sh | 6 + locale/.gitkeep | 0 logs/.gitkeep | 0 manage.py | 22 + media/.gitkeep | 0 pyproject.toml | 16 + pytest.ini | 3 + requirements/common.txt | 9 + requirements/local.txt | 4 + requirements/production.txt | 1 + scripts/.gitkeep | 0 static/.gitkeep | 0 67 files changed, 1634 insertions(+) create mode 100644 .dockerignore create mode 100644 .env.example create mode 100644 .pre-commit-config.yaml create mode 100644 README.md create mode 100644 Rasaddam_Backend/__init__.py create mode 100644 Rasaddam_Backend/asgi.py create mode 100644 Rasaddam_Backend/settings.py create mode 100644 Rasaddam_Backend/urls.py create mode 100644 Rasaddam_Backend/wsgi.py create mode 100644 apps/core/__init__.py create mode 100644 apps/core/apps.py create mode 100644 apps/core/constants.py create mode 100644 apps/core/exceptions.py create mode 100644 apps/core/helpers.py create mode 100644 apps/core/models.py create mode 100644 apps/core/urls.py create mode 100644 apps/example/__init__.py create mode 100644 apps/example/api/__init__.py create mode 100644 apps/example/api/v1/__init__.py create mode 100644 apps/example/api/v1/serializers.py create mode 100644 apps/example/api/v1/urls.py create mode 100644 apps/example/api/v1/views.py create mode 100644 apps/example/api/v2/__init__.py create mode 100644 apps/example/api/v2/serializers.py create mode 100644 apps/example/api/v2/urls.py create mode 100644 apps/example/api/v2/views.py create mode 100644 apps/example/apps.py create mode 100644 apps/example/fixtures/.gitkeep create mode 100644 apps/example/management/__init__.py create mode 100644 apps/example/management/commands/__init__.py create mode 100644 apps/example/management/commands/command.py create mode 100644 apps/example/migrations/__init__.py create mode 100644 apps/example/models.py create mode 100644 apps/example/services.py create mode 100644 apps/example/tests/test_common_services.py create mode 100644 apps/example/urls.py create mode 100644 common/constants.py create mode 100644 common/generics.py create mode 100644 common/helpers.py create mode 100644 common/mixins.py create mode 100644 common/models.py create mode 100644 common/serializers.py create mode 100644 config/__init__.py create mode 100644 config/asgi.py create mode 100644 config/settings.py create mode 100644 config/urls.py create mode 100644 config/wsgi.py create mode 100644 deployments/django-project/Dockerfile create mode 100644 deployments/docker-compose.yml create mode 100644 deployments/nginx/Dockerfile create mode 100644 deployments/nginx/default.conf create mode 100644 docs/CHANGELOG.md create mode 100644 docs/CONTRIBUTING.md create mode 100644 docs/deployment.md create mode 100644 docs/swagger.yaml create mode 100644 entrypoint.sh create mode 100644 locale/.gitkeep create mode 100644 logs/.gitkeep create mode 100644 manage.py create mode 100644 media/.gitkeep create mode 100644 pyproject.toml create mode 100644 pytest.ini create mode 100644 requirements/common.txt create mode 100644 requirements/local.txt create mode 100644 requirements/production.txt create mode 100644 scripts/.gitkeep create mode 100644 static/.gitkeep diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..e69de29 diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..ebf3670 --- /dev/null +++ b/.env.example @@ -0,0 +1,15 @@ +# Django secrets +SECRET_KEY=super-insecure-django-key +DEBUG=True +ALLOWED_HOSTS=* +ENV_NAME=DEV + +# Datbase secrets +DB_HOST=localhost +DB_PORT=5432 +DB_NAME=example_db +DB_USERNAME=postgres +DB_PASSWORD=12345678 + +# Super user information +SUPERUSER_EMAIL=superuser@example.com diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..481f8fd --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,66 @@ +default_language_version: + python: python3.11 +default_stages: [commit] +fail_fast: true + +repos: +# Basic pre-commit hooks +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v2.3.0 + hooks: + - id: check-xml + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace + - id: check-added-large-files + - id: check-ast + - id: debug-statements + - id: no-commit-to-branch + args: ['--branch', 'development', '--branch', 'staging', '--branch', 'production'] + +# Dependencies checker +- repo: https://github.com/Lucas-C/pre-commit-hooks-safety + rev: v1.3.2 + hooks: + - id: python-safety-dependencies-check + files: common.txt, local.txt + +# isort For sorting imports +- repo: https://github.com/pycqa/isort + rev: 5.12.0 + hooks: + - id: isort + +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.4.4 + hooks: + - id: ruff + name: ruff + entry: ruff check --fix + language: python + - id: ruff-format + name: ruff-format + entry: ruff format + language: python + +# Codespell for spell checking +# - repo: https://github.com/codespell-project/codespell +# rev: v2.2.4 +# hooks: +# - id: codespell +# exclude: > +# (?x)^( +# .*\test_*.py +# )$ + +# Pytest for testing +# - repo: local +# hooks: +# - id: pytest-check +# stages: [commit] +# types: [python] +# name: pytest-check +# entry: pytest +# language: system +# pass_filenames: false +# always_run: true diff --git a/README.md b/README.md new file mode 100644 index 0000000..27761f9 --- /dev/null +++ b/README.md @@ -0,0 +1,217 @@ +[![Python 3.12.3](https://img.shields.io/badge/python-3.12.3-blue.svg)](https://www.python.org/downloads/release/python-3123/) +[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) +[![Pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white)](https://pre-commit.com/) + +![Django](https://img.shields.io/badge/django-%23092E20.svg?style=for-the-badge&logo=django&logoColor=white) +![DjangoREST](https://img.shields.io/badge/DJANGO-REST-ff1709?style=for-the-badge&logo=django&logoColor=white&color=ff1709&labelColor=gray) +![Postgres](https://img.shields.io/badge/postgres-%23316192.svg?style=for-the-badge&logo=postgresql&logoColor=white) +![Swagger](https://img.shields.io/badge/-Swagger-%23Clojure?style=for-the-badge&logo=swagger&logoColor=white) + + +# Django Project Structure +This is a template/project structure for developing django-based applications - +using `django-rest-framework` along with `django`. + +The project is meant to be easily clone-able, and used as the starter template +for the next big thing you develop. Note, this is a folder structure only, not +“best practices”. + + +## Some batteries included: +* [Django Storages](https://django-storages.readthedocs.io/en/stable/) - To integrate with different types of storages +* [Django Rest Framework](https://www.django-rest-framework.org/) - For API development +* [Django CORS Headers](https://github.com/adamchainz/django-cors-headers) - To allow requests from other origins +* [Sentry](https://docs.sentry.io/platforms/python/) - For crashes +* [Gunicorn](https://gunicorn.org/) - As a web server + + +## Getting Started +1. Since this is a template repository, simply hit "Use this template" on GitHub +and follow the instructions. Otherwise, you can just clone the repo, remove/add +anything you see fit. +1. Run the project using `python manage.py runserver` and you should see the +default success page provided by Django at +[http://127.0.0.1:8000/](http://127.0.0.1:8000/). +1. [Optional] If you want to configure database, in the `DATABASE` section of +`settings.py` we have added `postgresql` as the default `DATABASE` (As most of +the application are using it). You can roll back to the `sqlite` by adding the +following code snippet, removing the current one. + +```bash +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + } +} +``` + + +### Creating an App +1. Create a folder with the app name in `apps`. For example: `poll` +1. Run `python manage.py startapp poll apps/poll` from the root directory of the +project + + +## Project Tree +``` bash +. +├── apps/ +│ └── example/ # A django rest app +│ ├── api/ +│ │ ├── v1/ # Only the "presentation" layer exists here. +│ │ │ ├── __init__.py +│ │ │ ├── serializers.py +│ │ │ ├── urls.py +│ │ │ └── views.py +│ │ ├── v2 # Only the "presentation" layer exists here. +│ │ │ ├── __init__.py +│ │ │ ├── serializers.py +│ │ │ ├── urls.py +│ │ │ └── views.py +│ │ └── __init__.py +│ ├── fixtures/ # Constant "seeders" to populate your database +│ ├── management/ +│ │ ├── commands/ # Try and write some database seeders here +│ │ │ └── command.py +│ │ └── __init__.py +│ ├── migrations/ +│ │ └── __init__.py +│ ├── templates/ # App-specific templates go here +│ ├── tests/ # All your integration and unit tests for an app go here. +│ ├── __init__.py +│ ├── admin.py +│ ├── apps.py +│ ├── models.py +│ ├── services.py # Your business logic and data abstractions go here. +│ ├── urls.py +│ └── views.py +├── common/ # An optional folder containing common "stuff" for the entire project +├── config/ +│ ├── __init__.py +│ ├── asgi.py +│ ├── settings.py +│ ├── urls.py +│ └── wsgi.py +├── deployments/ # Isolate Dockerfiles and docker-compose files here. +├── docs/ +│ ├── CHANGELOG.md +│ ├── CONTRIBUTING.md +│ ├── deployment.md +│ ├── local-development.md +│ └── swagger.yaml +├── requirements/ +│ ├── common.txt # Same for all environments +│ ├── development.txt # Only for a development server +│ ├── local.txt # Only for a local server (example: docs, performance testing, etc.) +│ └── production.txt # Production only +├── static/ # Your static files +├── .env.example # An example of your .env configurations. Add necessary comments. +├── .gitignore # https://github.com/github/gitignore/blob/main/Python.gitignore +├── entrypoint.sh # Any bootstrapping necessary for your application +├── manage.py +├── pytest.ini +└── README.md +``` + + +## Rationale +Each `app` should be designed in way to be plug-able, that is, dragged and dropped +into any other project and it’ll work independently. + + +### `apps` Folder +* A mother-folder containing all apps for our project. Congruent to any +JS-framework's `src` folder. If you really wanted to, you could even call it the +`src` folder. Again, it's up to you. +* An app can be a django template project, or a rest framework API. + +### `services` +* We’ll be writing business logic in services instead of anywhere else. +* There's a common argument: "Why not just use model managers?", and honestly, +that's a fair point. However, for our use case, we've often noticed that a single +service can leverage more zero to many models. Either way, managers or services, +both work towards the same goal - isolating business logic away from views, and +brings it closer to the data. + +### `api` Folder +* We like to place all our API components into a package within an app called +`api`. For example, in this repository it's the `example/api` folder. That +allows us to isolate our API components in a consistent location. If +we were to put it in the root of our app, then we would end up with a huge list +of API-specific modules in the general area of the app. That's without getting +into the mess of API versioning. + +For projects with a lot of small, interconnecting apps, it can be hard to hunt +down where a particular API view lives. In contrast to placing all API code +within each relevant app, sometimes it makes more sense to build an app +specifically for the API. This is where all the serializers, renderers, and views +are placed. Therefore, the name of the app should reflect its API version + + +#### API Versioning +It might often be necessary to support multiple versions of an API throughout +the lifetime of a project. Therefore, we're adding in support right from the +start. + +For different API versions, we're assuming the following will change: +- Serializers: That is, how the data is presented to a consumer +- Views: That is, how the data is accessed and modified by a consumer +- URLs: That is, where the consumer access the data + +`model`s and `service`s can be thought of as shared between versions. Therefore, +migrating changes should be versioned carefully without breaking different +versions of the API. After all, your API version is simply a presentation of how +data is handled and managed within your application. + +Sufficient unit tests and integration tests should wrap services and API +endpoints to ensure full compatibility. + + +#### What's `v2` of an API? +Currently, we're proposing that major changes to the following constitutes a new +API version: +1. Representation of data, either for submission or retrieval +1. Major optimizations +1. Major code reorganization and code refactor +1. Usually, in a Django project, you won't need to worry about API versioning + + +### `config` +* Contains project configuration files, including the primary URL file +* ~~Contains settings split into `base`, `local`, `production` and `development`.~~. +Update: As environment specific variables will be handled using environment +variables, we've deemed it unnecessary to have separate settings files for now. + +### `deployments` +* Contains Docker, Docker-Compose and nginx specific files for deploying in +different environments. + + +### Exception handling +You should probably add a custom exception handler to your project based on +who consumes your APIs. To learn how to create a custom exception handler, +you can check out the Django Rest Framework documentation at: +https://www.django-rest-framework.org/api-guide/exceptions/#custom-exception-handling + + +## FAQ +> Why not just make a cookiecutter out of this? + +Honestly, GitHub templates are so much easier. It's a one-click solution and +you're good to go. If we want to turn this into a cookiecutter, we'd have to also +start deciding sensible defaults, for instance, sentry, DRF version, formatters, +linters, etc. And that's something better left to the developer. Although, I am +playing around with the idea of having a cookiecutter with those sensible +defaults, but let's hope we have time to work on it on the `cookiecutter` branch. + + +## References +- [Two Scoops of Django by Daniel and Audrey Feldroy](https://www.feldroy.com/books/two-scoops-of-django-3-x) +- [Django Best Practices](https://django-best-practices.readthedocs.io/en/latest/index.html) +- [Cookiecutter Django](https://github.com/cookiecutter/cookiecutter-django) +- [HackSoft Django Style Guide](https://github.com/HackSoftware/Django-Styleguide) +- [Radoslav Georgiev - Django Structure for Scale and Longevity](https://www.youtube.com/watch?v=yG3ZdxBb1oo) +- [Build APIs You Won't Hate](https://apisyouwonthate.com/books/build-apis-you-wont-hate/) +- [Tuxedo Style Guides](https://github.com/saqibur/tuxedo) +- [Django Anti Patterns](https://www.django-antipatterns.com/) diff --git a/Rasaddam_Backend/__init__.py b/Rasaddam_Backend/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Rasaddam_Backend/asgi.py b/Rasaddam_Backend/asgi.py new file mode 100644 index 0000000..dff3d01 --- /dev/null +++ b/Rasaddam_Backend/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for Rasaddam_Backend project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/5.2/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'Rasaddam_Backend.settings') + +application = get_asgi_application() diff --git a/Rasaddam_Backend/settings.py b/Rasaddam_Backend/settings.py new file mode 100644 index 0000000..c8677dd --- /dev/null +++ b/Rasaddam_Backend/settings.py @@ -0,0 +1,122 @@ +""" +Django settings for Rasaddam_Backend project. + +Generated by 'django-admin startproject' using Django 5.2. + +For more information on this file, see +https://docs.djangoproject.com/en/5.2/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/5.2/ref/settings/ +""" + +from pathlib import Path + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'django-insecure-@0apn-lk85pfw=z00x2ib$w9#rwz8%2v4i_n^^9jz-m9b+y55*' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'Rasaddam_Backend.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'Rasaddam_Backend.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/5.2/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', + } +} + + +# Password validation +# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/5.2/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/5.2/howto/static-files/ + +STATIC_URL = 'static/' + +# Default primary key field type +# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' diff --git a/Rasaddam_Backend/urls.py b/Rasaddam_Backend/urls.py new file mode 100644 index 0000000..a0f721a --- /dev/null +++ b/Rasaddam_Backend/urls.py @@ -0,0 +1,22 @@ +""" +URL configuration for Rasaddam_Backend project. + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/5.2/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" +from django.contrib import admin +from django.urls import path + +urlpatterns = [ + path('admin/', admin.site.urls), +] diff --git a/Rasaddam_Backend/wsgi.py b/Rasaddam_Backend/wsgi.py new file mode 100644 index 0000000..b6baf27 --- /dev/null +++ b/Rasaddam_Backend/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for Rasaddam_Backend project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/5.2/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'Rasaddam_Backend.settings') + +application = get_wsgi_application() diff --git a/apps/core/__init__.py b/apps/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/core/apps.py b/apps/core/apps.py new file mode 100644 index 0000000..ab0051e --- /dev/null +++ b/apps/core/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class CoreConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "apps.core" diff --git a/apps/core/constants.py b/apps/core/constants.py new file mode 100644 index 0000000..57add79 --- /dev/null +++ b/apps/core/constants.py @@ -0,0 +1 @@ +# Add your app constants here. diff --git a/apps/core/exceptions.py b/apps/core/exceptions.py new file mode 100644 index 0000000..5262288 --- /dev/null +++ b/apps/core/exceptions.py @@ -0,0 +1,6 @@ +""" + You should probably add a custom exception handler to your project based on + who consumes your APIs. To learn how to create a custom exception handler, + you can check out the Django Rest Framework documentation at: + https://www.django-rest-framework.org/api-guide/exceptions/#custom-exception-handling +""" diff --git a/apps/core/helpers.py b/apps/core/helpers.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/core/models.py b/apps/core/models.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/core/urls.py b/apps/core/urls.py new file mode 100644 index 0000000..a87018c --- /dev/null +++ b/apps/core/urls.py @@ -0,0 +1,3 @@ +app_name = "core" + +urlpatterns = [] diff --git a/apps/example/__init__.py b/apps/example/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/example/api/__init__.py b/apps/example/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/example/api/v1/__init__.py b/apps/example/api/v1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/example/api/v1/serializers.py b/apps/example/api/v1/serializers.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/example/api/v1/urls.py b/apps/example/api/v1/urls.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/example/api/v1/views.py b/apps/example/api/v1/views.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/example/api/v2/__init__.py b/apps/example/api/v2/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/example/api/v2/serializers.py b/apps/example/api/v2/serializers.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/example/api/v2/urls.py b/apps/example/api/v2/urls.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/example/api/v2/views.py b/apps/example/api/v2/views.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/example/apps.py b/apps/example/apps.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/example/fixtures/.gitkeep b/apps/example/fixtures/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/apps/example/management/__init__.py b/apps/example/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/example/management/commands/__init__.py b/apps/example/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/example/management/commands/command.py b/apps/example/management/commands/command.py new file mode 100644 index 0000000..c68face --- /dev/null +++ b/apps/example/management/commands/command.py @@ -0,0 +1 @@ +# Your custom management commands go here. diff --git a/apps/example/migrations/__init__.py b/apps/example/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/example/models.py b/apps/example/models.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/example/services.py b/apps/example/services.py new file mode 100644 index 0000000..93888af --- /dev/null +++ b/apps/example/services.py @@ -0,0 +1 @@ +# Your services go here diff --git a/apps/example/tests/test_common_services.py b/apps/example/tests/test_common_services.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/example/urls.py b/apps/example/urls.py new file mode 100644 index 0000000..0865d84 --- /dev/null +++ b/apps/example/urls.py @@ -0,0 +1 @@ +# Your urls go here diff --git a/common/constants.py b/common/constants.py new file mode 100644 index 0000000..7f37291 --- /dev/null +++ b/common/constants.py @@ -0,0 +1 @@ +# Add your common constants here that are common for all other apps. \ No newline at end of file diff --git a/common/generics.py b/common/generics.py new file mode 100644 index 0000000..e69de29 diff --git a/common/helpers.py b/common/helpers.py new file mode 100644 index 0000000..e69de29 diff --git a/common/mixins.py b/common/mixins.py new file mode 100644 index 0000000..e69de29 diff --git a/common/models.py b/common/models.py new file mode 100644 index 0000000..49854e1 --- /dev/null +++ b/common/models.py @@ -0,0 +1,84 @@ +# Standard library imports +import uuid + +# Django imports +from django.conf import settings +from django.db import models +from django.utils.translation import gettext_lazy as _ + +# Django Rest Framework imports + +# Third party imports + +# Local imports + +user_model = settings.AUTH_USER_MODEL + + +class IsDeletedManager(models.Manager): + def get_queryset(self): + return super().get_queryset().filter(is_deleted=False) + + +class BaseModel(models.Model): + """ + Tracks instance creations, updates, and (soft) deletions. + """ + + uuid = models.UUIDField( + verbose_name=_("UUID"), unique=True, default=uuid.uuid4, editable=False + ) + + created_by = models.ForeignKey( + to=user_model, + verbose_name=_("Created by"), + null=True, + blank=True, + related_name="%(class)s_created", + on_delete=models.SET_NULL, + ) + + created_at = models.DateTimeField( + verbose_name=_("Created at"), + auto_now_add=True, + editable=False, + db_index=True, + ) + + updated_by = models.ForeignKey( + to=user_model, + verbose_name=_("Updated by"), + null=True, + blank=True, + related_name="%(class)s_updated", + on_delete=models.SET_NULL, + ) + + updated_at = models.DateTimeField( + verbose_name=_("Updated at"), + auto_now=True, + null=True, + blank=True, + ) + + deleted_by = models.ForeignKey( + to=user_model, + verbose_name=_("Deleted by"), + null=True, + blank=True, + related_name="%(class)s_deleted", + on_delete=models.SET_NULL, + ) + + deleted_at = models.DateTimeField( + verbose_name=_("Deleted at"), null=True, blank=True, default=None + ) + + is_deleted = models.BooleanField(verbose_name=_("Is deleted"), default=False) + + objects = IsDeletedManager() + + objects_all = models.Manager() + + class Meta: + abstract = True diff --git a/common/serializers.py b/common/serializers.py new file mode 100644 index 0000000..4b5022f --- /dev/null +++ b/common/serializers.py @@ -0,0 +1,43 @@ +from typing import List + +from rest_framework.request import Request +from rest_framework.serializers import ModelSerializer, Serializer + + +class DynamicFieldsModelSerializer(ModelSerializer): + """ + A ModelSerializer that takes an additional `fields` argument that + controls which fields should be displayed. + """ + + def __init__(self, *args, **kwargs): + # Don't pass the 'fields' arg up to the superclass + fields = kwargs.pop("fields", None) + + # Instantiate the superclass normally + super().__init__(*args, **kwargs) + + if fields is not None: + # Drop any fields that are not specified in the `fields` argument. + allowed = set(fields) + existing = set(self.fields) + for field_name in existing - allowed: + self.fields.pop(field_name) + + +def create_validated_instance(serializer: Serializer, request: Request): + serializer = serializer(data=request.data, context={"request": request}) + serializer.is_valid(raise_exception=True) + return serializer.save(), serializer.validated_data + + +def get_validated_data( + serializer: Serializer, request: Request, fields: List[str] = None +): + if fields and issubclass(serializer, DynamicFieldsModelSerializer): + serializer = serializer(fields=fields, data=request.data) + else: + serializer = serializer(data=request.data) + + serializer.is_valid(raise_exception=True) + return serializer.validated_data diff --git a/config/__init__.py b/config/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/config/asgi.py b/config/asgi.py new file mode 100644 index 0000000..872a3e7 --- /dev/null +++ b/config/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for config project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/4.0/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings") + +application = get_asgi_application() diff --git a/config/settings.py b/config/settings.py new file mode 100644 index 0000000..c4e5457 --- /dev/null +++ b/config/settings.py @@ -0,0 +1,151 @@ +""" +Django settings for config project. + +Generated by 'django-admin startproject' using Django 4.0.1. + +For more information on this file, see +https://docs.djangoproject.com/en/4.0/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/4.0/ref/settings/ +""" + +import os +from pathlib import Path + +import sentry_sdk +from sentry_sdk.integrations.django import DjangoIntegration + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = "django-insecure-$227hjjmuq2e!)o^@2v#+(-=@$v362o@8g#s9!2)tjn1)1a" + +ALLOWED_HOSTS = [] + +DEFAULT_APPS = [ + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", +] + +THIRD_PARTY_APPS = [ + "rest_framework", + "corsheaders", + "storages", + "django_filters", +] + +SELF_APPS = [ + "apps.core", +] + +INSTALLED_APPS = DEFAULT_APPS + THIRD_PARTY_APPS + SELF_APPS + +MIDDLEWARE = [ + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "corsheaders.middleware.CorsMiddleware", # Third-Party Middleware + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", +] + +ROOT_URLCONF = "config.urls" + +TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + ], + }, + }, +] + +WSGI_APPLICATION = "config.wsgi.application" + +# Database +# https://docs.djangoproject.com/en/5.0/ref/settings/#databases + +DATABASES = { + "default": { + "ENGINE": "django.db.backends.postgresql_psycopg2", + "NAME": "your-db-name", + "USER": "your-db-user", + "PASSWORD": "your-db-user-password", + "HOST": "your-db-host", + "PORT": "your-db-port", + } +} + + +# Password validation +# https://docs.djangoproject.com/en/4.0/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/4.0/topics/i18n/ + +LANGUAGE_CODE = "en-us" + +TIME_ZONE = "UTC" + +USE_I18N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/4.0/howto/static-files/ + +STATIC_URL = "static/" + +# Default primary key field type +# https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" + +DEBUG = True + +### --- SENTRY SETTINGS --- ### +if not DEBUG: + sentry_sdk.init( + dsn=os.environ("SENTRY_DSN"), + integrations=[ + DjangoIntegration(), + ], + traces_sample_rate=0.5, + send_default_pii=False, + ) diff --git a/config/urls.py b/config/urls.py new file mode 100644 index 0000000..05c1a05 --- /dev/null +++ b/config/urls.py @@ -0,0 +1,8 @@ +from django.urls import ( + include, + path, +) + +urlpatterns = [ + path("", include("apps.core.urls")), +] diff --git a/config/wsgi.py b/config/wsgi.py new file mode 100644 index 0000000..55ec642 --- /dev/null +++ b/config/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for config project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/4.0/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings") + +application = get_wsgi_application() diff --git a/deployments/django-project/Dockerfile b/deployments/django-project/Dockerfile new file mode 100644 index 0000000..e69de29 diff --git a/deployments/docker-compose.yml b/deployments/docker-compose.yml new file mode 100644 index 0000000..e69de29 diff --git a/deployments/nginx/Dockerfile b/deployments/nginx/Dockerfile new file mode 100644 index 0000000..e69de29 diff --git a/deployments/nginx/default.conf b/deployments/nginx/default.conf new file mode 100644 index 0000000..b2ab801 --- /dev/null +++ b/deployments/nginx/default.conf @@ -0,0 +1,33 @@ +# Gunicorn server +upstream django { + server domain.com:9000; +} + +# Redirect all requests on the www subdomain to the root domain +server { + listen 80; + server_name www.domain.com; + rewrite ^/(.*) http://domain.com/$1 permanent; +} + +# Serve static files and redirect any other request to Gunicorn +server { + listen 80; + server_name domain.com; + root /var/www/domain.com/; + access_log /var/log/nginx/domain.com.access.log; + error_log /var/log/nginx/domain.com.error.log; + + # Check if a file exists at /var/www/domain/ for the incoming request. + # If it doesn't proxy to Gunicorn/Django. + try_files $uri @django; + + # Setup named location for Django requests and handle proxy details + location @django { + proxy_pass http://django; + proxy_redirect off; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } +} \ No newline at end of file diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/deployment.md b/docs/deployment.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/swagger.yaml b/docs/swagger.yaml new file mode 100644 index 0000000..83ccdf1 --- /dev/null +++ b/docs/swagger.yaml @@ -0,0 +1,728 @@ +openapi: 3.0.1 +info: + title: Swagger Petstore + description: 'This is a sample server Petstore server. You can find out more about Swagger + at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For + this sample, you can use the api key `special-key` to test the authorization filters.' + termsOfService: http://swagger.io/terms/ + contact: + email: apiteam@swagger.io + license: + name: Apache 2.0 + url: http://www.apache.org/licenses/LICENSE-2.0.html + version: 1.0.0 +externalDocs: + description: Find out more about Swagger + url: http://swagger.io +servers: +- url: https://petstore.swagger.io/v2 +- url: http://petstore.swagger.io/v2 +tags: +- name: pet + description: Everything about your Pets + externalDocs: + description: Find out more + url: http://swagger.io +- name: store + description: Access to Petstore orders +- name: user + description: Operations about user + externalDocs: + description: Find out more about our store + url: http://swagger.io +paths: + /pet: + put: + tags: + - pet + summary: Update an existing pet + operationId: updatePet + requestBody: + description: Pet object that needs to be added to the store + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + required: true + responses: + 400: + description: Invalid ID supplied + content: {} + 404: + description: Pet not found + content: {} + 405: + description: Validation exception + content: {} + security: + - petstore_auth: + - write:pets + - read:pets + x-codegen-request-body-name: body + post: + tags: + - pet + summary: Add a new pet to the store + operationId: addPet + requestBody: + description: Pet object that needs to be added to the store + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + required: true + responses: + 405: + description: Invalid input + content: {} + security: + - petstore_auth: + - write:pets + - read:pets + x-codegen-request-body-name: body + /pet/findByStatus: + get: + tags: + - pet + summary: Finds Pets by status + description: Multiple status values can be provided with comma separated strings + operationId: findPetsByStatus + parameters: + - name: status + in: query + description: Status values that need to be considered for filter + required: true + style: form + explode: true + schema: + type: array + items: + type: string + default: available + enum: + - available + - pending + - sold + responses: + 200: + description: successful operation + content: + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + 400: + description: Invalid status value + content: {} + security: + - petstore_auth: + - write:pets + - read:pets + /pet/findByTags: + get: + tags: + - pet + summary: Finds Pets by tags + description: Muliple tags can be provided with comma separated strings. Use tag1, + tag2, tag3 for testing. + operationId: findPetsByTags + parameters: + - name: tags + in: query + description: Tags to filter by + required: true + style: form + explode: true + schema: + type: array + items: + type: string + responses: + 200: + description: successful operation + content: + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + 400: + description: Invalid tag value + content: {} + deprecated: true + security: + - petstore_auth: + - write:pets + - read:pets + /pet/{petId}: + get: + tags: + - pet + summary: Find pet by ID + description: Returns a single pet + operationId: getPetById + parameters: + - name: petId + in: path + description: ID of pet to return + required: true + schema: + type: integer + format: int64 + responses: + 200: + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/json: + schema: + $ref: '#/components/schemas/Pet' + 400: + description: Invalid ID supplied + content: {} + 404: + description: Pet not found + content: {} + security: + - api_key: [] + post: + tags: + - pet + summary: Updates a pet in the store with form data + operationId: updatePetWithForm + parameters: + - name: petId + in: path + description: ID of pet that needs to be updated + required: true + schema: + type: integer + format: int64 + requestBody: + content: + application/x-www-form-urlencoded: + schema: + properties: + name: + type: string + description: Updated name of the pet + status: + type: string + description: Updated status of the pet + responses: + 405: + description: Invalid input + content: {} + security: + - petstore_auth: + - write:pets + - read:pets + delete: + tags: + - pet + summary: Deletes a pet + operationId: deletePet + parameters: + - name: api_key + in: header + schema: + type: string + - name: petId + in: path + description: Pet id to delete + required: true + schema: + type: integer + format: int64 + responses: + 400: + description: Invalid ID supplied + content: {} + 404: + description: Pet not found + content: {} + security: + - petstore_auth: + - write:pets + - read:pets + /pet/{petId}/uploadImage: + post: + tags: + - pet + summary: uploads an image + operationId: uploadFile + parameters: + - name: petId + in: path + description: ID of pet to update + required: true + schema: + type: integer + format: int64 + requestBody: + content: + multipart/form-data: + schema: + properties: + additionalMetadata: + type: string + description: Additional data to pass to server + file: + type: string + description: file to upload + format: binary + responses: + 200: + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + security: + - petstore_auth: + - write:pets + - read:pets + /store/inventory: + get: + tags: + - store + summary: Returns pet inventories by status + description: Returns a map of status codes to quantities + operationId: getInventory + responses: + 200: + description: successful operation + content: + application/json: + schema: + type: object + additionalProperties: + type: integer + format: int32 + security: + - api_key: [] + /store/order: + post: + tags: + - store + summary: Place an order for a pet + operationId: placeOrder + requestBody: + description: order placed for purchasing the pet + content: + '*/*': + schema: + $ref: '#/components/schemas/Order' + required: true + responses: + 200: + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Order' + application/json: + schema: + $ref: '#/components/schemas/Order' + 400: + description: Invalid Order + content: {} + x-codegen-request-body-name: body + /store/order/{orderId}: + get: + tags: + - store + summary: Find purchase order by ID + description: For valid response try integer IDs with value >= 1 and <= 10. Other + values will generated exceptions + operationId: getOrderById + parameters: + - name: orderId + in: path + description: ID of pet that needs to be fetched + required: true + schema: + maximum: 10.0 + minimum: 1.0 + type: integer + format: int64 + responses: + 200: + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Order' + application/json: + schema: + $ref: '#/components/schemas/Order' + 400: + description: Invalid ID supplied + content: {} + 404: + description: Order not found + content: {} + delete: + tags: + - store + summary: Delete purchase order by ID + description: For valid response try integer IDs with positive integer value. Negative + or non-integer values will generate API errors + operationId: deleteOrder + parameters: + - name: orderId + in: path + description: ID of the order that needs to be deleted + required: true + schema: + minimum: 1.0 + type: integer + format: int64 + responses: + 400: + description: Invalid ID supplied + content: {} + 404: + description: Order not found + content: {} + /user: + post: + tags: + - user + summary: Create user + description: This can only be done by the logged in user. + operationId: createUser + requestBody: + description: Created user object + content: + '*/*': + schema: + $ref: '#/components/schemas/User' + required: true + responses: + default: + description: successful operation + content: {} + x-codegen-request-body-name: body + /user/createWithArray: + post: + tags: + - user + summary: Creates list of users with given input array + operationId: createUsersWithArrayInput + requestBody: + description: List of user object + content: + '*/*': + schema: + type: array + items: + $ref: '#/components/schemas/User' + required: true + responses: + default: + description: successful operation + content: {} + x-codegen-request-body-name: body + /user/createWithList: + post: + tags: + - user + summary: Creates list of users with given input array + operationId: createUsersWithListInput + requestBody: + description: List of user object + content: + '*/*': + schema: + type: array + items: + $ref: '#/components/schemas/User' + required: true + responses: + default: + description: successful operation + content: {} + x-codegen-request-body-name: body + /user/login: + get: + tags: + - user + summary: Logs user into the system + operationId: loginUser + parameters: + - name: username + in: query + description: The user name for login + required: true + schema: + type: string + - name: password + in: query + description: The password for login in clear text + required: true + schema: + type: string + responses: + 200: + description: successful operation + headers: + X-Rate-Limit: + description: calls per hour allowed by the user + schema: + type: integer + format: int32 + X-Expires-After: + description: date in UTC when token expires + schema: + type: string + format: date-time + content: + application/xml: + schema: + type: string + application/json: + schema: + type: string + 400: + description: Invalid username/password supplied + content: {} + /user/logout: + get: + tags: + - user + summary: Logs out current logged in user session + operationId: logoutUser + responses: + default: + description: successful operation + content: {} + /user/{username}: + get: + tags: + - user + summary: Get user by user name + operationId: getUserByName + parameters: + - name: username + in: path + description: 'The name that needs to be fetched. Use user1 for testing. ' + required: true + schema: + type: string + responses: + 200: + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/User' + application/json: + schema: + $ref: '#/components/schemas/User' + 400: + description: Invalid username supplied + content: {} + 404: + description: User not found + content: {} + put: + tags: + - user + summary: Updated user + description: This can only be done by the logged in user. + operationId: updateUser + parameters: + - name: username + in: path + description: name that need to be updated + required: true + schema: + type: string + requestBody: + description: Updated user object + content: + '*/*': + schema: + $ref: '#/components/schemas/User' + required: true + responses: + 400: + description: Invalid user supplied + content: {} + 404: + description: User not found + content: {} + x-codegen-request-body-name: body + delete: + tags: + - user + summary: Delete user + description: This can only be done by the logged in user. + operationId: deleteUser + parameters: + - name: username + in: path + description: The name that needs to be deleted + required: true + schema: + type: string + responses: + 400: + description: Invalid username supplied + content: {} + 404: + description: User not found + content: {} +components: + schemas: + Order: + type: object + properties: + id: + type: integer + format: int64 + petId: + type: integer + format: int64 + quantity: + type: integer + format: int32 + shipDate: + type: string + format: date-time + status: + type: string + description: Order Status + enum: + - placed + - approved + - delivered + complete: + type: boolean + default: false + xml: + name: Order + Category: + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + xml: + name: Category + User: + type: object + properties: + id: + type: integer + format: int64 + username: + type: string + firstName: + type: string + lastName: + type: string + email: + type: string + password: + type: string + phone: + type: string + userStatus: + type: integer + description: User Status + format: int32 + xml: + name: User + Tag: + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + xml: + name: Tag + Pet: + required: + - name + - photoUrls + type: object + properties: + id: + type: integer + format: int64 + category: + $ref: '#/components/schemas/Category' + name: + type: string + example: doggie + photoUrls: + type: array + xml: + name: photoUrl + wrapped: true + items: + type: string + tags: + type: array + xml: + name: tag + wrapped: true + items: + $ref: '#/components/schemas/Tag' + status: + type: string + description: pet status in the store + enum: + - available + - pending + - sold + xml: + name: Pet + ApiResponse: + type: object + properties: + code: + type: integer + format: int32 + type: + type: string + message: + type: string + securitySchemes: + petstore_auth: + type: oauth2 + flows: + implicit: + authorizationUrl: http://petstore.swagger.io/oauth/dialog + scopes: + write:pets: modify pets in your account + read:pets: read your pets + api_key: + type: apiKey + name: api_key + in: header diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100644 index 0000000..3153bd3 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,6 @@ +#!/bin/sh +set -e +python manage.py migrate --noinput +python manage.py collectstatic --noinput + +exec "$@" diff --git a/locale/.gitkeep b/locale/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/logs/.gitkeep b/logs/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/manage.py b/manage.py new file mode 100644 index 0000000..bb457a2 --- /dev/null +++ b/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'Rasaddam_Backend.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() diff --git a/media/.gitkeep b/media/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..06932a7 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,16 @@ +[tool.black] +line-length = 79 +target-version = ['py312'] +include = '\.pyi?$' +extend-exclude = 'migrations/*' + + +[tool.ruff.lint.isort] +case-sensitive = true +force-single-line = true +section-order = ["future", "standard-library", "first-party", "django", "rest_framework", "third-party", "local-folder"] + + +[tool.ruff.lint.isort.sections] +"django" = ["django"] +"rest_framework" = ["rest_framework"] diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..7a4fb9b --- /dev/null +++ b/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +DJANGO_SETTINGS_MODULE = config.settings +python_files = tests.py test_*.py *_tests.py diff --git a/requirements/common.txt b/requirements/common.txt new file mode 100644 index 0000000..4fe8495 --- /dev/null +++ b/requirements/common.txt @@ -0,0 +1,9 @@ +Django +djangorestframework +django-cors-headers +django-filter +django-storages +psycopg2-binary==2.9.9 +Pillow +sentry-sdk +gunicorn diff --git a/requirements/local.txt b/requirements/local.txt new file mode 100644 index 0000000..29890c1 --- /dev/null +++ b/requirements/local.txt @@ -0,0 +1,4 @@ +-r common.txt +ruff +pre-commit +pytest-django diff --git a/requirements/production.txt b/requirements/production.txt new file mode 100644 index 0000000..c3899b0 --- /dev/null +++ b/requirements/production.txt @@ -0,0 +1 @@ +-r common.txt \ No newline at end of file diff --git a/scripts/.gitkeep b/scripts/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/static/.gitkeep b/static/.gitkeep new file mode 100644 index 0000000..e69de29