content: # ==== Project Metadata ====
  [project]
  name = "django-template"
  version = "1.0.0"
  description = "Django REST API template with modular app structure"
  readme = "README.md"
  requires-python = ">=3.13"
  dependencies = [
      # Python Utilities
      "python-slugify==8.0.4",
      "Pillow==11.3.0",
      "argon2-cffi==25.1.0",
      "pydantic==2.11.10",
      "pydantic-settings==2.11.0",
      "boto3==1.40.45",
      "pycountry==24.6.1",
      "py-moneyed==3.0",
      # Redis.
      "redis==6.4.0",
      "hiredis==3.2.1",
      "psycopg[pool]==3.2.10",
      # Django and Extensions
      "django==5.2.7",
      "django-model-utils==5.0.0",
      "django-redis==6.0.0",
      "django-import-export==4.3.10",
      "openpyxl==3.1.5",
      "django-phonenumber-field[phonenumbers]==8.2.0",
      "faker-e164==0.1.0",
      "django-polymorphic==4.1.0",
      "django-unfold==0.67.0",
      "fcm-django==2.3.1",
      "django-solo==2.4.0",
      "rules==3.5",
      "django-imagekit==5.0.0",
      "django-filter==25.2",
      "django-money==3.5.4",
      "django-cors-headers==4.9.0",
      "dj-database-url==3.0.1",
      "django_model_suite==0.2.1",
      "django-simple-history==3.10.1",
      "django-modeltranslation==0.19.17",
      "django-soft-delete==1.0.21",
      "django-guardian==3.2.0",
      "django-location-field==2.7.3",
      "django-map-widgets==0.5.1",
      "django-constance==4.3.2",
      # Django REST Framework and Extensions
      "djangorestframework==3.16.1",
      "djangorestframework-gis==1.2.0",
      "djangorestframework-simplejwt==5.5.1",
      "drf-spectacular==0.28.0",
      "drf-standardized-errors[openapi]==0.15.0",
      "djangorestframework-camel-case==1.4.2",
      # OAuth
      "dj-rest-auth[with_social]==7.0.1",  # IMPORTANT: 7.0.1 isn't working correctly
  ]
  
  # ==== Dependency Groups (PEP 735) ====
  [dependency-groups]
  # Production dependencies (installed in production environment)
  production = [
      "gunicorn==23.0.0",
      "Collectfasta==3.3.1",
      "sentry-sdk[django]==2.39.0",
      "django-storages[s3]==1.14.6",
      "gevent==25.9.1",
  ]
  
  # Development dependencies (installed in local development)
  dev = [
      "Werkzeug[watchdog]==3.1.3",
      "ipdb==0.13.13",
      "watchfiles==1.1.0",
      "django-debug-toolbar==6.0.0",
      "django-extensions==4.1",
      "pytest==8.4.2",
      "pytest-sugar==1.1.1",
      "pytest-django==4.11.1",
      "pytest-factoryboy==2.8.1",
      "factory-boy==3.3.3",
      "coverage==7.10.7",
      "django-coverage-plugin==3.1.1",
      "mypy==1.18.2",
      "django-stubs[compatible-mypy]==5.2.6",
      "djangorestframework-stubs[compatible-mypy]==3.16.4",
      "ruff==0.13.3",
      "djlint==1.36.4",
      "pre-commit==4.3.0",
  ]
  
  # ==== pytest ====
  [tool.pytest.ini_options]
  minversion = "6.0"
  addopts = """
      --ds=config.settings.test
      --import-mode=importlib
      --tb=short
      -x
      -v
  """
  python_files = [
      "tests.py",
      "test_*.py",
  ]
  console_output_style = "classic"
  markers = [
      "slow: marks tests as slow (integration tests, API calls)",
      "unit: fast isolated unit tests",
      "integration: tests requiring DB/external services",
  ]
  
  # Key options:
  # -v         : Verbose - show test names (what's being tested)
  # --tb=short : Concise traceback (not overwhelming)
  # -x         : Stop on first failure (fix one issue at a time)
  #
  # Override when needed:
  #   pytest -q           (quiet - less detail)
  #   pytest -vv          (very verbose - more details)
  #   pytest --tb=line    (one line per failure)
  #   pytest --lf         (rerun last failed only)
  
  # ==== Coverage ====
  [tool.coverage.run]
  include = ["config/**", "apps/**", "common/**"]
  omit = ["*/migrations/*", "*/tests/*"]
  plugins = ["django_coverage_plugin"]
  
  # ==== mypy ====
  [tool.mypy]
  ignore_missing_imports = true
  check_untyped_defs = true
  warn_unused_ignores = true
  warn_redundant_casts = true
  warn_unused_configs = true
  allow_untyped_globals = true
  allow_redefinition = true
  pretty = true
  show_column_numbers = true
  show_error_codes = true
  show_error_context = true
  show_traceback = true
  plugins = [
      "mypy_django_plugin.main",
      "mypy_drf_plugin.main",
  ]
  
  # Django migrations should not produce any errors:
  [[tool.mypy.overrides]]
  module = "*.migrations.*"
  ignore_errors = true
  
  [tool.django-stubs]
  django_settings_module = "config.settings.test"
  
  # ==== djLint ====
  [tool.djlint]
  blank_line_after_tag = "load,extends"
  close_void_tags = true
  format_css = true
  format_js = true
  # TODO: remove T002 when fixed https://github.com/djlint/djLint/issues/687
  ignore = "H006,H030,H031,T002"
  include = "H017,H035"
  indent = 2
  max_line_length = 119
  profile = "django"
  
  [tool.djlint.css]
  indent_size = 2
  
  [tool.djlint.js]
  indent_size = 2
  
  [tool.ruff]
  target-version = "py313"
  # Exclude a variety of commonly ignored directories.
  extend-exclude = [
      "*/migrations/*.py",
      "staticfiles/*",
  ]
  
  [tool.ruff.lint]
  select = [
      "F",
      "E",
      "W",
      "C90",
      "I",
      "N",
      "UP",
      "YTT",
      # "ANN", # flake8-annotations: we should support this in the future but 100+ errors atm
      "ASYNC",
      "S",
      "BLE",
      "FBT",
      "B",
      "A",
      "COM",
      "C4",
      "DTZ",
      "T10",
      "DJ",
      "EM",
      "EXE",
      "FA",
      'ISC',
      "ICN",
      "G",
      'INP',
      'PIE',
      "T20",
      'PYI',
      'PT',
      "Q",
      "RSE",
      "RET",
      "SLF",
      "SLOT",
      "SIM",
      "TID",
      "TCH",
      "INT",
      # "ARG", # Unused function argument
      "PTH",
      "ERA",
      "PD",
      "PGH",
      "PL",
      "TRY",
      "FLY",
      # "NPY",
      # "AIR",
      "PERF",
      # "FURB",
      # "LOG",
      "RUF",
  ]
  
  ignore = [
      "C901",
      "PLC0415",
      "S101", # Use of assert detected
      "RUF012", # Mutable class attributes should be annotated with `typing.ClassVar`
      "SIM102", # sometimes it's better to nest
      "COM812", # Missing trailing comma in Python 3.6+
      "ISC001", # Single line implicit string concatenation
      "RET506",
      "S603",
      "TRY003", # ensure using execption for consistance in messages
      "ERA001", # no commented out code
      "EM101", # avoid using string directly inside the exceptions
      "PGH004", # use correct noqa code
      "N806", # lower case ensuring
      "FBT002", # Boolean default positional argument in function definition
      "DJ007", # don't use __all__ in model form
      "RET505", # Unnecessary `else` after `return` statement
      "E501", # too long line
      "F405",
      "SLF001", # access private member
      "SIM103", # return condition directly
      "EM102", # avoid using string directly inside the exceptions
      "TRY201", # Use `raise` without specifying exception name
      "TID252", # ignore absoulte import
      "BLE001",
      "PLR0913",
      "RET504",
      "G004"
  ]
  
  # The fixes in extend-unsafe-fixes will require
  # provide the `--unsafe-fixes` flag when fixing.
  extend-unsafe-fixes = []
  
  [tool.ruff.lint.isort]
  force-single-line = true
  
  [tool.ruff.lint.per-file-ignores]
  "scripts/*.py" = ["INP001"]
  
  [tool.semantic_release]
  branch = "main"
  commit_message = "chore: bump version {version}"
  