feat(core): add core module with mixins, services, and background jobs

- Add Model Mixins: TimestampMixin, SoftDeleteMixin, AuditMixin, etc.
- Add Base Services: BaseService, BulkOperationsMixin, QueryOptimizerMixin
- Add Base ViewSets with bulk operations
- Add BackgroundJob model for Celery task tracking
- Add BaseAppCommand for management commands
- Add permissions, pagination, filters, cache, logging
- Migrate tests to factory_boy + faker
- Add CHANGELOG.md
- 297 tests passing
This commit is contained in:
2026-01-21 11:47:26 +01:00
parent 06b30fca02
commit f121445313
72 changed files with 9258 additions and 594 deletions

View File

@@ -45,6 +45,8 @@ dependencies = [
"coreapi>=2.3.3",
"django-rest-swagger>=2.2.0",
"model-bakery>=1.17.0",
"faker>=40.1.2",
"factory-boy>=3.3.0",
]
[project.optional-dependencies]
@@ -52,43 +54,39 @@ dev = [
# WSGI server
"gunicorn==21.2.0",
"gevent==23.9.1",
# Development
"django-extensions==3.2.3",
"werkzeug==3.0.1",
"django-debug-toolbar==4.2.0",
# Testing
"pytest==7.4.4",
"pytest-django==4.7.0",
"pytest-cov==4.1.0",
"factory-boy==3.3.0",
"coverage==7.4.0",
# Linters and formatters
"flake8==6.1.0",
"black==23.12.1",
"isort==5.13.2",
"ruff==0.1.14",
# Documentation
"sphinx==7.2.6",
"sphinx-rtd-theme==2.0.0",
# Monitoring
"flower==2.0.1",
# CLI tools
"click==8.1.7",
"typer==0.9.0",
# Debugging (removed due to compatibility issues)
# "ipdb==0.13.13",
# "pdbpp==0.10.3",
# Additional tools
"watchdog==3.0.0",
# Pre-commit hooks
"pre-commit==3.6.0",
]
@@ -100,32 +98,101 @@ build-backend = "setuptools.build_meta"
[tool.setuptools]
packages = ["src"]
# ==================================================================================
# PYTEST CONFIGURATION
# ==================================================================================
[tool.pytest.ini_options]
DJANGO_SETTINGS_MODULE = "config.settings.test"
python_paths = ["src"]
testpaths = ["tests"]
addopts = [
"--verbose",
"--tb=short",
"--reuse-db",
"--nomigrations",
"--strict-markers",
"--strict-config",
"--color=yes",
]
markers = [
"slow: marks tests as slow (deselect with '-m \"not slow\"')",
"integration: marks tests as integration tests",
"unit: marks tests as unit tests",
"models: marks tests for models",
"views: marks tests for views",
"serializers: marks tests for serializers",
"services: marks tests for services",
"factories: marks tests for factories",
]
filterwarnings = [
"ignore::django.utils.deprecation.RemovedInDjango40Warning",
"ignore::django.utils.deprecation.RemovedInDjango41Warning",
"ignore::DeprecationWarning",
"ignore::PendingDeprecationWarning",
]
norecursedirs = [
".git",
".venv",
"__pycache__",
"*.egg-info",
".pytest_cache",
"node_modules",
"migrations",
]
# ==================================================================================
# COVERAGE CONFIGURATION
# ==================================================================================
[tool.coverage.run]
source = ["src"]
omit = [
"*/migrations/*",
"*/tests/*",
"*/venv/*",
"*/virtualenv/*",
"*/site-packages/*",
"manage.py",
"*/settings/*",
"*/config/wsgi.py",
"*/config/asgi.py",
"*/__pycache__/*",
]
branch = true
relative_files = true
[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
"def __repr__",
"if self.debug:",
"if settings.DEBUG",
"raise AssertionError",
"raise NotImplementedError",
"if 0:",
"if __name__ == .__main__.:",
"class .*\\bProtocol\\):",
"@(abc\\.)?abstractmethod",
]
show_missing = true
skip_covered = false
precision = 2
[tool.coverage.html]
directory = "htmlcov"
[tool.coverage.xml]
output = "coverage.xml"
# ==================================================================================
# RUFF CONFIGURATION (Linting and Code Quality)
# ==================================================================================
[tool.ruff]
# Enable pycodestyle (`E`) and Pyflakes (`F`) codes by default.
lint.select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # pyflakes
"I", # isort
"C", # mccabe
"B", # flake8-bugbear
"Q", # flake8-quotes
"DJ", # flake8-django
]
line-length = 88
target-version = "py311"
lint.extend-ignore = [
"E501", # line too long, handled by formatter
"DJ01", # Missing docstring (too strict for Django)
]
# Allow autofix for all enabled rules (when `--fix`) is provided.
lint.fixable = ["ALL"]
lint.unfixable = []
# Allow unused variables when underscore-prefixed.
lint.dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
# Exclude a variety of commonly ignored directories.
exclude = [
".bzr",
".direnv",
@@ -152,11 +219,40 @@ exclude = [
"*/__pycache__/*",
]
# Same as Black.
line-length = 88
[tool.ruff.lint]
# Enable pycodestyle (`E`) and Pyflakes (`F`) codes by default.
select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # pyflakes
"I", # isort
"C", # mccabe
"B", # flake8-bugbear
"Q", # flake8-quotes
"DJ", # flake8-django
"UP", # pyupgrade
"S", # bandit security
"T20", # flake8-print
"SIM", # flake8-simplify
]
# Assume Python 3.11.
target-version = "py311"
extend-ignore = [
"E501", # line too long, handled by formatter
"DJ01", # Missing docstring (too strict for Django)
"DJ001", # null=True on string fields (architectural decision)
"F403", # star imports (common in Django settings)
"F405", # name may be undefined from star imports (Django settings)
"E402", # module level import not at top (Django settings)
"S101", # Use of assert (common in tests)
"T201", # print statements (useful for debugging)
]
# Allow autofix for all enabled rules (when `--fix`) is provided.
fixable = ["ALL"]
unfixable = []
# Allow unused variables when underscore-prefixed.
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
[tool.ruff.lint.mccabe]
# Unlike Flake8, default to a complexity level of 10.
@@ -165,10 +261,19 @@ max-complexity = 10
[tool.ruff.lint.per-file-ignores]
# Ignore `E402` (import violations) in all `__init__.py` files
"__init__.py" = ["E402"]
# Ignore star imports and related errors in settings
"src/config/settings/*" = ["F403", "F405", "E402"]
# Ignore star imports in test runner files
"check_tests.py" = ["F403"]
"run_tests.py" = ["F403"]
"run_tests_simple.py" = ["F403"]
# Ignore complexity issues in tests
"tests/*" = ["C901"]
"**/test_*" = ["C901"]
"**/tests.py" = ["C901"]
"tests/*" = ["C901", "S101"]
"**/test_*" = ["C901", "S101"]
"**/tests.py" = ["C901", "S101"]
# Ignore security warnings in test factories
"tests/**/factories.py" = ["S311"]
"**/factories.py" = ["S311"]
[tool.ruff.format]
# Like Black, use double quotes for strings.
@@ -183,6 +288,132 @@ skip-magic-trailing-comma = false
# Like Black, automatically detect the appropriate line ending.
line-ending = "auto"
# ==================================================================================
# BLACK CONFIGURATION (Code Formatting)
# ==================================================================================
[tool.black]
line-length = 88
target-version = ['py311']
include = '\.pyi?$'
extend-exclude = '''
/(
# directories
\.eggs
| \.git
| \.hg
| \.mypy_cache
| \.tox
| \.venv
| _build
| buck-out
| build
| dist
| migrations
)/
'''
# ==================================================================================
# ISORT CONFIGURATION (Import Sorting)
# ==================================================================================
[tool.isort]
profile = "black"
multi_line_output = 3
line_length = 88
include_trailing_comma = true
force_grid_wrap = 0
use_parentheses = true
ensure_newline_before_comments = true
src_paths = ["src"]
skip = ["migrations"]
known_django = ["django"]
known_third_party = [
"celery",
"redis",
"requests",
"pandas",
"numpy",
"scrapy",
"selenium",
"beautifulsoup4",
"rest_framework",
"django_filters",
"corsheaders",
"drf_yasg",
"model_bakery",
"factory",
"pytest",
]
sections = [
"FUTURE",
"STDLIB",
"DJANGO",
"THIRDPARTY",
"FIRSTPARTY",
"LOCALFOLDER",
]
# ==================================================================================
# MYPY CONFIGURATION (Type Checking)
# ==================================================================================
[tool.mypy]
python_version = "3.11"
check_untyped_defs = true
ignore_missing_imports = true
warn_unused_ignores = true
warn_redundant_casts = true
warn_unused_configs = true
warn_return_any = true
warn_unreachable = true
strict_optional = true
no_implicit_reexport = true
show_error_codes = true
plugins = ["mypy_django_plugin.main"]
[[tool.mypy.overrides]]
module = "*.migrations.*"
ignore_errors = true
[[tool.mypy.overrides]]
module = "tests.*"
disallow_untyped_defs = false
[tool.django-stubs]
django_settings_module = "config.settings.development"
# ==================================================================================
# BANDIT CONFIGURATION (Security)
# ==================================================================================
[tool.bandit]
exclude_dirs = ["tests", "migrations"]
tests = ["B201", "B301"]
skips = ["B101", "B601"]
# ==================================================================================
# PYLINT CONFIGURATION
# ==================================================================================
[tool.pylint.messages_control]
disable = [
"C0114", # missing-module-docstring
"C0115", # missing-class-docstring
"C0116", # missing-function-docstring
"R0903", # too-few-public-methods (Django models)
"R0901", # too-many-ancestors (Django views)
"W0613", # unused-argument (Django views)
"C0103", # invalid-name (Django field names)
]
[tool.pylint.format]
max-line-length = 88
[tool.pylint.design]
max-args = 10
max-locals = 25
max-returns = 10
max-branches = 20
# ==================================================================================
# DEPENDENCY GROUPS (Alternative to optional-dependencies)
# ==================================================================================
[dependency-groups]
dev = [
"gunicorn==21.2.0",
@@ -204,8 +435,38 @@ dev = [
"flower==2.0.1",
"click==8.1.7",
"typer==0.9.0",
"ipdb==0.13.13",
"pdbpp==0.10.3",
"watchdog==3.0.0",
"pre-commit==3.6.0",
"mypy==1.8.0",
"django-stubs==4.2.7",
"types-requests==2.31.0.20240125",
"bandit==1.7.5",
]
test = [
"pytest==7.4.4",
"pytest-django==4.7.0",
"pytest-cov==4.1.0",
"pytest-xdist==3.5.0",
"pytest-mock==3.12.0",
"factory-boy==3.3.0",
"model-bakery>=1.17.0",
"coverage==7.4.0",
]
docs = [
"sphinx==7.2.6",
"sphinx-rtd-theme==2.0.0",
"sphinx-autodoc-typehints==1.25.2",
"myst-parser==2.0.0",
]
lint = [
"ruff==0.1.14",
"black==23.12.1",
"isort==5.13.2",
"mypy==1.8.0",
"django-stubs==4.2.7",
"bandit==1.7.5",
"pre-commit==3.6.0",
]