summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRoger Gonzalez <roger@rogs.me>2023-10-21 11:56:07 -0300
committerRoger Gonzalez <roger@rogs.me>2023-10-21 11:56:07 -0300
commit7081ed74c2012821510b2659fdda3e7069b13ea3 (patch)
tree7523bb09abf6172ede4e7c484d58625ecb667b19
Initial commit
-rw-r--r--.flake83
-rw-r--r--.github/workflows/lint.yml64
-rw-r--r--.gitignore271
-rw-r--r--.gitlab-ci.yml25
-rw-r--r--.pre-commit-config.yaml48
-rw-r--r--Dockerfile14
-rw-r--r--Makefile40
-rw-r--r--app/__init__.py0
-rw-r--r--app/asgi.py16
-rw-r--r--app/settings.py123
-rw-r--r--app/tests.py11
-rw-r--r--app/urls.py22
-rw-r--r--app/wsgi.py16
-rw-r--r--docker-compose.yml29
-rwxr-xr-xmanage.py22
-rw-r--r--pylintrc65
-rw-r--r--pyproject.toml53
-rw-r--r--requirements.txt28
18 files changed, 850 insertions, 0 deletions
diff --git a/.flake8 b/.flake8
new file mode 100644
index 0000000..f54c19e
--- /dev/null
+++ b/.flake8
@@ -0,0 +1,3 @@
+[flake8]
+max-line-length = 121
+select = A,E,F,W,I,T4,C4,B,T,R,FS,ECE
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
new file mode 100644
index 0000000..6a8127f
--- /dev/null
+++ b/.github/workflows/lint.yml
@@ -0,0 +1,64 @@
+name: Lint
+
+on:
+ push:
+ branches:
+ - master
+ pull_request:
+ branches:
+ - master
+
+jobs:
+ lint:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout Repository
+ uses: actions/checkout@v2
+
+ - name: Set up Python
+ uses: actions/setup-python@v2
+ with:
+ python-version: 3.11.5
+
+ - name: Install Dependencies
+ run: pip install -r requirements.txt
+
+ - name: Run Black
+ run: |
+ python -m black --check .
+
+ - name: Run isort
+ run: |
+ python -m isort --check .
+
+ - name: Run Flake8
+ run: |
+ python -m flake8
+
+ - name: Run Pylint
+ run: |
+ python -m pylint app
+
+ - name: Run mypy
+ run: |
+ python -m mypy .
+
+ testing:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout Repository
+ uses: actions/checkout@v2
+
+ - name: Set up Python
+ uses: actions/setup-python@v2
+ with:
+ python-version: 3.11.5
+
+ - name: Install Dependencies
+ run: pip install -r requirements.txt
+
+ - name: Run Tests
+ run: |
+ python -m pytest
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1f5151f
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,271 @@
+# Created by https://www.toptal.com/developers/gitignore/api/python,django
+# Edit at https://www.toptal.com/developers/gitignore?templates=python,django
+
+### Django ###
+*.log
+*.pot
+*.pyc
+__pycache__/
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+media
+
+# If your build process includes running collectstatic, then you probably don't need or want to include staticfiles/
+# in your Git repository. Update and uncomment the following line accordingly.
+# <django-project-name>/staticfiles/
+
+### Django.Python Stack ###
+# Byte-compiled / optimized / DLL files
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+cover/
+
+# Translations
+*.mo
+
+# Django stuff:
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+.pybuilder/
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+# For a library or package, you might want to ignore these files since the code is
+# intended to run in multiple environments; otherwise, check them in:
+# .python-version
+
+# pipenv
+# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+# However, in case of collaboration, if having platform-specific dependencies or dependencies
+# having no cross-platform support, pipenv may install dependencies that don't work, or not
+# install all needed dependencies.
+#Pipfile.lock
+
+# poetry
+# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
+# This is especially recommended for binary packages to ensure reproducibility, and is more
+# commonly ignored for libraries.
+# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
+#poetry.lock
+
+# pdm
+# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
+#pdm.lock
+# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
+# in version control.
+# https://pdm.fming.dev/#use-with-ide
+.pdm.toml
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# Cython debug symbols
+cython_debug/
+
+# PyCharm
+# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
+# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
+# and can be added to the global gitignore or merged into this file. For a more nuclear
+# option (not recommended) you can uncomment the following to ignore the entire idea folder.
+#.idea/
+
+### Python ###
+# Byte-compiled / optimized / DLL files
+
+# C extensions
+
+# Distribution / packaging
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+
+# Installer logs
+
+# Unit test / coverage reports
+
+# Translations
+
+# Django stuff:
+
+# Flask stuff:
+
+# Scrapy stuff:
+
+# Sphinx documentation
+
+# PyBuilder
+
+# Jupyter Notebook
+
+# IPython
+
+# pyenv
+# For a library or package, you might want to ignore these files since the code is
+# intended to run in multiple environments; otherwise, check them in:
+# .python-version
+
+# pipenv
+# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+# However, in case of collaboration, if having platform-specific dependencies or dependencies
+# having no cross-platform support, pipenv may install dependencies that don't work, or not
+# install all needed dependencies.
+
+# poetry
+# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
+# This is especially recommended for binary packages to ensure reproducibility, and is more
+# commonly ignored for libraries.
+# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
+
+# pdm
+# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
+# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
+# in version control.
+# https://pdm.fming.dev/#use-with-ide
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
+
+# Celery stuff
+
+# SageMath parsed files
+
+# Environments
+
+# Spyder project settings
+
+# Rope project settings
+
+# mkdocs documentation
+
+# mypy
+
+# Pyre type checker
+
+# pytype static type analyzer
+
+# Cython debug symbols
+
+# PyCharm
+# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
+# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
+# and can be added to the global gitignore or merged into this file. For a more nuclear
+# option (not recommended) you can uncomment the following to ignore the entire idea folder.
+
+### Python Patch ###
+# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
+poetry.toml
+
+# ruff
+.ruff_cache/
+
+# LSP config files
+pyrightconfig.json
+
+# End of https://www.toptal.com/developers/gitignore/api/python,django
+
+# Own ignores
+
+# Emacs stuff
+*.el
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000..2145814
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,25 @@
+stages:
+ - lint
+ - test
+
+lint:
+ stage: lint
+ script:
+ - apt-get update -qy
+ - apt-get install -y python3 python3-pip
+ - python3 -m pip install --upgrade pip
+ - python3 -m pip install -r requirements.txt
+ - python3 -m black --check .
+ - python3 -m isort --check .
+ - python3 -m flake8
+ - python3 -m pylint app
+ - python3 -m mypy .
+
+test:
+ stage: test
+ script:
+ - apt-get update -qy
+ - apt-get install -y python3 python3-pip
+ - python3 -m pip install --upgrade pip
+ - python3 -m pip install -r requirements.txt
+ - python3 -m pytest
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 0000000..19ce43c
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,48 @@
+exclude: ^(.bzr|\.direnv|\.eggs|\.git|\.hg|\.mypy_cache|\.nox|\.pants\.d|\.svn|\.tox|\.venv|_build|buck-out|build|dist|node_modules|venv|\.idea|dockerdata|static|.*\b(migrations)\b.*)
+repos:
+- repo: https://github.com/pycqa/isort
+ rev: 5.12.0
+ hooks:
+ - id: isort
+- repo: https://github.com/ambv/black
+ rev: 22.3.0
+ hooks:
+ - id: black
+ language_version: python3
+- repo: https://github.com/pycqa/flake8
+ rev: 6.1.0
+ hooks:
+ - id: flake8
+ additional_dependencies:
+ - flake8-bugbear==23.9.16
+ - flake8-builtins==2.1.0
+ - flake8-comprehensions==3.14.0
+ - flake8-tidy-imports==4.10.0
+ - flake8-eradicate==1.5.0
+ - flake8-print==5.0.0
+ - flake8-return==1.2.0
+ - flake8-use-fstring==1.4
+- repo: local
+ hooks:
+ - id: pylint
+ name: pylint
+ entry: pylint
+ language: system
+ types: [python]
+ args:
+ [
+ "-rn", # Only display messages
+ "-sn", # Don't display the score
+ "--rcfile=pylintrc", # Link to your config file
+ "--load-plugins=pylint_django",
+ "--django-settings-module=app.settings"
+ ]
+- repo: https://github.com/pre-commit/mirrors-mypy
+ rev: v1.6.1
+ hooks:
+ - id: mypy
+ args: [--strict]
+ additional_dependencies:
+ - django-stubs==4.2.5
+ - django-environ==0.11.2
+ - django_extensions==3.2.3
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..316456f
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,14 @@
+FROM python:latest
+
+RUN apt update && \
+ apt upgrade -y && \
+ rm -rf /var/lib/apt/lists/*
+
+WORKDIR /app
+COPY . .
+
+RUN python -m pip install --upgrade pip
+
+RUN pip install --no-cache-dir -r requirements.txt
+
+CMD ["python", "manage.py", "runserver_plus", "--nopin", "0.0.0.0:8000"]
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..50253ff
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,40 @@
+build:
+ docker-compose build --pull
+
+makemigrations:
+ docker-compose run --rm app python manage.py makemigrations
+
+migrate:
+ docker-compose run --rm app python manage.py migrate
+
+createsu:
+ docker-compose run --rm app python manage.py createsuperuser
+
+init: build migrate
+
+# Development
+
+up:
+ docker-compose up
+
+django-manage:
+ docker-compose run app python manage.py $(command)
+
+shell:
+ docker-compose run app python manage.py shell_plus
+
+full: init up
+
+kill:
+ docker-compose down
+
+cleanup:
+ docker-compose down -v
+
+test:
+ docker-compose run --rm app sh -c 'python -m pytest -k "$(target)" --exitfirst'
+
+# Utils
+
+bash:
+ docker-compose run --rm app bash
diff --git a/app/__init__.py b/app/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/app/__init__.py
diff --git a/app/asgi.py b/app/asgi.py
new file mode 100644
index 0000000..3296932
--- /dev/null
+++ b/app/asgi.py
@@ -0,0 +1,16 @@
+"""
+ASGI config for app 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.2/howto/deployment/asgi/
+"""
+
+import os
+
+from django.core.asgi import get_asgi_application
+
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "app.settings")
+
+application = get_asgi_application()
diff --git a/app/settings.py b/app/settings.py
new file mode 100644
index 0000000..77ebf47
--- /dev/null
+++ b/app/settings.py
@@ -0,0 +1,123 @@
+"""
+Django settings for app project.
+
+Generated by 'django-admin startproject' using Django 4.2.6.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/4.2/topics/settings/
+
+For the full list of settings and their values, see
+https://docs.djangoproject.com/en/4.2/ref/settings/
+"""
+
+from pathlib import Path
+
+import environ
+
+# Build paths inside the project like this: BASE_DIR / 'subdir'.
+BASE_DIR = Path(__file__).resolve().parent.parent
+
+env = environ.Env()
+
+
+# Quick-start development settings - unsuitable for production
+# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
+
+# SECURITY WARNING: keep the secret key used in production secret!
+SECRET_KEY = "django-insecure-*d23*f@dv2%dch^l#&(0*$ko*xmj-)wc-&s9_uln$1rvkp+kvm"
+
+# 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",
+ "django_extensions",
+]
+
+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 = "app.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 = "app.wsgi.application"
+
+
+# Database
+# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
+
+DATABASES = {"default": env.db("POSTGRES_CONNECTION_STRING", default="sqlite:///db.sqlite3")}
+
+
+# Password validation
+# https://docs.djangoproject.com/en/4.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/4.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/4.2/howto/static-files/
+
+STATIC_URL = "static/"
+
+# Default primary key field type
+# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
+
+DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
diff --git a/app/tests.py b/app/tests.py
new file mode 100644
index 0000000..3c8dd57
--- /dev/null
+++ b/app/tests.py
@@ -0,0 +1,11 @@
+"""Mock test to test pytest."""
+
+
+def inc(number: int) -> int:
+ """Mock test to test pytest."""
+ return number + 1
+
+
+def test_answer() -> None:
+ """Mock test to test pytest."""
+ assert inc(3) == 4
diff --git a/app/urls.py b/app/urls.py
new file mode 100644
index 0000000..65cf0d6
--- /dev/null
+++ b/app/urls.py
@@ -0,0 +1,22 @@
+"""
+URL configuration for app project.
+
+The `urlpatterns` list routes URLs to views. For more information please see:
+ https://docs.djangoproject.com/en/4.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/app/wsgi.py b/app/wsgi.py
new file mode 100644
index 0000000..cbdf434
--- /dev/null
+++ b/app/wsgi.py
@@ -0,0 +1,16 @@
+"""
+WSGI config for app 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.2/howto/deployment/wsgi/
+"""
+
+import os
+
+from django.core.wsgi import get_wsgi_application
+
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "app.settings")
+
+application = get_wsgi_application()
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..6eeed16
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,29 @@
+version: '4.1'
+
+services:
+ app:
+ build:
+ dockerfile: Dockerfile
+ context: .
+ volumes:
+ - .:/app
+ environment:
+ POSTGRES_CONNECTION_STRING: "postgresql://app:app@db:5432/app"
+ depends_on:
+ db:
+ condition: service_healthy
+ ports:
+ - 8000:8000
+
+ db:
+ image: postgres
+ restart: always
+ environment:
+ POSTGRES_USER: app
+ POSTGRES_PASSWORD: app
+ POSTGRES_DB: app
+ healthcheck:
+ test: ["CMD-SHELL", "pg_isready -U app -d app"]
+ interval: 3s
+ timeout: 10s
+ retries: 3
diff --git a/manage.py b/manage.py
new file mode 100755
index 0000000..53b83e4
--- /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() -> None:
+ """Run administrative tasks."""
+ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "app.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/pylintrc b/pylintrc
new file mode 100644
index 0000000..e7d0578
--- /dev/null
+++ b/pylintrc
@@ -0,0 +1,65 @@
+[MAIN]
+persistent=yes
+ignore=migrations,manage.py
+load-plugins=pylint_django
+django-settings-module=app.settings
+
+[REPORTS]
+msg-template="{path}:{line}: [{msg_id}({symbol}), {obj}] {msg}"
+
+[BASIC]
+no-docstring-rgx=__.*__|_.*
+class-rgx=[A-Z_][a-zA-Z0-9_]+$
+function-rgx=[a-zA_][a-zA-Z0-9_]{2,70}$
+method-rgx=[a-z_][a-zA-Z0-9_]{2,70}$
+const-rgx=(([A-Z_][A-Z0-9_]*)|([a-z_][a-z0-9_]*)|(__.*__)|register|urlpatterns)$
+good-names=_,i,j,k,v,e,qs,pk,setUp,tearDown
+variable-rgx=[a-z_][a-z0-9_]{1,40}$
+argument-rgx=[a-z_][a-z0-9_]{1,30}$
+inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
+bad-names=foo,bar,baz
+
+[TYPECHECK]
+
+# Tells whether missing members accessed in mixin class should be ignored. A
+# mixin class is detected if its name ends with "mixin" (case insensitive).
+ignore-mixin-members=yes
+
+# List of classes names for which member attributes should not be checked
+# (useful for classes with attributes dynamically set).
+ignored-classes=SQLObject
+
+# List of members which are set dynamically and missed by pylint inference
+# system, and so shouldn't trigger E0201 when accessed.
+generated-members=objects,DoesNotExist,id,pk,_meta,base_fields,context
+
+# List of method names used to declare (i.e. assign) instance attributes
+defining-attr-methods=__init__,__new__,setUp
+
+[VARIABLES]
+init-import=no
+dummy-variables-rgx=_|dummy
+
+[SIMILARITIES]
+min-similarity-lines=6
+ignore-comments=yes
+ignore-docstrings=yes
+
+[MISCELLANEOUS]
+notes=FIXME,XXX,TODO
+
+[FORMAT]
+max-line-length=121
+max-module-lines=1000
+indent-string=' '
+
+[DESIGN]
+max-args=10
+max-locals=15
+max-returns=6
+max-branches=12
+max-statements=50
+max-parents=15
+max-attributes=10
+min-public-methods=0
+max-public-methods=50
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..09e6750
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,53 @@
+[tool.black]
+line-length = 121
+target-version = ['py310']
+include = '\.py$'
+exclude = '''
+/(
+ \.bzr
+ | \.direnv
+ | \.eggs
+ | \.git
+ | \.hg
+ | \.mypy_cache
+ | \.nox
+ | \.pants\.d
+ | \.svn
+ | \.tox
+ | \.venv
+ | _build
+ | buck-out
+ | build
+ | dist
+ | node_modules
+ | venv
+ | \.idea
+ | dockerdata
+ | static
+ | \.git
+ | migrations
+)/
+'''
+
+[tool.isort]
+# these are black-compatible settings
+multi_line_output = 3
+include_trailing_comma = true
+force_grid_wrap = 0
+use_parentheses = true
+ensure_newline_before_comments = true
+line_length = 121
+skip = "dockerdata,.idea,static"
+filter_files = true
+
+[tool.mypy]
+plugins = ["mypy_django_plugin.main"]
+ignore_missing_imports = true
+strict = true
+
+[tool.django-stubs]
+django_settings_module = "app.settings"
+
+[tool.pytest.ini_options]
+python_files = ["tests.py", "test_*.py", "*_tests.py"]
+addopts = "--ds=app.settings --reuse-db --diff-width=121 -n 6 --dist=loadscope --durations=5 --disable-warnings --cov=."
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..9aafc63
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,28 @@
+Django==4.2.6
+djangorestframework==3.14.0
+django-environ==0.11.2
+django-extensions==3.2.3
+psycopg2-binary
+pylint-django==2.5.3
+pre-commit==3.5.0
+django-stubs[compatible-mypy]==4.2.5
+Werkzeug==3.0.0
+flake8-bugbear==23.9.16
+flake8-builtins==2.1.0
+flake8-comprehensions==3.14.0
+flake8-tidy-imports==4.10.0
+flake8-eradicate==1.5.0
+flake8-print==5.0.0
+flake8-return==1.2.0
+flake8-use-fstring==1.4
+black==23.10.0
+
+# Testing
+pytest==7.4.2
+pytest-sugar==0.9.7
+django-coverage-plugin==3.1.0
+pytest-django==4.5.2
+pytest-cov==4.1.0
+pytest-randomly==3.15.0
+pytest-clarity==1.0.1
+pytest-xdist==3.3.1