drf-to-mkdoc 0.1.5__tar.gz → 0.1.8__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of drf-to-mkdoc might be problematic. Click here for more details.

Files changed (74) hide show
  1. drf_to_mkdoc-0.1.8/.pre-commit-config.yaml +8 -0
  2. {drf_to_mkdoc-0.1.5 → drf_to_mkdoc-0.1.8}/PKG-INFO +3 -25
  3. {drf_to_mkdoc-0.1.5 → drf_to_mkdoc-0.1.8}/README.md +2 -24
  4. {drf_to_mkdoc-0.1.5 → drf_to_mkdoc-0.1.8}/conf/defaults.py +1 -0
  5. {drf_to_mkdoc-0.1.5 → drf_to_mkdoc-0.1.8}/conf/settings.py +3 -0
  6. drf_to_mkdoc-0.1.8/docs/mkdocs.yml +70 -0
  7. {drf_to_mkdoc-0.1.5 → drf_to_mkdoc-0.1.8}/drf_to_mkdoc/__init__.py +1 -1
  8. {drf_to_mkdoc-0.1.5 → drf_to_mkdoc-0.1.8}/drf_to_mkdoc/apps.py +6 -2
  9. {drf_to_mkdoc-0.1.5 → drf_to_mkdoc-0.1.8}/drf_to_mkdoc/conf/defaults.py +0 -1
  10. {drf_to_mkdoc-0.1.5 → drf_to_mkdoc-0.1.8}/drf_to_mkdoc/conf/settings.py +11 -5
  11. {drf_to_mkdoc-0.1.5 → drf_to_mkdoc-0.1.8}/drf_to_mkdoc/management/commands/build_docs.py +61 -19
  12. {drf_to_mkdoc-0.1.5 → drf_to_mkdoc-0.1.8}/drf_to_mkdoc/management/commands/generate_docs.py +5 -5
  13. {drf_to_mkdoc-0.1.5 → drf_to_mkdoc-0.1.8}/drf_to_mkdoc/management/commands/generate_model_docs.py +37 -7
  14. drf_to_mkdoc-0.1.8/drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/base.css +84 -0
  15. drf_to_mkdoc-0.1.8/drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/endpoint-content.css +165 -0
  16. drf_to_mkdoc-0.1.8/drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/endpoints-grid.css +194 -0
  17. drf_to_mkdoc-0.1.8/drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/responsive.css +96 -0
  18. {drf_to_mkdoc-0.1.5/static → drf_to_mkdoc-0.1.8/drf_to_mkdoc/static/drf-to-mkdoc}/stylesheets/endpoints/theme-toggle.css +12 -0
  19. drf_to_mkdoc-0.1.8/drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/variables.css +73 -0
  20. drf_to_mkdoc-0.1.8/drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/models/animations.css +25 -0
  21. drf_to_mkdoc-0.1.8/drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/models/base.css +83 -0
  22. drf_to_mkdoc-0.1.8/drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/models/model-cards.css +126 -0
  23. drf_to_mkdoc-0.1.8/drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/models/model-tables.css +57 -0
  24. drf_to_mkdoc-0.1.8/drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/models/responsive.css +51 -0
  25. drf_to_mkdoc-0.1.8/drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/models/variables.css +46 -0
  26. {drf_to_mkdoc-0.1.5 → drf_to_mkdoc-0.1.8}/drf_to_mkdoc/utils/common.py +31 -29
  27. drf_to_mkdoc-0.1.5/drf_to_mkdoc/utils/endpoint_generator.py → drf_to_mkdoc-0.1.8/drf_to_mkdoc/utils/endpoint_detail_generator.py +214 -384
  28. drf_to_mkdoc-0.1.8/drf_to_mkdoc/utils/endpoint_list_generator.py +234 -0
  29. {drf_to_mkdoc-0.1.5 → drf_to_mkdoc-0.1.8}/drf_to_mkdoc/utils/extractors/query_parameter_extractors.py +15 -16
  30. drf_to_mkdoc-0.1.5/drf_to_mkdoc/utils/model_generator.py → drf_to_mkdoc-0.1.8/drf_to_mkdoc/utils/model_detail_generator.py +20 -51
  31. drf_to_mkdoc-0.1.8/drf_to_mkdoc/utils/model_list_generator.py +67 -0
  32. {drf_to_mkdoc-0.1.5 → drf_to_mkdoc-0.1.8}/drf_to_mkdoc.egg-info/PKG-INFO +3 -25
  33. drf_to_mkdoc-0.1.8/drf_to_mkdoc.egg-info/SOURCES.txt +64 -0
  34. drf_to_mkdoc-0.1.8/pyproject.toml +214 -0
  35. drf_to_mkdoc-0.1.8/setup.py +3 -0
  36. drf_to_mkdoc-0.1.5/drf_to_mkdoc.egg-info/SOURCES.txt +0 -54
  37. drf_to_mkdoc-0.1.5/pyproject.toml +0 -80
  38. drf_to_mkdoc-0.1.5/static/stylesheets/endpoints/base.css +0 -15
  39. drf_to_mkdoc-0.1.5/static/stylesheets/endpoints/endpoint-content.css +0 -48
  40. drf_to_mkdoc-0.1.5/static/stylesheets/endpoints/endpoints-grid.css +0 -75
  41. drf_to_mkdoc-0.1.5/static/stylesheets/endpoints/responsive.css +0 -89
  42. drf_to_mkdoc-0.1.5/static/stylesheets/endpoints/variables.css +0 -30
  43. drf_to_mkdoc-0.1.5/static/stylesheets/extra.css +0 -358
  44. {drf_to_mkdoc-0.1.5 → drf_to_mkdoc-0.1.8}/.github/workflows/publish.yaml +0 -0
  45. {drf_to_mkdoc-0.1.5 → drf_to_mkdoc-0.1.8}/CONTRIBUTING.md +0 -0
  46. {drf_to_mkdoc-0.1.5 → drf_to_mkdoc-0.1.8}/LICENSE +0 -0
  47. {drf_to_mkdoc-0.1.5 → drf_to_mkdoc-0.1.8}/MANIFEST.in +0 -0
  48. {drf_to_mkdoc-0.1.5 → drf_to_mkdoc-0.1.8}/conf/__init__.py +0 -0
  49. {drf_to_mkdoc-0.1.5 → drf_to_mkdoc-0.1.8}/docs/customizing_endpoints.md +0 -0
  50. {drf_to_mkdoc-0.1.5 → drf_to_mkdoc-0.1.8}/docs/serving_mkdocs_with_django.md +0 -0
  51. {drf_to_mkdoc-0.1.5 → drf_to_mkdoc-0.1.8}/drf_to_mkdoc/conf/__init__.py +0 -0
  52. {drf_to_mkdoc-0.1.5 → drf_to_mkdoc-0.1.8}/drf_to_mkdoc/management/__init__.py +0 -0
  53. {drf_to_mkdoc-0.1.5 → drf_to_mkdoc-0.1.8}/drf_to_mkdoc/management/commands/__init__.py +0 -0
  54. {drf_to_mkdoc-0.1.5 → drf_to_mkdoc-0.1.8}/drf_to_mkdoc/management/commands/generate_doc_json.py +0 -0
  55. {drf_to_mkdoc-0.1.5 → drf_to_mkdoc-0.1.8}/drf_to_mkdoc/management/commands/update_doc_schema.py +0 -0
  56. {drf_to_mkdoc-0.1.5/static → drf_to_mkdoc-0.1.8/drf_to_mkdoc/static/drf-to-mkdoc}/javascripts/endpoints-filter.js +0 -0
  57. {drf_to_mkdoc-0.1.5/static → drf_to_mkdoc-0.1.8/drf_to_mkdoc/static/drf-to-mkdoc}/stylesheets/endpoints/accessibility.css +0 -0
  58. {drf_to_mkdoc-0.1.5/static → drf_to_mkdoc-0.1.8/drf_to_mkdoc/static/drf-to-mkdoc}/stylesheets/endpoints/animations.css +0 -0
  59. {drf_to_mkdoc-0.1.5/static → drf_to_mkdoc-0.1.8/drf_to_mkdoc/static/drf-to-mkdoc}/stylesheets/endpoints/badges.css +0 -0
  60. {drf_to_mkdoc-0.1.5/static → drf_to_mkdoc-0.1.8/drf_to_mkdoc/static/drf-to-mkdoc}/stylesheets/endpoints/filter-section.css +0 -0
  61. {drf_to_mkdoc-0.1.5/static → drf_to_mkdoc-0.1.8/drf_to_mkdoc/static/drf-to-mkdoc}/stylesheets/endpoints/fixes.css +0 -0
  62. {drf_to_mkdoc-0.1.5/static → drf_to_mkdoc-0.1.8/drf_to_mkdoc/static/drf-to-mkdoc}/stylesheets/endpoints/layout.css +0 -0
  63. {drf_to_mkdoc-0.1.5/static → drf_to_mkdoc-0.1.8/drf_to_mkdoc/static/drf-to-mkdoc}/stylesheets/endpoints/loading.css +0 -0
  64. {drf_to_mkdoc-0.1.5/static → drf_to_mkdoc-0.1.8/drf_to_mkdoc/static/drf-to-mkdoc}/stylesheets/endpoints/sections.css +0 -0
  65. {drf_to_mkdoc-0.1.5/static → drf_to_mkdoc-0.1.8/drf_to_mkdoc/static/drf-to-mkdoc}/stylesheets/endpoints/stats.css +0 -0
  66. {drf_to_mkdoc-0.1.5/static → drf_to_mkdoc-0.1.8/drf_to_mkdoc/static/drf-to-mkdoc}/stylesheets/endpoints/tags.css +0 -0
  67. {drf_to_mkdoc-0.1.5 → drf_to_mkdoc-0.1.8}/drf_to_mkdoc/utils/__init__.py +0 -0
  68. {drf_to_mkdoc-0.1.5 → drf_to_mkdoc-0.1.8}/drf_to_mkdoc/utils/extractors/__init__.py +0 -0
  69. {drf_to_mkdoc-0.1.5 → drf_to_mkdoc-0.1.8}/drf_to_mkdoc/utils/md_generators/__init__.py +0 -0
  70. {drf_to_mkdoc-0.1.5 → drf_to_mkdoc-0.1.8}/drf_to_mkdoc/utils/md_generators/query_parameters_generators.py +0 -0
  71. {drf_to_mkdoc-0.1.5 → drf_to_mkdoc-0.1.8}/drf_to_mkdoc.egg-info/dependency_links.txt +0 -0
  72. {drf_to_mkdoc-0.1.5 → drf_to_mkdoc-0.1.8}/drf_to_mkdoc.egg-info/requires.txt +0 -0
  73. {drf_to_mkdoc-0.1.5 → drf_to_mkdoc-0.1.8}/drf_to_mkdoc.egg-info/top_level.txt +0 -0
  74. {drf_to_mkdoc-0.1.5 → drf_to_mkdoc-0.1.8}/setup.cfg +0 -0
@@ -0,0 +1,8 @@
1
+ repos:
2
+ - repo: https://github.com/astral-sh/ruff-pre-commit
3
+ rev: v0.12.4
4
+ hooks:
5
+ - id: ruff
6
+ args: [ --fix ]
7
+ - id: ruff-format
8
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: drf-to-mkdoc
3
- Version: 0.1.5
3
+ Version: 0.1.8
4
4
  Summary: Generate Markdown API docs from Django/DRF OpenAPI schema for MkDocs
5
5
  Author-email: Hossein Shayesteh <shayestehhs1@gmail.com>
6
6
  Maintainer-email: Hossein Shayesteh <shayestehhs1@gmail.com>
@@ -101,30 +101,8 @@ DRF_TO_MKDOC = {
101
101
  }
102
102
  ```
103
103
 
104
- 2. **Create MkDocs configuration**:
105
-
106
- ```yaml
107
- # mkdocs.yml
108
- site_name: Your API Documentation
109
- theme:
110
- name: material
111
- features:
112
- - navigation.tabs
113
- - navigation.sections
114
- - navigation.expand
115
- - search.highlight
116
- - search.share
117
-
118
- plugins:
119
- - search
120
-
121
- nav:
122
- - Home: index.md
123
- - About: about.md
124
- - Models: models/index.md
125
- - API Endpoints: endpoints/index.md
126
- - Pagination: pagination.md
127
- ```
104
+ 2. **Create MkDocs configuration**:
105
+ Copy the [`docs/mkdocs.yml`](docs/mkdocs.yml) file to your project root and customize it as needed.
128
106
 
129
107
  3. **Build documentation**:
130
108
 
@@ -52,30 +52,8 @@ DRF_TO_MKDOC = {
52
52
  }
53
53
  ```
54
54
 
55
- 2. **Create MkDocs configuration**:
56
-
57
- ```yaml
58
- # mkdocs.yml
59
- site_name: Your API Documentation
60
- theme:
61
- name: material
62
- features:
63
- - navigation.tabs
64
- - navigation.sections
65
- - navigation.expand
66
- - search.highlight
67
- - search.share
68
-
69
- plugins:
70
- - search
71
-
72
- nav:
73
- - Home: index.md
74
- - About: about.md
75
- - Models: models/index.md
76
- - API Endpoints: endpoints/index.md
77
- - Pagination: pagination.md
78
- ```
55
+ 2. **Create MkDocs configuration**:
56
+ Copy the [`docs/mkdocs.yml`](docs/mkdocs.yml) file to your project root and customize it as needed.
79
57
 
80
58
  3. **Build documentation**:
81
59
 
@@ -7,4 +7,5 @@ DEFAULTS = {
7
7
  },
8
8
  "INDEX_TEMPLATE": "index.md",
9
9
  "AUTH_TEMPLATE": "auth.md",
10
+ "PATH_PARAM_SUBSTITUTOR": None,
10
11
  }
@@ -1,6 +1,8 @@
1
1
  from django.conf import settings
2
+
2
3
  from .defaults import DEFAULTS
3
4
 
5
+
4
6
  class DocBuilderSettings:
5
7
  def __init__(self, user_settings_key="DOC_BUILDER", defaults=None):
6
8
  self.user_settings_key = user_settings_key
@@ -15,4 +17,5 @@ class DocBuilderSettings:
15
17
  def __getattr__(self, key):
16
18
  return self.get(key)
17
19
 
20
+
18
21
  drf_to_mkdoc_settings = DocBuilderSettings(defaults=DEFAULTS)
@@ -0,0 +1,70 @@
1
+ site_name: API Documentation
2
+ site_description: Comprehensive API documentation
3
+ site_url: https://example.com/docs/
4
+
5
+ theme:
6
+ name: material
7
+ palette:
8
+ - scheme: default
9
+ primary: blue
10
+ accent: blue
11
+ toggle:
12
+ icon: material/brightness-7
13
+ name: Switch to dark mode
14
+ - scheme: slate
15
+ primary: blue
16
+ accent: blue
17
+ toggle:
18
+ icon: material/brightness-4
19
+ name: Switch to light mode
20
+ features:
21
+ - navigation.tabs
22
+ - navigation.tabs.sticky
23
+ - navigation.sections
24
+ - navigation.expand
25
+ - navigation.path
26
+ - navigation.indexes
27
+ - toc.integrate
28
+ - toc.follow
29
+ - search.suggest
30
+ - search.highlight
31
+ - content.code.copy
32
+ - content.code.annotate
33
+
34
+ plugins:
35
+ - search
36
+
37
+ nav:
38
+ - Home: index.md
39
+ - Models: models/
40
+ - API Endpoints: endpoints/
41
+
42
+ markdown_extensions:
43
+ - pymdownx.highlight:
44
+ anchor_linenums: true
45
+ - pymdownx.inlinehilite
46
+ - pymdownx.snippets
47
+ - pymdownx.superfences:
48
+ custom_fences:
49
+ - name: mermaid
50
+ class: mermaid
51
+ format: !!python/name:pymdownx.superfences.fence_code_format
52
+ - pymdownx.tabbed:
53
+ alternate_style: true
54
+ - pymdownx.tasklist:
55
+ custom_checkbox: true
56
+ - admonition
57
+ - pymdownx.details
58
+ - attr_list
59
+ - md_in_html
60
+ - tables
61
+ - toc:
62
+ permalink: true
63
+
64
+ extra:
65
+ social:
66
+ - icon: fontawesome/brands/github
67
+ link: https://github.com/ShayestehHS/drf-to-mkdoc/
68
+ generator: false
69
+
70
+ copyright: Copyright &copy; 2025 drf-to-mkdoc Project
@@ -4,4 +4,4 @@ DRF to MkDocs - Generate Markdown API docs from Django/DRF OpenAPI schema for Mk
4
4
 
5
5
  __version__ = "0.1.0"
6
6
  __author__ = "ShayestehHs"
7
- __email__ = "shayestehhs1@gmail.com"
7
+ __email__ = "shayestehhs1@gmail.com"
@@ -1,15 +1,19 @@
1
+ import logging
2
+
1
3
  from django.apps import AppConfig
2
4
 
5
+ logger = logging.getLogger()
6
+
3
7
 
4
8
  class DrfToMkdocConfig(AppConfig):
5
9
  default_auto_field = "django.db.models.BigAutoField"
6
10
  name = "drf_to_mkdoc"
7
11
  verbose_name = "DRF to MkDocs Documentation Generator"
8
-
12
+
9
13
  def ready(self):
10
14
  """Initialize the app when Django starts."""
11
15
  # Import management commands to register them
12
16
  try:
13
17
  import drf_to_mkdoc.management.commands # noqa
14
18
  except ImportError:
15
- pass
19
+ logger.exception("Failed to import drf_to_mkdoc commands")
@@ -5,7 +5,6 @@ DEFAULTS = {
5
5
  "MODEL_DOCS_FILE": "docs/model-docs.json", # Path to model documentation JSON file
6
6
  "DOC_CONFIG_FILE": "docs/configs/doc_config.json", # Path to documentation configuration file
7
7
  "CUSTOM_SCHEMA_FILE": "docs/configs/custom_schema.json", # Path to custom schema file
8
-
9
8
  # Django apps - required, no default
10
9
  "DJANGO_APPS": None, # List of Django app names to process
11
10
  }
@@ -1,8 +1,11 @@
1
1
  from django.conf import settings
2
+
2
3
  from drf_to_mkdoc.conf.defaults import DEFAULTS
3
4
 
5
+
4
6
  class DRFToMkDocSettings:
5
7
  required_settings = ["DJANGO_APPS"]
8
+ project_settings = {"PROJECT_NAME": "drf-to-mkdoc"}
6
9
 
7
10
  def __init__(self, user_settings_key="DRF_TO_MKDOC", defaults=None):
8
11
  self.user_settings_key = user_settings_key
@@ -11,16 +14,18 @@ class DRFToMkDocSettings:
11
14
 
12
15
  def get(self, key):
13
16
  if key not in self.defaults:
17
+ if key in self.project_settings:
18
+ return self.project_settings[key]
14
19
  raise AttributeError(f"Invalid DRF_TO_MKDOC setting: '{key}'")
15
-
20
+
16
21
  value = self._user_settings.get(key, self.defaults[key])
17
-
22
+
18
23
  if value is None and key in self.required_settings:
19
24
  raise ValueError(
20
25
  f"DRF_TO_MKDOC setting '{key}' is required but not configured. "
21
26
  f"Please add it to your Django settings under {self.user_settings_key}."
22
27
  )
23
-
28
+
24
29
  return value
25
30
 
26
31
  def __getattr__(self, key):
@@ -28,17 +33,18 @@ class DRFToMkDocSettings:
28
33
 
29
34
  def validate_required_settings(self):
30
35
  missing_settings = []
31
-
36
+
32
37
  for setting in self.required_settings:
33
38
  try:
34
39
  self.get(setting)
35
40
  except ValueError:
36
41
  missing_settings.append(setting)
37
-
42
+
38
43
  if missing_settings:
39
44
  raise ValueError(
40
45
  f"Missing required settings: {', '.join(missing_settings)}. "
41
46
  f"Please configure these in your Django settings under {self.user_settings_key}."
42
47
  )
43
48
 
49
+
44
50
  drf_to_mkdoc_settings = DRFToMkDocSettings(defaults=DEFAULTS)
@@ -1,11 +1,13 @@
1
+ import shutil
1
2
  import subprocess
2
3
  from pathlib import Path
3
4
 
5
+ from django.apps import apps
4
6
  from django.conf import settings
7
+ from django.core.management import call_command
5
8
  from django.core.management.base import BaseCommand, CommandError
6
- from django.apps import apps
9
+
7
10
  from drf_to_mkdoc.conf.settings import drf_to_mkdoc_settings
8
- from django.core.management import call_command
9
11
 
10
12
 
11
13
  class Command(BaseCommand):
@@ -18,13 +20,13 @@ class Command(BaseCommand):
18
20
  try:
19
21
  apps.check_apps_ready()
20
22
  except Exception as e:
21
- raise CommandError(f"Django apps not properly configured: {e}")
23
+ raise CommandError(f"Django apps not properly configured: {e}") from e
22
24
 
23
25
  base_dir = Path(settings.BASE_DIR)
24
26
  site_dir = base_dir / "site"
25
27
  mkdocs_config = base_dir / "mkdocs.yml"
26
28
  mkdocs_config_alt = base_dir / "mkdocs.yaml"
27
-
29
+
28
30
  if not mkdocs_config.exists() and not mkdocs_config_alt.exists():
29
31
  raise CommandError(
30
32
  "MkDocs configuration file not found. Please create either 'mkdocs.yml' or 'mkdocs.yaml' "
@@ -34,10 +36,7 @@ class Command(BaseCommand):
34
36
  try:
35
37
  # Generate the model documentation JSON first
36
38
  self.stdout.write("Generating model documentation...")
37
-
38
- call_command(
39
- "generate_model_docs", "--pretty"
40
- )
39
+ call_command("generate_model_docs", "--pretty")
41
40
  self.stdout.write(self.style.SUCCESS("Model documentation generated."))
42
41
 
43
42
  # Generate the documentation content
@@ -47,17 +46,7 @@ class Command(BaseCommand):
47
46
 
48
47
  # Build the MkDocs site
49
48
  self.stdout.write("Building MkDocs site...")
50
- result = subprocess.run(
51
- ["mkdocs", "build", "--clean"],
52
- check=False,
53
- cwd=base_dir,
54
- capture_output=True,
55
- text=True,
56
- )
57
-
58
- if result.returncode != 0:
59
- raise CommandError(f"MkDocs build failed: {result.stderr}")
60
-
49
+ self._build_mkdocs_site(base_dir, site_dir)
61
50
  self.stdout.write(self.style.SUCCESS("Documentation built successfully!"))
62
51
  self.stdout.write(f"Site built in: {site_dir}")
63
52
 
@@ -65,3 +54,56 @@ class Command(BaseCommand):
65
54
  raise CommandError(
66
55
  "MkDocs not found. Please install it with: pip install mkdocs mkdocs-material"
67
56
  ) from e
57
+
58
+ def _build_mkdocs_site(self, base_dir: Path, site_dir: Path) -> None:
59
+ """
60
+ Build the MkDocs site with proper security checks.
61
+
62
+ Args:
63
+ base_dir: The base directory of the Django project
64
+ site_dir: The directory where the site will be built
65
+
66
+ Raises:
67
+ FileNotFoundError: If mkdocs executable is not found
68
+ CommandError: If the build process fails
69
+ """
70
+ mkdocs_path = shutil.which("mkdocs")
71
+ if not mkdocs_path:
72
+ raise FileNotFoundError("mkdocs executable not found in PATH")
73
+
74
+ mkdocs_path_obj = Path(mkdocs_path)
75
+ if not mkdocs_path_obj.exists() or not mkdocs_path_obj.is_file():
76
+ raise CommandError(f"Invalid mkdocs executable path: {mkdocs_path}")
77
+
78
+ if not base_dir.is_absolute():
79
+ base_dir = base_dir.resolve()
80
+
81
+ if not base_dir.exists():
82
+ raise CommandError(f"Base directory does not exist: {base_dir}")
83
+
84
+ cmd = [
85
+ str(mkdocs_path_obj), # Convert to string for subprocess
86
+ "build",
87
+ "--clean",
88
+ ]
89
+
90
+ try:
91
+ result = subprocess.run( # noqa S603
92
+ cmd,
93
+ check=True,
94
+ cwd=str(base_dir),
95
+ capture_output=True,
96
+ text=True,
97
+ timeout=300,
98
+ )
99
+
100
+ if result.stdout:
101
+ self.stdout.write(f"MkDocs output: {result.stdout}")
102
+
103
+ except subprocess.TimeoutExpired as e:
104
+ raise CommandError("MkDocs build timed out after 5 minutes") from e
105
+ except subprocess.CalledProcessError as e:
106
+ error_msg = f"MkDocs build failed (exit code {e.returncode})"
107
+ if e.stderr:
108
+ error_msg += f": {e.stderr}"
109
+ raise CommandError(error_msg) from e
@@ -4,15 +4,15 @@ from pathlib import Path
4
4
 
5
5
  from django.core.management.base import BaseCommand
6
6
 
7
+ from drf_to_mkdoc.conf.settings import drf_to_mkdoc_settings
7
8
  from drf_to_mkdoc.utils.common import get_schema, load_model_json_data
8
- from drf_to_mkdoc.utils.endpoint_generator import (
9
- create_endpoints_index,
9
+ from drf_to_mkdoc.utils.endpoint_detail_generator import (
10
10
  generate_endpoint_files,
11
11
  parse_endpoints_from_schema,
12
12
  )
13
- from drf_to_mkdoc.utils.model_generator import create_models_index, generate_model_docs
14
- from drf_to_mkdoc.conf.settings import drf_to_mkdoc_settings
15
-
13
+ from drf_to_mkdoc.utils.endpoint_list_generator import create_endpoints_index
14
+ from drf_to_mkdoc.utils.model_detail_generator import generate_model_docs
15
+ from drf_to_mkdoc.utils.model_list_generator import create_models_index
16
16
 
17
17
 
18
18
  class Command(BaseCommand):
@@ -9,6 +9,7 @@ from django.db import models
9
9
 
10
10
  from drf_to_mkdoc.conf.settings import drf_to_mkdoc_settings
11
11
 
12
+
12
13
  class Command(BaseCommand):
13
14
  help = "Generate model documentation JSON from Django model introspection"
14
15
 
@@ -139,21 +140,50 @@ class Command(BaseCommand):
139
140
 
140
141
  def introspect_relationship(self, field):
141
142
  """Introspect relationship fields"""
142
- return {
143
+ # Safely resolve related model label; can be None for generic relations
144
+ related_model_label = None
145
+ try:
146
+ if getattr(field, "related_model", None) is not None:
147
+ related_model_label = (
148
+ f"{field.related_model._meta.app_label}.{field.related_model.__name__}"
149
+ )
150
+ except Exception:
151
+ related_model_label = None
152
+
153
+ relationship_data = {
143
154
  "name": field.name,
144
155
  "type": field.__class__.__name__,
145
- "related_model": f"{field.related_model._meta.app_label}."
146
- f"{field.related_model.__name__}",
156
+ "related_model": related_model_label,
147
157
  "related_name": getattr(field, "related_name", None),
148
158
  "on_delete": self.get_on_delete_name(field),
149
159
  "null": getattr(field, "null", False),
150
160
  "blank": getattr(field, "blank", False),
151
- "many_to_many": field.many_to_many,
152
- "one_to_many": field.one_to_many,
153
- "many_to_one": field.many_to_one,
154
- "one_to_one": field.one_to_one,
161
+ "many_to_many": getattr(field, "many_to_many", False),
162
+ "one_to_many": getattr(field, "one_to_many", False),
163
+ "many_to_one": getattr(field, "many_to_one", False),
164
+ "one_to_one": getattr(field, "one_to_one", False),
155
165
  }
156
166
 
167
+ # Handle Django generic relations where related_model can be None
168
+ field_class_name = field.__class__.__name__
169
+ if field_class_name in ("GenericForeignKey", "GenericRelation"):
170
+ relationship_data["is_generic"] = True
171
+ # Capture common generic relation details when available
172
+ for attr_name in (
173
+ "ct_field",
174
+ "fk_field",
175
+ "object_id_field",
176
+ "content_type_field",
177
+ "for_concrete_model",
178
+ "related_query_name",
179
+ ):
180
+ if hasattr(field, attr_name):
181
+ relationship_data[attr_name] = getattr(field, attr_name)
182
+ else:
183
+ relationship_data["is_generic"] = False
184
+
185
+ return relationship_data
186
+
157
187
  def get_on_delete_name(self, field):
158
188
  """Get readable name for on_delete option"""
159
189
  if not hasattr(field, "on_delete") or field.on_delete is None:
@@ -0,0 +1,84 @@
1
+ /* ===== BASE STYLES ===== */
2
+ * {
3
+ box-sizing: border-box;
4
+ }
5
+
6
+ body {
7
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
8
+ background: var(--bg-primary);
9
+ color: var(--text-primary);
10
+ line-height: 1.6;
11
+ transition: background-color 0.3s ease, color 0.3s ease;
12
+ margin: 0;
13
+ padding: 0;
14
+ min-height: 100vh;
15
+ }
16
+
17
+ .md-nav__item--active > .md-nav__link {
18
+ font-weight: 600;
19
+ }
20
+
21
+ /* Admonition customizations */
22
+ .admonition.note {
23
+ border-left-color: var(--md-primary-fg-color);
24
+ }
25
+
26
+ .admonition.tip {
27
+ border-left-color: #00c853;
28
+ }
29
+
30
+ .admonition.warning {
31
+ border-left-color: #ff9800;
32
+ }
33
+
34
+ .admonition.danger {
35
+ border-left-color: #f44336;
36
+ }
37
+
38
+ /* Search result enhancements */
39
+ .md-search-result__meta {
40
+ color: var(--md-default-fg-color--light);
41
+ }
42
+
43
+ /* Code syntax highlighting enhancements */
44
+ .codehilite .k { color: #0066cc; } /* Keywords */
45
+ .codehilite .s { color: #009900; } /* Strings */
46
+ .codehilite .c { color: #999999; } /* Comments */
47
+ .codehilite .n { color: #333333; } /* Names */
48
+ .codehilite .o { color: #666666; } /* Operators */
49
+
50
+ /* Custom icons for different sections */
51
+ .md-nav__item[data-md-level="1"] > .md-nav__link[href*="models"] {
52
+ position: relative;
53
+ }
54
+
55
+ .md-nav__item[data-md-level="1"] > .md-nav__link[href*="models"]::before {
56
+ content: "🗃️";
57
+ margin-right: 0.5rem;
58
+ }
59
+
60
+ .md-nav__item[data-md-level="1"] > .md-nav__link[href*="endpoints"] {
61
+ position: relative;
62
+ }
63
+
64
+ .md-nav__item[data-md-level="1"] > .md-nav__link[href*="endpoints"]::before {
65
+ content: "🔗";
66
+ margin-right: 0.5rem;
67
+ }
68
+
69
+ /* Print styles */
70
+ @media print {
71
+ .md-header, .md-sidebar, .md-footer {
72
+ display: none;
73
+ }
74
+
75
+ .md-content {
76
+ margin: 0;
77
+ }
78
+
79
+ .highlight pre {
80
+ border: 1px solid #ccc;
81
+ page-break-inside: avoid;
82
+ }
83
+ }
84
+