├── .coveragerc ├── .github ├── FUNDING.yml ├── ci-reporter.yml └── workflows │ ├── main.yml │ └── stale.yml ├── .gitignore ├── .readthedocs.yaml ├── .travis.yml ├── CONTRIBUTING.MD ├── README.rst ├── db.sqlite3 ├── django_admin_generator ├── __about__.py ├── __init__.py ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ └── admin_generator.py └── py.typed ├── docs ├── Makefile ├── _theme │ ├── LICENSE │ ├── flask_theme_support.py │ └── wolph │ │ ├── layout.html │ │ ├── relations.html │ │ ├── static │ │ ├── flasky.css_t │ │ └── small_flask.css │ │ └── theme.conf ├── conf.py ├── django_admin_generator.management.commands.rst ├── django_admin_generator.management.rst ├── django_admin_generator.rst ├── index.rst ├── make.bat ├── requirements.txt └── usage.rst ├── pytest.ini ├── setup.cfg ├── setup.py ├── test_project ├── __init__.py ├── manage.py ├── requirements.txt ├── settings.py ├── test_app │ ├── __init__.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── sub_models │ │ ├── __init__.py │ │ ├── eggs.py │ │ ├── meat.py │ │ └── models.py │ ├── tests.py │ └── views.py ├── test_commands.py ├── urls.py └── wsgi.py └── tox.ini /.coveragerc: -------------------------------------------------------------------------------- 1 | [report] 2 | fail_under = 100 3 | show_missing = True 4 | exclude_lines = 5 | pragma: no cover 6 | def __repr__ 7 | if self.debug: 8 | if settings.DEBUG 9 | raise AssertionError 10 | raise NotImplementedError 11 | if 0: 12 | if __name__ == .__main__.: 13 | 14 | [run] 15 | source = 16 | django_admin_generator 17 | test_project 18 | 19 | branch = True 20 | parallel = True 21 | omit = 22 | */__about__.py 23 | */test_project/* 24 | */metadata.py 25 | 26 | [paths] 27 | source = 28 | django_admin_generator 29 | test_project 30 | 31 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: WoLpH 2 | -------------------------------------------------------------------------------- /.github/ci-reporter.yml: -------------------------------------------------------------------------------- 1 | # Set to false to create a new comment instead of updating the app's first one 2 | updateComment: true 3 | 4 | # Use a custom string, or set to false to disable 5 | before: "✨ Good work on this PR so far! ✨ Unfortunately, the [ build]() is failing as of . Here's the output:" 6 | 7 | # Use a custom string, or set to false to disable 8 | after: "I'm sure you can fix it! If you need help, don't hesitate to ask a maintainer of the project!" 9 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: tox 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | setup: 9 | runs-on: ubuntu-latest 10 | timeout-minutes: 1 11 | outputs: 12 | tox-envs: ${{ steps.set-matrix.outputs.tox-envs }} 13 | steps: 14 | - uses: actions/checkout@v3 15 | - uses: actions/setup-python@v4 16 | with: 17 | python-version: '3.10' 18 | cache: 'pip' 19 | - run: python -m pip install --upgrade tox 20 | - id: set-matrix 21 | run: | 22 | echo tox-envs=$(tox -l | grep py | jq -R -s -c 'split("\n") | map(select(length > 0))') >> $GITHUB_OUTPUT 23 | 24 | build: 25 | needs: setup 26 | runs-on: ubuntu-latest 27 | timeout-minutes: 10 28 | strategy: 29 | fail-fast: false 30 | matrix: 31 | tox-env: ${{ fromJson(needs.setup.outputs.tox-envs) }} 32 | 33 | steps: 34 | - run: | 35 | PY=${{ matrix.tox-env }} 36 | PY=${PY//-django*/} 37 | PY=${PY//py3/3.} 38 | echo "PY=$PY" >> $GITHUB_ENV 39 | - uses: actions/checkout@v3 40 | - uses: actions/setup-python@v4 41 | with: 42 | python-version: ${{ env.PY }} 43 | cache: 'pip' 44 | - run: python -m pip install --upgrade tox 45 | - run: tox --skip-missing-interpreters -e ${{ matrix.tox-env }} 46 | 47 | docs: 48 | runs-on: ubuntu-latest 49 | timeout-minutes: 5 50 | steps: 51 | - uses: actions/checkout@v3 52 | - uses: actions/setup-python@v4 53 | with: 54 | python-version: '3.10' 55 | cache: 'pip' 56 | - run: python -m pip install --upgrade tox 57 | - run: tox --skip-missing-interpreters -e docs 58 | 59 | flake8: 60 | runs-on: ubuntu-latest 61 | timeout-minutes: 5 62 | steps: 63 | - uses: actions/checkout@v3 64 | - uses: actions/setup-python@v4 65 | with: 66 | python-version: '3.10' 67 | cache: 'pip' 68 | - run: python -m pip install --upgrade tox 69 | - run: tox --skip-missing-interpreters -e flake8 70 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: Close stale issues and pull requests 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: '0 0 * * *' # Run every day at midnight 7 | 8 | jobs: 9 | stale: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/stale@v8 13 | with: 14 | days-before-stale: 30 15 | exempt-issue-labels: in-progress,help-wanted,pinned,security,enhancement 16 | exempt-all-pr-assignees: true 17 | 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | dist 3 | .* 4 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # Read the Docs configuration file for Sphinx projects 2 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 3 | 4 | # Required 5 | version: 2 6 | 7 | # Set the OS, Python version and other tools you might need 8 | build: 9 | os: ubuntu-22.04 10 | tools: 11 | python: "3.12" 12 | # You can also specify other tool versions: 13 | # nodejs: "20" 14 | # rust: "1.70" 15 | # golang: "1.20" 16 | 17 | # Build documentation in the "docs/" directory with Sphinx 18 | sphinx: 19 | configuration: docs/conf.py 20 | # You can configure Sphinx to use a different builder, for instance use the dirhtml builder for simpler URLs 21 | # builder: "dirhtml" 22 | # Fail on all warnings to avoid broken references 23 | # fail_on_warning: true 24 | 25 | # Optionally build your docs in additional formats such as PDF and ePub 26 | formats: 27 | - pdf 28 | - epub 29 | 30 | # Optional but recommended, declare the Python requirements required 31 | # to build your documentation 32 | # See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html 33 | python: 34 | install: 35 | - requirements: docs/requirements.txt 36 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | cache: pip 3 | language: python 4 | python: 3.6 5 | 6 | env: 7 | - TOX_ENV=py35-django22 8 | - TOX_ENV=py36-django22 9 | - TOX_ENV=py37-django22 10 | - TOX_ENV=py37-django30 11 | - TOX_ENV=py37-django31 12 | - TOX_ENV=py38-django22 13 | - TOX_ENV=py38-django30 14 | - TOX_ENV=py38-django31 15 | - TOX_ENV=py39-django22 16 | - TOX_ENV=py39-django30 17 | - TOX_ENV=py39-django31 18 | - TOX_ENV=flake8 19 | - TOX_ENV=docs 20 | - TOX_ENV=coveralls 21 | 22 | install: 23 | - pip install tox 24 | 25 | script: 26 | - tox -e $TOX_ENV 27 | -------------------------------------------------------------------------------- /CONTRIBUTING.MD: -------------------------------------------------------------------------------- 1 | Contributing 2 | ============ 3 | 4 | Help is greatly appreciated, just please remember to clone the **development** 5 | branch and to run `tox` before creating pull requests. 6 | 7 | Travis tests for `flake8` support and test coverage so it's always good to 8 | check those before creating a pull request. 9 | 10 | Development branch: 11 | https://github.com/WoLpH/django-admin-generator/tree/development 12 | 13 | A simple trick to speed up your tox runs is to use detox, it runs all tox 14 | processes in parallel. 15 | 16 | ``` 17 | pip install tox detox 18 | detox 19 | ``` 20 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Readme 2 | ====== 3 | 4 | Introduction 5 | ------------ 6 | 7 | Build status: 8 | 9 | .. image:: https://github.com/WoLpH/django-admin-generator/actions/workflows/main.yml/badge.svg 10 | :alt: django-admin-generator test status 11 | :target: https://github.com/WoLpH/django-admin-generator/actions 12 | 13 | Coverage: 14 | 15 | .. image:: https://coveralls.io/repos/WoLpH/django-admin-generator/badge.svg?branch=master 16 | :alt: Coverage Status 17 | :target: https://coveralls.io/r/WoLpH/django-admin-generator?branch=master 18 | 19 | The Django Admin Generator is a project which can automatically generate 20 | (scaffold) a Django Admin for you. By doing this it will introspect your 21 | models and automatically generate an Admin with properties like: 22 | 23 | - ``list_display`` for all local fields 24 | - ``list_filter`` for foreign keys with few items 25 | - ``raw_id_fields`` for foreign keys with a lot of items 26 | - ``search_fields`` for name and ``slug`` fields 27 | - ``prepopulated_fields`` for ``slug`` fields 28 | - ``date_hierarchy`` for ``created_at``, ``updated_at`` or ``joined_at`` fields 29 | 30 | Links 31 | ----- 32 | 33 | * Documentation 34 | - http://django-admin-generator.readthedocs.org/en/latest/ 35 | * Source 36 | - https://github.com/WoLpH/django-admin-generator 37 | * Bug reports 38 | - https://github.com/WoLpH/django-admin-generator/issues 39 | * Package homepage 40 | - https://pypi.python.org/pypi/django-admin-generator 41 | * My blog 42 | - http://w.wol.ph/ 43 | 44 | Install 45 | ------- 46 | 47 | To install: 48 | 49 | 1. Run ``pip install django-admin-generator`` or execute ``python setup.py install`` in the source directory 50 | 2. Add ``django_admin_generator`` to your ``INSTALLED_APPS`` 51 | 52 | If you want to run the tests, run ``py.test`` (requires ``pytest``) 53 | 54 | Usage 55 | ----- 56 | 57 | To generate an admin for a given app: 58 | 59 | ./manage.py admin_generator APP_NAME >> APP_NAME/admin.py 60 | 61 | To generate an admin for a given app with all models starting with user: 62 | 63 | ./manage.py admin_generator APP_NAME '^user' >> APP_NAME/admin.py 64 | 65 | -------------------------------------------------------------------------------- /db.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wolph/django-admin-generator/bf340fe31c18a52d3f4199d18e45c6d907a1fe5c/db.sqlite3 -------------------------------------------------------------------------------- /django_admin_generator/__about__.py: -------------------------------------------------------------------------------- 1 | __package_name__ = 'django-admin-generator' 2 | __version__ = '2.6.0' 3 | __author__ = 'Rick van Hattem' 4 | __author_email__ = 'Wolph@Wol.ph' 5 | __description__ = ' '.join((''' 6 | Django Admin Generator is a management command to automatically generate a 7 | Django `admin.py` file for given apps/models. 8 | '''.strip().split())) 9 | __url__ = 'https://github.com/WoLpH/django-admin-generator/' 10 | 11 | -------------------------------------------------------------------------------- /django_admin_generator/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wolph/django-admin-generator/bf340fe31c18a52d3f4199d18e45c6d907a1fe5c/django_admin_generator/__init__.py -------------------------------------------------------------------------------- /django_admin_generator/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wolph/django-admin-generator/bf340fe31c18a52d3f4199d18e45c6d907a1fe5c/django_admin_generator/management/__init__.py -------------------------------------------------------------------------------- /django_admin_generator/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wolph/django-admin-generator/bf340fe31c18a52d3f4199d18e45c6d907a1fe5c/django_admin_generator/management/commands/__init__.py -------------------------------------------------------------------------------- /django_admin_generator/management/commands/admin_generator.py: -------------------------------------------------------------------------------- 1 | import pathlib 2 | import re 3 | import sys 4 | from pathlib import Path 5 | from typing import Any, List 6 | 7 | import python_utils 8 | import six 9 | from django.apps import AppConfig 10 | from django.apps.registry import apps 11 | from django.conf import settings 12 | from django.db import models 13 | from django_utils.management.commands import base_command 14 | from python_utils import logger, types 15 | 16 | 17 | def get_models(app): 18 | for model in app.get_models(): 19 | yield model 20 | 21 | 22 | def get_apps() -> types.Generator[types.Tuple[str, AppConfig], None, None]: 23 | for app_config in apps.get_app_configs(): 24 | yield app_config.name, app_config 25 | yield app_config.name.rsplit('.')[-1], app_config 26 | 27 | 28 | def get_local_apps() -> List[AppConfig]: 29 | local_app_configs: List[AppConfig] = [] 30 | # Get the absolute path of the project's base directory as a Path object 31 | project_root: Path = Path(settings.BASE_DIR).resolve() 32 | 33 | # If your virtual environment is inside the project directory, get its path 34 | venv_path: Path = project_root / 'venv' # Adjust 'venv' if your virtualenv folder has a different name 35 | 36 | for app_config in apps.get_app_configs(): 37 | app_path: Path = Path(app_config.path).resolve() 38 | # Check if the app is within the project directory but not in the virtual environment or site-packages 39 | if (project_root in app_path.parents or app_path == project_root) \ 40 | and (venv_path not in app_path.parents) \ 41 | and ('site-packages' not in str(app_path)): 42 | local_app_configs.append(app_config) 43 | return local_app_configs 44 | 45 | 46 | MAX_LINE_WIDTH = 78 47 | INDENT_WIDTH = 4 48 | 49 | LIST_FILTER = ( 50 | models.DateField, 51 | models.DateTimeField, 52 | models.ForeignKey, 53 | models.BooleanField, 54 | ) 55 | 56 | SEARCH_FIELD_NAMES = ( 57 | 'name', 58 | 'slug', 59 | ) 60 | 61 | DATE_HIERARCHY_NAMES = ( 62 | 'joined_at', 63 | 'updated_at', 64 | 'created_at', 65 | ) 66 | 67 | PREPOPULATED_FIELD_NAMES = ( 68 | 'slug=name', 69 | ) 70 | 71 | DATE_HIERARCHY_THRESHOLD = 250 72 | LIST_FILTER_THRESHOLD = 25 73 | RAW_ID_THRESHOLD = 100 74 | NO_QUERY_DB = False 75 | 76 | PRINT_IMPORTS_BASE = ''' 77 | from django.contrib import admin 78 | {imports} 79 | 80 | class ModelAdminBase({model_admin_class}): 81 | formfield_overrides = {formfield_overrides} 82 | ''' 83 | 84 | VERSION_ADMIN_CLASS = ''' 85 | class VersionModelAdminBase({reversion_admin_class}, ModelAdminBase): 86 | pass 87 | ''' 88 | 89 | PRINT_ADMIN_CLASS = ''' 90 | 91 | class %(name)sAdmin(%(base_class)s): 92 | %(class_)s 93 | ''' 94 | 95 | PRINT_ADMIN_REGISTRATION_METHOD = ''' 96 | 97 | def _register(model, admin_class): 98 | admin.site.register(model, admin_class) 99 | 100 | ''' 101 | 102 | PRINT_ADMIN_REGISTRATION = ''' 103 | _register(%(full_name)s, %(name)sAdmin)''' 104 | 105 | PRINT_ADMIN_REGISTRATION_LONG = ''' 106 | _register( 107 | %(full_name)s, 108 | %(name)sAdmin)''' 109 | 110 | PRINT_ADMIN_PROPERTY = ''' 111 | %(key)s = %(value)s''' 112 | 113 | 114 | class AdminApp: 115 | def __init__(self, app, model_res, **options): 116 | self.app = app 117 | self.model_res = model_res 118 | self.options = options 119 | self._reversion_enabled = False 120 | 121 | def __iter__(self): 122 | for model in get_models(self.app): 123 | admin_model = AdminModel(model, **self.options) 124 | 125 | for model_re in self.model_res: 126 | if model_re.search(admin_model.name): 127 | break 128 | else: 129 | if self.model_res: 130 | continue 131 | 132 | yield admin_model 133 | 134 | def __unicode__(self): 135 | return six.u('').join(self._unicode_generator()) 136 | 137 | def __str__(self): # pragma: no cover 138 | if six.PY2: 139 | return six.text_type(self).encode('utf-8', 'replace') 140 | else: 141 | return self.__unicode__() 142 | 143 | def _get_imports(self): 144 | imports = [] 145 | formfield_overrides = dict() 146 | self._detect_json_widget_support(formfield_overrides, imports) 147 | return imports, formfield_overrides 148 | 149 | def _unicode_generator(self): 150 | imports = [] 151 | formfield_overrides = dict() 152 | 153 | self._detect_json_widget_support(formfield_overrides, imports) 154 | yield from self._yield_imports_and_base_classes(imports, formfield_overrides) 155 | 156 | models = dict() 157 | modules = dict() 158 | module_names = dict() 159 | for admin_model in sorted(self, key=lambda x: x.model.__module__): 160 | model = admin_model.model 161 | module = model.__module__ 162 | # Get the module name if it was generated before or use the last 163 | # part of the module path 164 | name = modules.get(module, module.rsplit('.', 1)[-1]) 165 | 166 | # If the module name was already used, use the last two parts of 167 | # the module path converting `project.spam.models` to `spam_models` 168 | if module_names.get(name, module) != module: 169 | name = '_'.join(module.rsplit('.', 2)[-2:]) 170 | 171 | # Store the module name and models for later use. 172 | module_names[name] = module 173 | modules[module] = name 174 | models[admin_model.name] = name 175 | 176 | for module, name in sorted(modules.items()): 177 | yield 'import %s as %s\n' % (module, name) 178 | 179 | admin_model_names = [] 180 | for admin_model in self: 181 | if self._reversion_enabled and re.match(self.options.get('reversion_admin_regex'), admin_model.name): 182 | base_class = 'VersionModelAdminBase' 183 | else: 184 | base_class = 'ModelAdminBase' 185 | 186 | yield PRINT_ADMIN_CLASS % dict( 187 | name=admin_model.name, 188 | class_=admin_model, 189 | base_class=base_class, 190 | ) 191 | admin_model_names.append(admin_model.name) 192 | 193 | yield PRINT_ADMIN_REGISTRATION_METHOD 194 | 195 | for name in admin_model_names: 196 | full_name = '%s.%s' % (models[name], name) 197 | context = dict(name=name, full_name=full_name) 198 | row = PRINT_ADMIN_REGISTRATION % context 199 | if len(row) > MAX_LINE_WIDTH: 200 | row = PRINT_ADMIN_REGISTRATION_LONG % context 201 | yield row 202 | 203 | def _detect_json_widget_support(self, formfield_overrides, imports): 204 | if not self.options.get('disable_json_widget'): 205 | try: 206 | import django_json_widget 207 | except ImportError: 208 | pass 209 | else: 210 | imports.append( 211 | 'from django_json_widget.widgets import JSONEditorWidget') 212 | imports.append('from django.db.models import JSONField') 213 | formfield_overrides['JSONField'] = { 214 | 'widget': 'JSONEditorWidget', 215 | } 216 | 217 | def _yield_imports_and_base_classes(self, imports, formfield_overrides): 218 | addendum = '' 219 | if self.options.get('enable_reversion'): 220 | try: 221 | import reversion 222 | except ImportError: 223 | pass 224 | else: 225 | self._reversion_enabled = True 226 | imports.append(self.options.get('reversion_admin_class_import')) 227 | 228 | assert 'VersionModelAdminBase' != self.options.get('reversion_admin_class'), \ 229 | 'The reversion admin base class cannot be the same as the default admin base class' 230 | addendum = VERSION_ADMIN_CLASS.format( 231 | reversion_admin_class=self.options.get('reversion_admin_class'), 232 | ) + '\n\n' 233 | 234 | yield PRINT_IMPORTS_BASE.format( 235 | formfield_overrides=formfield_overrides, 236 | imports='\n'.join(imports), 237 | model_admin_class=self.options.get('admin_class'), 238 | ) 239 | 240 | yield addendum 241 | 242 | def __repr__(self): 243 | return '<%s[%s]>' % ( 244 | self.__class__.__name__, 245 | self.app, 246 | ) 247 | 248 | 249 | class AdminModel(object): 250 | PRINTABLE_PROPERTIES = ( 251 | 'list_display', 252 | 'list_filter', 253 | 'raw_id_fields', 254 | 'auto_complete_fields', 255 | 'search_fields', 256 | 'prepopulated_fields', 257 | 'date_hierarchy', 258 | ) 259 | 260 | def __init__( 261 | self, model, raw_id_threshold=RAW_ID_THRESHOLD, 262 | date_hierarchy_threshold=DATE_HIERARCHY_THRESHOLD, 263 | list_filter_threshold=LIST_FILTER_THRESHOLD, 264 | search_field_names=SEARCH_FIELD_NAMES, 265 | date_hierarchy_names=DATE_HIERARCHY_NAMES, 266 | prepopulated_field_names=PREPOPULATED_FIELD_NAMES, 267 | no_query_db=NO_QUERY_DB, 268 | auto_complete: list[str] | None=None, 269 | disable_auto_complete: bool=False, 270 | **options 271 | ): 272 | self.model = model 273 | self.list_display = python_utils.UniqueList() 274 | self.list_filter = python_utils.UniqueList() 275 | self.raw_id_fields = python_utils.UniqueList() 276 | self.search_fields = python_utils.UniqueList() 277 | self.auto_complete_fields = python_utils.UniqueList() 278 | self.prepopulated_fields = {} 279 | self.date_hierarchy = None 280 | self.search_field_names = search_field_names 281 | self.raw_id_threshold = raw_id_threshold 282 | self.list_filter_threshold = list_filter_threshold 283 | self.date_hierarchy_threshold = date_hierarchy_threshold 284 | self.date_hierarchy_names = date_hierarchy_names 285 | self.prepopulated_field_names = prepopulated_field_names 286 | self.query_db = not no_query_db 287 | self.auto_complete = auto_complete or not disable_auto_complete 288 | 289 | def __repr__(self): 290 | return '<%s[%s]>' % ( 291 | self.__class__.__name__, 292 | self.name, 293 | ) 294 | 295 | @property 296 | def name(self): 297 | return self.model.__name__ 298 | 299 | def _process_many_to_many(self, meta): 300 | raw_id_threshold = self.raw_id_threshold 301 | for field in meta.local_many_to_many: 302 | if field.name in self.auto_complete_fields: 303 | continue 304 | 305 | related_model = self._get_related_model(field) 306 | related_objects = related_model.objects.all() 307 | if (related_objects[:raw_id_threshold].count() < raw_id_threshold): 308 | yield field.name 309 | 310 | def _process_many_to_many_autocomplete(self, meta): 311 | for field in meta.local_many_to_many: 312 | if self.auto_complete is True or field.name in self.auto_complete: 313 | yield field.name 314 | 315 | def _process_fields(self, meta): 316 | parent_fields = meta.parents.values() 317 | for field in meta.fields: 318 | name = self._process_field(field, parent_fields) 319 | if name: # pragma: no cover 320 | yield name 321 | 322 | @classmethod 323 | def _get_related_model(cls, field): # pragma: no cover 324 | if hasattr(field, 'remote_field'): 325 | related_model = field.remote_field.model 326 | elif hasattr(field.related, 'related_model'): 327 | related_model = field.related.related_model 328 | else: 329 | related_model = field.related.model 330 | 331 | return related_model 332 | 333 | def _process_foreign_key(self, field): 334 | raw_id_threshold = self.raw_id_threshold 335 | list_filter_threshold = self.list_filter_threshold 336 | max_count = max(list_filter_threshold, raw_id_threshold) 337 | related_model = self._get_related_model(field) 338 | related_count = related_model.objects.all() 339 | related_count = related_count[:max_count].count() 340 | 341 | if related_count >= raw_id_threshold: 342 | self.raw_id_fields.append(field.name) 343 | 344 | elif related_count < list_filter_threshold: 345 | self.list_filter.append(field.name) 346 | 347 | else: # pragma: no cover 348 | pass # Do nothing :) 349 | 350 | def _process_field(self, field, parent_fields): 351 | if field in parent_fields: # pragma: no cover 352 | return 353 | 354 | self.list_display.append(field.name) 355 | if isinstance(field, LIST_FILTER): 356 | if isinstance(field, models.ForeignKey) and self.query_db: 357 | self._process_foreign_key(field) 358 | else: 359 | self.list_filter.append(field.name) 360 | 361 | if field.name in self.search_field_names: 362 | self.search_fields.append(field.name) 363 | 364 | return field.name 365 | 366 | def __str__(self): # pragma: no cover 367 | if six.PY2: 368 | return six.text_type(self).encode('utf-8', 'replace') 369 | else: 370 | return self.__unicode__() 371 | 372 | def __unicode__(self): 373 | return six.u('').join(self._unicode_generator()) 374 | 375 | def _yield_value(self, key, value): 376 | if isinstance(value, (list, set, tuple)): 377 | return self._yield_tuple(key, tuple(value)) 378 | elif isinstance(value, dict): 379 | return self._yield_dict(key, value) 380 | elif isinstance(value, six.string_types): 381 | return self._yield_string(key, value) 382 | else: # pragma: no cover 383 | raise TypeError('%s is not supported in %r' % (type(value), value)) 384 | 385 | def _yield_string(self, key, value, converter=repr): 386 | return PRINT_ADMIN_PROPERTY % dict( 387 | key=key, 388 | value=converter(value), 389 | ) 390 | 391 | def _yield_dict(self, key, value): 392 | row_parts = [] 393 | row = self._yield_string(key, value) 394 | if len(row) > MAX_LINE_WIDTH: 395 | row_parts.append(self._yield_string(key, '{', str)) 396 | for k, v in six.iteritems(value): 397 | row_parts.append('%s%r: %r' % (2 * INDENT_WIDTH * ' ', k, v)) 398 | 399 | row_parts.append(INDENT_WIDTH * ' ' + '}') 400 | row = six.u('\n').join(row_parts) 401 | 402 | return row 403 | 404 | def _yield_tuple(self, key, value): 405 | row_parts = [] 406 | row = self._yield_string(key, value) 407 | if len(row) > MAX_LINE_WIDTH: 408 | row_parts.append(self._yield_string(key, '(', str)) 409 | for v in value: 410 | row_parts.append(2 * INDENT_WIDTH * ' ' + repr(v) + ',') 411 | 412 | row_parts.append(INDENT_WIDTH * ' ' + ')') 413 | row = six.u('\n').join(row_parts) 414 | 415 | return row 416 | 417 | def _unicode_generator(self): 418 | self._process() 419 | for key in self.PRINTABLE_PROPERTIES: 420 | value = getattr(self, key) 421 | if value: 422 | yield self._yield_value(key, value) 423 | 424 | def _process(self): 425 | meta = self.model._meta 426 | qs = self.model.objects.all() 427 | 428 | if self.auto_complete: 429 | self.auto_complete_fields += list(self._process_many_to_many_autocomplete(meta)) 430 | if self.query_db: 431 | self.raw_id_fields += list(self._process_many_to_many(meta)) 432 | 433 | field_names = list(self._process_fields(meta)) 434 | 435 | if self.query_db: 436 | threshold = self.list_filter_threshold + 1 437 | for field in field_names: 438 | distinct_count = len(qs.only(field).distinct()[:threshold]) 439 | if distinct_count <= self.list_filter_threshold: 440 | self.list_filter.append(field) 441 | 442 | if self.query_db: 443 | if qs.count() < self.date_hierarchy_threshold: 444 | for field_name in self.date_hierarchy_names[::-1]: 445 | if field_name in field_names and not self.date_hierarchy: 446 | self.date_hierarchy = field_name 447 | break 448 | 449 | for k in sorted(self.prepopulated_field_names): 450 | k, vs = k.split('=', 1) 451 | vs = vs.split(',') 452 | if k in field_names: 453 | incomplete = False 454 | for v in vs: 455 | if v not in field_names: 456 | incomplete = True 457 | break 458 | 459 | if not incomplete: 460 | self.prepopulated_fields[k] = vs 461 | 462 | self.processed = True 463 | 464 | 465 | class Command(base_command.CustomBaseCommand): 466 | help = '''Generate a `admin.py` file for the given app (models)''' 467 | can_import_settings = True 468 | requires_system_checks = '__all__', 469 | 470 | def add_arguments(self, parser): 471 | super(Command, self).add_arguments(parser) 472 | 473 | parser.add_argument( 474 | '-s', '--search-field', action='append', 475 | default=SEARCH_FIELD_NAMES, 476 | help='Fields named like this will be added to `search_fields`' 477 | ) 478 | parser.add_argument( 479 | '-d', '--date-hierarchy', action='append', 480 | default=DATE_HIERARCHY_NAMES, 481 | help='A field named like this will be set as `date_hierarchy`' 482 | ) 483 | parser.add_argument( 484 | '--date-hierarchy-threshold', type=int, 485 | default=DATE_HIERARCHY_THRESHOLD, 486 | metavar='DATE_HIERARCHY_THRESHOLD', 487 | help='If a model has less than DATE_HIERARCHY_THRESHOLD items ' 488 | 'it will be added to `date_hierarchy`' 489 | ) 490 | parser.add_argument( 491 | '-p', '--prepopulated-fields', action='append', 492 | default=PREPOPULATED_FIELD_NAMES, 493 | help='These fields will be prepopulated by the other field.' 494 | 'The field names can be specified like `spam=eggA,eggB,eggC`' 495 | ) 496 | parser.add_argument( 497 | '-l', '--list-filter-threshold', type=int, 498 | default=LIST_FILTER_THRESHOLD, metavar='LIST_FILTER_THRESHOLD', 499 | help='If a foreign key/field has less than LIST_FILTER_THRESHOLD ' 500 | 'items it will be added to `list_filter`' 501 | ) 502 | parser.add_argument( 503 | '-r', '--raw-id-threshold', type=int, 504 | default=RAW_ID_THRESHOLD, metavar='RAW_ID_THRESHOLD', 505 | help='If a foreign key has more than RAW_ID_THRESHOLD items ' 506 | 'it will be added to `list_filter`' 507 | ) 508 | parser.add_argument( 509 | '-n', '--no-query-db', action="store_true", dest='no_query_db', 510 | help='Don\'t query the database in order to decide whether ' 511 | 'fields/relationships are added to `list_filter`' 512 | ) 513 | parser.add_argument( 514 | '-w', '--write', action='store_true', 515 | help='Write the output to the admin.py file(s)', 516 | ) 517 | parser.add_argument( 518 | '-o', '--output', default='admin.py', 519 | help='Output file name', 520 | ) 521 | parser.add_argument( 522 | '-f', '--force', action='store_true', 523 | help='Overwrite the output file if it exists', 524 | ) 525 | parser.add_argument( 526 | '-a', '--append', action='store_true', 527 | help='Append the output to the output file if it exists', 528 | ) 529 | parser.add_argument( 530 | 'app', 531 | help='App to generate admin definitions for, use `all` to generate ' 532 | 'for all local (i.e. not in site-packages) apps' 533 | ) 534 | parser.add_argument( 535 | 'models', nargs='*', 536 | help='Regular expressions to filter the models by' 537 | ) 538 | parser.add_argument( 539 | '--disable-json-widget', action='store_true', 540 | help='Disable the JSON widget import and formfield override', 541 | ) 542 | parser.add_argument( 543 | '--enable-reversion', action='store_true', 544 | help='Enable django-reversion support', 545 | ) 546 | parser.add_argument( 547 | '--reversion-admin-regex', default=r'.*', 548 | help='Regular expression to filter the models by for reversion', 549 | ) 550 | parser.add_argument( 551 | '--reversion-admin-class', default='VersionAdmin', 552 | help='The base class for the ModelAdmin classes for reversion', 553 | ) 554 | parser.add_argument( 555 | '--reversion-admin-class-import', default='from reversion.admin import VersionAdmin', 556 | help='The import statement for the base class for the ModelAdmin classes for reversion', 557 | ) 558 | parser.add_argument( 559 | '--admin-class', default='admin.ModelAdmin', 560 | help='The base class for the ModelAdmin classes', 561 | ) 562 | parser.add_argument( 563 | '--admin-class-import', default='', 564 | help='The import statement for the base class for the ModelAdmin classes if it is not the Django default', 565 | ) 566 | parser.add_argument( 567 | '--disable-auto-complete', action='store_true', 568 | help='Disable the auto-complete feature for the many-to-many fields', 569 | ) 570 | parser.add_argument( 571 | '--auto-complete', action='append', 572 | help='Enable the auto-complete feature only for the specified many-to-many fields', 573 | ) 574 | 575 | @classmethod 576 | def warning( 577 | cls, 578 | msg: object, 579 | *args: object, 580 | exc_info: logger._ExcInfoType = None, 581 | stack_info: bool = False, 582 | stacklevel: int = 1, 583 | extra: types.Union[types.Mapping[str, object], None] = None, 584 | ) -> None: 585 | # This replaces the regular warning method from the CustomBaseCommand 586 | # since some Django installations capture all logging output 587 | # unfortunately 588 | sys.stderr.write(str(msg)) 589 | sys.stderr.write('\n') 590 | 591 | def handle(self, app: types.Optional[str]=None, *args, **kwargs): 592 | super(Command, self).handle(*args, **kwargs) 593 | 594 | if app == 'all': 595 | for app in get_local_apps(): 596 | self.handle_app(app, [], **kwargs) 597 | else: 598 | installed_apps: dict[str, Any] = dict(get_apps()) 599 | 600 | app = installed_apps.get(app) 601 | if not app: 602 | self.warning( 603 | 'This command requires an existing app name as ' 604 | 'argument' 605 | ) 606 | self.warning('Available apps:') 607 | for app in sorted(installed_apps): 608 | self.warning(' %s' % app) 609 | sys.exit(1) 610 | 611 | model_res = [] 612 | for model in kwargs.get('models', []): 613 | model_res.append(re.compile(model, re.IGNORECASE)) 614 | 615 | self.handle_app(app, model_res, **kwargs) 616 | 617 | def handle_app( 618 | self, 619 | app, 620 | model_res, 621 | write: bool=False, 622 | output: types.Optional[str]=None, 623 | force: bool=False, 624 | append: bool=False, 625 | **options, 626 | ): 627 | if output: 628 | if '/' in output or '\\' in output: 629 | output_path = pathlib.Path(output) 630 | else: 631 | output_path = pathlib.Path(app.path) / output 632 | 633 | print(f'Writing {app.name} to {output_path}', file=sys.stderr) 634 | 635 | if output_path.exists(): 636 | if not force and not append: 637 | self.warning( 638 | 'The output file `%s` already exists. Use the ' 639 | '`-f` or `-a` option to overwrite or append to it.' 640 | % output 641 | ) 642 | sys.exit(1) 643 | 644 | fh = output_path.open('a' if append else 'w') if write else sys.stdout 645 | print(f'Writing to {fh}', file=sys.stderr) 646 | else: 647 | fh = sys.stdout 648 | 649 | print(AdminApp(app, model_res, **options), file=fh) 650 | 651 | if output: 652 | fh.close() 653 | -------------------------------------------------------------------------------- /django_admin_generator/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wolph/django-admin-generator/bf340fe31c18a52d3f4199d18e45c6d907a1fe5c/django_admin_generator/py.typed -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | # the i18n builder cannot share the environment and doctrees with the others 15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 16 | 17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 18 | 19 | help: 20 | @echo "Please use \`make ' where is one of" 21 | @echo " html to make standalone HTML files" 22 | @echo " dirhtml to make HTML files named index.html in directories" 23 | @echo " singlehtml to make a single large HTML file" 24 | @echo " pickle to make pickle files" 25 | @echo " json to make JSON files" 26 | @echo " htmlhelp to make HTML files and a HTML help project" 27 | @echo " qthelp to make HTML files and a qthelp project" 28 | @echo " devhelp to make HTML files and a Devhelp project" 29 | @echo " epub to make an epub" 30 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 31 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 32 | @echo " text to make text files" 33 | @echo " man to make manual pages" 34 | @echo " texinfo to make Texinfo files" 35 | @echo " info to make Texinfo files and run them through makeinfo" 36 | @echo " gettext to make PO message catalogs" 37 | @echo " changes to make an overview of all changed/added/deprecated items" 38 | @echo " linkcheck to check all external links for integrity" 39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 40 | 41 | clean: 42 | -rm -rf $(BUILDDIR)/* 43 | 44 | html: 45 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 48 | 49 | dirhtml: 50 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 51 | @echo 52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 53 | 54 | singlehtml: 55 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 56 | @echo 57 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 58 | 59 | pickle: 60 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 61 | @echo 62 | @echo "Build finished; now you can process the pickle files." 63 | 64 | json: 65 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 66 | @echo 67 | @echo "Build finished; now you can process the JSON files." 68 | 69 | htmlhelp: 70 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 71 | @echo 72 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 73 | ".hhp project file in $(BUILDDIR)/htmlhelp." 74 | 75 | qthelp: 76 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 77 | @echo 78 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 79 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 80 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/PythonUtils.qhcp" 81 | @echo "To view the help file:" 82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/PythonUtils.qhc" 83 | 84 | devhelp: 85 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 86 | @echo 87 | @echo "Build finished." 88 | @echo "To view the help file:" 89 | @echo "# mkdir -p $$HOME/.local/share/devhelp/PythonUtils" 90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/PythonUtils" 91 | @echo "# devhelp" 92 | 93 | epub: 94 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 95 | @echo 96 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 97 | 98 | latex: 99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 100 | @echo 101 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 102 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 103 | "(use \`make latexpdf' here to do that automatically)." 104 | 105 | latexpdf: 106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 107 | @echo "Running LaTeX files through pdflatex..." 108 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 109 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 110 | 111 | text: 112 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 113 | @echo 114 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 115 | 116 | man: 117 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 118 | @echo 119 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 120 | 121 | texinfo: 122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 123 | @echo 124 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 125 | @echo "Run \`make' in that directory to run these through makeinfo" \ 126 | "(use \`make info' here to do that automatically)." 127 | 128 | info: 129 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 130 | @echo "Running Texinfo files through makeinfo..." 131 | make -C $(BUILDDIR)/texinfo info 132 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 133 | 134 | gettext: 135 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 136 | @echo 137 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 138 | 139 | changes: 140 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 141 | @echo 142 | @echo "The overview file is in $(BUILDDIR)/changes." 143 | 144 | linkcheck: 145 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 146 | @echo 147 | @echo "Link check complete; look for any errors in the above output " \ 148 | "or in $(BUILDDIR)/linkcheck/output.txt." 149 | 150 | doctest: 151 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 152 | @echo "Testing of doctests in the sources finished, look at the " \ 153 | "results in $(BUILDDIR)/doctest/output.txt." 154 | -------------------------------------------------------------------------------- /docs/_theme/LICENSE: -------------------------------------------------------------------------------- 1 | Modifications: 2 | 3 | Copyright (c) 2012 Rick van Hattem. 4 | 5 | 6 | Original Projects: 7 | 8 | Copyright (c) 2010 Kenneth Reitz. 9 | Copyright (c) 2010 by Armin Ronacher. 10 | 11 | 12 | Some rights reserved. 13 | 14 | Redistribution and use in source and binary forms of the theme, with or 15 | without modification, are permitted provided that the following conditions 16 | are met: 17 | 18 | * Redistributions of source code must retain the above copyright 19 | notice, this list of conditions and the following disclaimer. 20 | 21 | * Redistributions in binary form must reproduce the above 22 | copyright notice, this list of conditions and the following 23 | disclaimer in the documentation and/or other materials provided 24 | with the distribution. 25 | 26 | * The names of the contributors may not be used to endorse or 27 | promote products derived from this software without specific 28 | prior written permission. 29 | 30 | We kindly ask you to only use these themes in an unmodified manner just 31 | for Flask and Flask-related products, not for unrelated projects. If you 32 | like the visual style and want to use it for your own projects, please 33 | consider making some larger changes to the themes (such as changing 34 | font faces, sizes, colors or margins). 35 | 36 | THIS THEME IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 37 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 38 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 39 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 40 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 41 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 42 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 43 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 44 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 45 | ARISING IN ANY WAY OUT OF THE USE OF THIS THEME, EVEN IF ADVISED OF THE 46 | POSSIBILITY OF SUCH DAMAGE. 47 | -------------------------------------------------------------------------------- /docs/_theme/flask_theme_support.py: -------------------------------------------------------------------------------- 1 | # flasky extensions. flasky pygments style based on tango style 2 | from pygments.style import Style 3 | from pygments.token import Keyword, Name, Comment, String, Error, \ 4 | Number, Operator, Generic, Whitespace, Punctuation, Other, Literal 5 | 6 | 7 | class FlaskyStyle(Style): 8 | background_color = "#f8f8f8" 9 | default_style = "" 10 | 11 | styles = { 12 | # No corresponding class for the following: 13 | # Text: "", # class: '' 14 | Whitespace: "underline #f8f8f8", # class: 'w' 15 | Error: "#a40000 border:#ef2929", # class: 'err' 16 | Other: "#000000", # class 'x' 17 | 18 | Comment: "italic #8f5902", # class: 'c' 19 | Comment.Preproc: "noitalic", # class: 'cp' 20 | 21 | Keyword: "bold #004461", # class: 'k' 22 | Keyword.Constant: "bold #004461", # class: 'kc' 23 | Keyword.Declaration: "bold #004461", # class: 'kd' 24 | Keyword.Namespace: "bold #004461", # class: 'kn' 25 | Keyword.Pseudo: "bold #004461", # class: 'kp' 26 | Keyword.Reserved: "bold #004461", # class: 'kr' 27 | Keyword.Type: "bold #004461", # class: 'kt' 28 | 29 | Operator: "#582800", # class: 'o' 30 | Operator.Word: "bold #004461", # class: 'ow' - like keywords 31 | 32 | Punctuation: "bold #000000", # class: 'p' 33 | 34 | # because special names such as Name.Class, Name.Function, etc. 35 | # are not recognized as such later in the parsing, we choose them 36 | # to look the same as ordinary variables. 37 | Name: "#000000", # class: 'n' 38 | Name.Attribute: "#c4a000", # class: 'na' - to be revised 39 | Name.Builtin: "#004461", # class: 'nb' 40 | Name.Builtin.Pseudo: "#3465a4", # class: 'bp' 41 | Name.Class: "#000000", # class: 'nc' - to be revised 42 | Name.Constant: "#000000", # class: 'no' - to be revised 43 | Name.Decorator: "#888", # class: 'nd' - to be revised 44 | Name.Entity: "#ce5c00", # class: 'ni' 45 | Name.Exception: "bold #cc0000", # class: 'ne' 46 | Name.Function: "#000000", # class: 'nf' 47 | Name.Property: "#000000", # class: 'py' 48 | Name.Label: "#f57900", # class: 'nl' 49 | Name.Namespace: "#000000", # class: 'nn' - to be revised 50 | Name.Other: "#000000", # class: 'nx' 51 | Name.Tag: "bold #004461", # class: 'nt' - like a keyword 52 | Name.Variable: "#000000", # class: 'nv' - to be revised 53 | Name.Variable.Class: "#000000", # class: 'vc' - to be revised 54 | Name.Variable.Global: "#000000", # class: 'vg' - to be revised 55 | Name.Variable.Instance: "#000000", # class: 'vi' - to be revised 56 | 57 | Number: "#990000", # class: 'm' 58 | 59 | Literal: "#000000", # class: 'l' 60 | Literal.Date: "#000000", # class: 'ld' 61 | 62 | String: "#4e9a06", # class: 's' 63 | String.Backtick: "#4e9a06", # class: 'sb' 64 | String.Char: "#4e9a06", # class: 'sc' 65 | String.Doc: "italic #8f5902", # class: 'sd' - like a comment 66 | String.Double: "#4e9a06", # class: 's2' 67 | String.Escape: "#4e9a06", # class: 'se' 68 | String.Heredoc: "#4e9a06", # class: 'sh' 69 | String.Interpol: "#4e9a06", # class: 'si' 70 | String.Other: "#4e9a06", # class: 'sx' 71 | String.Regex: "#4e9a06", # class: 'sr' 72 | String.Single: "#4e9a06", # class: 's1' 73 | String.Symbol: "#4e9a06", # class: 'ss' 74 | 75 | Generic: "#000000", # class: 'g' 76 | Generic.Deleted: "#a40000", # class: 'gd' 77 | Generic.Emph: "italic #000000", # class: 'ge' 78 | Generic.Error: "#ef2929", # class: 'gr' 79 | Generic.Heading: "bold #000080", # class: 'gh' 80 | Generic.Inserted: "#00A000", # class: 'gi' 81 | Generic.Output: "#888", # class: 'go' 82 | Generic.Prompt: "#745334", # class: 'gp' 83 | Generic.Strong: "bold #000000", # class: 'gs' 84 | Generic.Subheading: "bold #800080", # class: 'gu' 85 | Generic.Traceback: "bold #a40000", # class: 'gt' 86 | } 87 | -------------------------------------------------------------------------------- /docs/_theme/wolph/layout.html: -------------------------------------------------------------------------------- 1 | {%- extends "basic/layout.html" %} 2 | {%- block extrahead %} 3 | {{ super() }} 4 | {% if theme_touch_icon %} 5 | 6 | {% endif %} 7 | 9 | {% endblock %} 10 | {%- block relbar2 %}{% endblock %} 11 | {%- block footer %} 12 | 16 | {%- endblock %} 17 | -------------------------------------------------------------------------------- /docs/_theme/wolph/relations.html: -------------------------------------------------------------------------------- 1 |

Related Topics

2 | 20 | -------------------------------------------------------------------------------- /docs/_theme/wolph/static/flasky.css_t: -------------------------------------------------------------------------------- 1 | /* 2 | * flasky.css_t 3 | * ~~~~~~~~~~~~ 4 | * 5 | * :copyright: Copyright 2010 by Armin Ronacher. Modifications by Kenneth Reitz. 6 | * :license: Flask Design License, see LICENSE for details. 7 | */ 8 | 9 | {% set page_width = '940px' %} 10 | {% set sidebar_width = '220px' %} 11 | 12 | @import url("basic.css"); 13 | 14 | /* -- page layout ----------------------------------------------------------- */ 15 | 16 | body { 17 | font-family: 'goudy old style', 'minion pro', 'bell mt', Georgia, 'Hiragino Mincho Pro'; 18 | font-size: 17px; 19 | background-color: white; 20 | color: #000; 21 | margin: 0; 22 | padding: 0; 23 | } 24 | 25 | div.document { 26 | width: {{ page_width }}; 27 | margin: 30px auto 0 auto; 28 | } 29 | 30 | div.documentwrapper { 31 | float: left; 32 | width: 100%; 33 | } 34 | 35 | div.bodywrapper { 36 | margin: 0 0 0 {{ sidebar_width }}; 37 | } 38 | 39 | div.sphinxsidebar { 40 | width: {{ sidebar_width }}; 41 | } 42 | 43 | hr { 44 | border: 1px solid #B1B4B6; 45 | } 46 | 47 | div.body { 48 | background-color: #ffffff; 49 | color: #3E4349; 50 | padding: 0 30px 0 30px; 51 | } 52 | 53 | img.floatingflask { 54 | padding: 0 0 10px 10px; 55 | float: right; 56 | } 57 | 58 | div.footer { 59 | width: {{ page_width }}; 60 | margin: 20px auto 30px auto; 61 | font-size: 14px; 62 | color: #888; 63 | text-align: right; 64 | } 65 | 66 | div.footer a { 67 | color: #888; 68 | } 69 | 70 | div.related { 71 | display: none; 72 | } 73 | 74 | div.sphinxsidebar a { 75 | color: #444; 76 | text-decoration: none; 77 | border-bottom: 1px dotted #999; 78 | } 79 | 80 | div.sphinxsidebar a:hover { 81 | border-bottom: 1px solid #999; 82 | } 83 | 84 | div.sphinxsidebar { 85 | font-size: 14px; 86 | line-height: 1.5; 87 | } 88 | 89 | div.sphinxsidebarwrapper { 90 | padding: 0px 10px; 91 | } 92 | 93 | div.sphinxsidebarwrapper p.logo { 94 | padding: 0 0 20px 0; 95 | margin: 0; 96 | text-align: center; 97 | } 98 | 99 | div.sphinxsidebar h3, 100 | div.sphinxsidebar h4 { 101 | font-family: 'Garamond', 'Georgia', serif; 102 | color: #555; 103 | font-size: 24px; 104 | font-weight: normal; 105 | margin: 0 0 5px 0; 106 | padding: 0; 107 | } 108 | 109 | div.sphinxsidebar h4 { 110 | font-size: 20px; 111 | } 112 | 113 | div.sphinxsidebar h3 a { 114 | color: #444; 115 | } 116 | 117 | div.sphinxsidebar p.logo a, 118 | div.sphinxsidebar h3 a, 119 | div.sphinxsidebar p.logo a:hover, 120 | div.sphinxsidebar h3 a:hover { 121 | border: none; 122 | } 123 | 124 | div.sphinxsidebar p { 125 | color: #555; 126 | margin: 10px 0; 127 | } 128 | 129 | div.sphinxsidebar ul { 130 | margin: 10px 0; 131 | padding: 0; 132 | color: #000; 133 | } 134 | 135 | div.sphinxsidebar input[type="text"] { 136 | width: 160px!important; 137 | } 138 | div.sphinxsidebar input { 139 | border: 1px solid #ccc; 140 | font-family: 'Georgia', serif; 141 | font-size: 1em; 142 | } 143 | 144 | /* -- body styles ----------------------------------------------------------- */ 145 | 146 | a { 147 | color: #004B6B; 148 | text-decoration: underline; 149 | } 150 | 151 | a:hover { 152 | color: #6D4100; 153 | text-decoration: underline; 154 | } 155 | 156 | div.body h1, 157 | div.body h2, 158 | div.body h3, 159 | div.body h4, 160 | div.body h5, 161 | div.body h6 { 162 | font-family: 'Garamond', 'Georgia', serif; 163 | font-weight: normal; 164 | margin: 30px 0px 10px 0px; 165 | padding: 0; 166 | } 167 | 168 | div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; } 169 | div.body h2 { font-size: 180%; } 170 | div.body h3 { font-size: 150%; } 171 | div.body h4 { font-size: 130%; } 172 | div.body h5 { font-size: 100%; } 173 | div.body h6 { font-size: 100%; } 174 | 175 | a.headerlink { 176 | color: #ddd; 177 | padding: 0 4px; 178 | text-decoration: none; 179 | } 180 | 181 | a.headerlink:hover { 182 | color: #444; 183 | background: #eaeaea; 184 | } 185 | 186 | div.body p, div.body dd, div.body li { 187 | line-height: 1.4em; 188 | } 189 | 190 | div.admonition { 191 | background: #fafafa; 192 | margin: 20px -30px; 193 | padding: 10px 30px; 194 | border-top: 1px solid #ccc; 195 | border-bottom: 1px solid #ccc; 196 | } 197 | 198 | div.admonition tt.xref, div.admonition a tt { 199 | border-bottom: 1px solid #fafafa; 200 | } 201 | 202 | dd div.admonition { 203 | margin-left: -60px; 204 | padding-left: 60px; 205 | } 206 | 207 | div.admonition p.admonition-title { 208 | font-family: 'Garamond', 'Georgia', serif; 209 | font-weight: normal; 210 | font-size: 24px; 211 | margin: 0 0 10px 0; 212 | padding: 0; 213 | line-height: 1; 214 | } 215 | 216 | div.admonition p.last { 217 | margin-bottom: 0; 218 | } 219 | 220 | div.highlight { 221 | background-color: white; 222 | } 223 | 224 | dt:target, .highlight { 225 | background: #FAF3E8; 226 | } 227 | 228 | div.note { 229 | background-color: #eee; 230 | border: 1px solid #ccc; 231 | } 232 | 233 | div.seealso { 234 | background-color: #ffc; 235 | border: 1px solid #ff6; 236 | } 237 | 238 | div.topic { 239 | background-color: #eee; 240 | } 241 | 242 | p.admonition-title { 243 | display: inline; 244 | } 245 | 246 | p.admonition-title:after { 247 | content: ":"; 248 | } 249 | 250 | pre, tt { 251 | font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; 252 | font-size: 0.9em; 253 | } 254 | 255 | img.screenshot { 256 | } 257 | 258 | tt.descname, tt.descclassname { 259 | font-size: 0.95em; 260 | } 261 | 262 | tt.descname { 263 | padding-right: 0.08em; 264 | } 265 | 266 | img.screenshot { 267 | -moz-box-shadow: 2px 2px 4px #eee; 268 | -webkit-box-shadow: 2px 2px 4px #eee; 269 | box-shadow: 2px 2px 4px #eee; 270 | } 271 | 272 | table.docutils { 273 | border: 1px solid #888; 274 | -moz-box-shadow: 2px 2px 4px #eee; 275 | -webkit-box-shadow: 2px 2px 4px #eee; 276 | box-shadow: 2px 2px 4px #eee; 277 | } 278 | 279 | table.docutils td, table.docutils th { 280 | border: 1px solid #888; 281 | padding: 0.25em 0.7em; 282 | } 283 | 284 | table.field-list, table.footnote { 285 | border: none; 286 | -moz-box-shadow: none; 287 | -webkit-box-shadow: none; 288 | box-shadow: none; 289 | } 290 | 291 | table.footnote { 292 | margin: 15px 0; 293 | width: 100%; 294 | border: 1px solid #eee; 295 | background: #fdfdfd; 296 | font-size: 0.9em; 297 | } 298 | 299 | table.footnote + table.footnote { 300 | margin-top: -15px; 301 | border-top: none; 302 | } 303 | 304 | table.field-list th { 305 | padding: 0 0.8em 0 0; 306 | } 307 | 308 | table.field-list td { 309 | padding: 0; 310 | } 311 | 312 | table.footnote td.label { 313 | width: 0px; 314 | padding: 0.3em 0 0.3em 0.5em; 315 | } 316 | 317 | table.footnote td { 318 | padding: 0.3em 0.5em; 319 | } 320 | 321 | dl { 322 | margin: 0; 323 | padding: 0; 324 | } 325 | 326 | dl dd { 327 | margin-left: 30px; 328 | } 329 | 330 | blockquote { 331 | margin: 0 0 0 30px; 332 | padding: 0; 333 | } 334 | 335 | ul, ol { 336 | margin: 10px 0 10px 30px; 337 | padding: 0; 338 | } 339 | 340 | pre { 341 | background: #eee; 342 | padding: 7px 30px; 343 | margin: 15px -30px; 344 | line-height: 1.3em; 345 | } 346 | 347 | dl pre, blockquote pre, li pre { 348 | margin-left: -60px; 349 | padding-left: 60px; 350 | } 351 | 352 | dl dl pre { 353 | margin-left: -90px; 354 | padding-left: 90px; 355 | } 356 | 357 | tt { 358 | background-color: #ecf0f3; 359 | color: #222; 360 | /* padding: 1px 2px; */ 361 | } 362 | 363 | tt.xref, a tt { 364 | background-color: #FBFBFB; 365 | border-bottom: 1px solid white; 366 | } 367 | 368 | a.reference { 369 | text-decoration: none; 370 | border-bottom: 1px dotted #004B6B; 371 | } 372 | 373 | a.reference:hover { 374 | border-bottom: 1px solid #6D4100; 375 | } 376 | 377 | a.footnote-reference { 378 | text-decoration: none; 379 | font-size: 0.7em; 380 | vertical-align: top; 381 | border-bottom: 1px dotted #004B6B; 382 | } 383 | 384 | a.footnote-reference:hover { 385 | border-bottom: 1px solid #6D4100; 386 | } 387 | 388 | a:hover tt { 389 | background: #EEE; 390 | } 391 | 392 | 393 | /* scrollbars */ 394 | 395 | ::-webkit-scrollbar { 396 | width: 6px; 397 | height: 6px; 398 | } 399 | 400 | ::-webkit-scrollbar-button:start:decrement, 401 | ::-webkit-scrollbar-button:end:increment { 402 | display: block; 403 | height: 10px; 404 | } 405 | 406 | ::-webkit-scrollbar-button:vertical:increment { 407 | background-color: #fff; 408 | } 409 | 410 | ::-webkit-scrollbar-track-piece { 411 | background-color: #eee; 412 | -webkit-border-radius: 3px; 413 | } 414 | 415 | ::-webkit-scrollbar-thumb:vertical { 416 | height: 50px; 417 | background-color: #ccc; 418 | -webkit-border-radius: 3px; 419 | } 420 | 421 | ::-webkit-scrollbar-thumb:horizontal { 422 | width: 50px; 423 | background-color: #ccc; 424 | -webkit-border-radius: 3px; 425 | } 426 | 427 | /* misc. */ 428 | 429 | .revsys-inline { 430 | display: none!important; 431 | } 432 | -------------------------------------------------------------------------------- /docs/_theme/wolph/static/small_flask.css: -------------------------------------------------------------------------------- 1 | /* 2 | * small_flask.css_t 3 | * ~~~~~~~~~~~~~~~~~ 4 | * 5 | * :copyright: Copyright 2010 by Armin Ronacher. 6 | * :license: Flask Design License, see LICENSE for details. 7 | */ 8 | 9 | body { 10 | margin: 0; 11 | padding: 20px 30px; 12 | } 13 | 14 | div.documentwrapper { 15 | float: none; 16 | background: white; 17 | } 18 | 19 | div.sphinxsidebar { 20 | display: block; 21 | float: none; 22 | width: 102.5%; 23 | margin: 50px -30px -20px -30px; 24 | padding: 10px 20px; 25 | background: #333; 26 | color: white; 27 | } 28 | 29 | div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p, 30 | div.sphinxsidebar h3 a { 31 | color: white; 32 | } 33 | 34 | div.sphinxsidebar a { 35 | color: #aaa; 36 | } 37 | 38 | div.sphinxsidebar p.logo { 39 | display: none; 40 | } 41 | 42 | div.document { 43 | width: 100%; 44 | margin: 0; 45 | } 46 | 47 | div.related { 48 | display: block; 49 | margin: 0; 50 | padding: 10px 0 20px 0; 51 | } 52 | 53 | div.related ul, 54 | div.related ul li { 55 | margin: 0; 56 | padding: 0; 57 | } 58 | 59 | div.footer { 60 | display: none; 61 | } 62 | 63 | div.bodywrapper { 64 | margin: 0; 65 | } 66 | 67 | div.body { 68 | min-height: 0; 69 | padding: 0; 70 | } 71 | -------------------------------------------------------------------------------- /docs/_theme/wolph/theme.conf: -------------------------------------------------------------------------------- 1 | [theme] 2 | inherit = basic 3 | stylesheet = flasky.css 4 | pygments_style = flask_theme_support.FlaskyStyle 5 | 6 | [options] 7 | touch_icon = 8 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Django Admin Generator documentation build configuration file, created by 4 | # sphinx-quickstart on Thu Feb 27 20:00:23 2014. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import os 16 | import sys 17 | import datetime 18 | 19 | # If extensions (or modules to document with autodoc) are in another directory, 20 | # add these directories to sys.path here. If the directory is relative to the 21 | # documentation root, use os.path.abspath to make it absolute, like shown here. 22 | sys.path.insert(0, os.path.abspath('..')) 23 | from django_admin_generator import __about__ 24 | 25 | # -- General configuration ------------------------------------------------ 26 | 27 | # If your documentation needs a minimal Sphinx version, state it here. 28 | #needs_sphinx = '1.0' 29 | 30 | # Add any Sphinx extension module names here, as strings. They can be 31 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 32 | # ones. 33 | extensions = [ 34 | 'sphinx.ext.autodoc', 35 | 'sphinx.ext.doctest', 36 | 'sphinx.ext.intersphinx', 37 | 'sphinx.ext.todo', 38 | 'sphinx.ext.coverage', 39 | 'sphinx.ext.mathjax', 40 | 'sphinx.ext.ifconfig', 41 | 'sphinx.ext.viewcode', 42 | ] 43 | 44 | # Add any paths that contain templates here, relative to this directory. 45 | templates_path = ['_templates'] 46 | 47 | # The suffix of source filenames. 48 | source_suffix = '.rst' 49 | 50 | # The encoding of source files. 51 | #source_encoding = 'utf-8-sig' 52 | 53 | # The master toctree document. 54 | master_doc = 'index' 55 | 56 | # General information about the project. 57 | project = __about__.__package_name__.replace('-', ' ').capitalize() 58 | copyright = u'%s, %s' % ( 59 | datetime.date.today().year, 60 | __about__.__author__, 61 | ) 62 | 63 | # The version info for the project you're documenting, acts as replacement for 64 | # |version| and |release|, also used in various other places throughout the 65 | # built documents. 66 | # 67 | # The short X.Y version. 68 | version = __about__.__version__ 69 | # The full version, including alpha/beta/rc tags. 70 | release = __about__.__version__ 71 | 72 | # The language for content autogenerated by Sphinx. Refer to documentation 73 | # for a list of supported languages. 74 | #language = None 75 | 76 | # There are two options for replacing |today|: either, you set today to some 77 | # non-false value, then it is used: 78 | #today = '' 79 | # Else, today_fmt is used as the format for a strftime call. 80 | #today_fmt = '%B %d, %Y' 81 | 82 | # List of patterns, relative to source directory, that match files and 83 | # directories to ignore when looking for source files. 84 | exclude_patterns = ['_build'] 85 | 86 | # The reST default role (used for this markup: `text`) to use for all 87 | # documents. 88 | #default_role = None 89 | 90 | # If true, '()' will be appended to :func: etc. cross-reference text. 91 | #add_function_parentheses = True 92 | 93 | # If true, the current module name will be prepended to all description 94 | # unit titles (such as .. function::). 95 | #add_module_names = True 96 | 97 | # If true, sectionauthor and moduleauthor directives will be shown in the 98 | # output. They are ignored by default. 99 | #show_authors = False 100 | 101 | # The name of the Pygments (syntax highlighting) style to use. 102 | pygments_style = 'sphinx' 103 | 104 | # A list of ignored prefixes for module index sorting. 105 | #modindex_common_prefix = [] 106 | 107 | # If true, keep warnings as "system message" paragraphs in the built documents. 108 | #keep_warnings = False 109 | 110 | 111 | # -- Options for HTML output ---------------------------------------------- 112 | 113 | # The theme to use for HTML and HTML Help pages. See the documentation for 114 | # a list of builtin themes. 115 | html_theme = 'wolph' 116 | 117 | # Theme options are theme-specific and customize the look and feel of a theme 118 | # further. For a list of options available for each theme, see the 119 | # documentation. 120 | #html_theme_options = {} 121 | 122 | # Add any paths that contain custom themes here, relative to this directory. 123 | html_theme_path = ['_theme'] 124 | 125 | # The name for this set of Sphinx documents. If None, it defaults to 126 | # " v documentation". 127 | #html_title = None 128 | 129 | # A shorter title for the navigation bar. Default is the same as html_title. 130 | #html_short_title = None 131 | 132 | # The name of an image file (relative to this directory) to place at the top 133 | # of the sidebar. 134 | #html_logo = None 135 | 136 | # The name of an image file (within the static path) to use as favicon of the 137 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 138 | # pixels large. 139 | #html_favicon = None 140 | 141 | # Add any paths that contain custom static files (such as style sheets) here, 142 | # relative to this directory. They are copied after the builtin static files, 143 | # so a file named "default.css" will overwrite the builtin "default.css". 144 | html_static_path = ['_static'] 145 | 146 | # Add any extra paths that contain custom files (such as robots.txt or 147 | # .htaccess) here, relative to this directory. These files are copied 148 | # directly to the root of the documentation. 149 | #html_extra_path = [] 150 | 151 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 152 | # using the given strftime format. 153 | #html_last_updated_fmt = '%b %d, %Y' 154 | 155 | # If true, SmartyPants will be used to convert quotes and dashes to 156 | # typographically correct entities. 157 | #html_use_smartypants = True 158 | 159 | # Custom sidebar templates, maps document names to template names. 160 | #html_sidebars = {} 161 | 162 | # Additional templates that should be rendered to pages, maps page names to 163 | # template names. 164 | #html_additional_pages = {} 165 | 166 | # If false, no module index is generated. 167 | #html_domain_indices = True 168 | 169 | # If false, no index is generated. 170 | #html_use_index = True 171 | 172 | # If true, the index is split into individual pages for each letter. 173 | #html_split_index = False 174 | 175 | # If true, links to the reST sources are added to the pages. 176 | #html_show_sourcelink = True 177 | 178 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 179 | #html_show_sphinx = True 180 | 181 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 182 | #html_show_copyright = True 183 | 184 | # If true, an OpenSearch description file will be output, and all pages will 185 | # contain a tag referring to it. The value of this option must be the 186 | # base URL from which the finished HTML is served. 187 | #html_use_opensearch = '' 188 | 189 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 190 | #html_file_suffix = None 191 | 192 | # Output file base name for HTML help builder. 193 | htmlhelp_basename = __about__.__package_name__ + '-doc' 194 | 195 | 196 | # -- Options for LaTeX output --------------------------------------------- 197 | 198 | latex_elements = { 199 | # The paper size ('letterpaper' or 'a4paper'). 200 | #'papersize': 'letterpaper', 201 | 202 | # The font size ('10pt', '11pt' or '12pt'). 203 | #'pointsize': '10pt', 204 | 205 | # Additional stuff for the LaTeX preamble. 206 | #'preamble': '', 207 | } 208 | 209 | # Grouping the document tree into LaTeX files. List of tuples 210 | # (source start file, target name, title, author, documentclass [howto/manual]). 211 | latex_documents = [( 212 | 'index', 213 | '%s.tex' % __about__.__package_name__, 214 | u'%s Documentation' % __about__.__package_name__.replace('-', ' ').capitalize(), 215 | __about__.__author__, 216 | 'manual', 217 | )] 218 | 219 | # The name of an image file (relative to this directory) to place at the top of 220 | # the title page. 221 | #latex_logo = None 222 | 223 | # For "manual" documents, if this is true, then toplevel headings are parts, 224 | # not chapters. 225 | #latex_use_parts = False 226 | 227 | # If true, show page references after internal links. 228 | #latex_show_pagerefs = False 229 | 230 | # If true, show URL addresses after external links. 231 | #latex_show_urls = False 232 | 233 | # Documents to append as an appendix to all manuals. 234 | #latex_appendices = [] 235 | 236 | # If false, no module index is generated. 237 | #latex_domain_indices = True 238 | 239 | 240 | # -- Options for manual page output --------------------------------------- 241 | 242 | # One entry per manual page. List of tuples 243 | # (source start file, name, description, authors, manual section). 244 | man_pages = [( 245 | 'index', 246 | __about__.__package_name__, 247 | u'%s Documentation' % __about__.__package_name__.replace('-', ' ').capitalize(), 248 | [__about__.__author__], 249 | 1, 250 | )] 251 | 252 | # If true, show URL addresses after external links. 253 | #man_show_urls = False 254 | 255 | 256 | # -- Options for Texinfo output ------------------------------------------- 257 | 258 | # Grouping the document tree into Texinfo files. List of tuples 259 | # (source start file, target name, title, author, 260 | # dir menu entry, description, category) 261 | texinfo_documents = [( 262 | 'index', 263 | __about__.__package_name__, 264 | u'%s Documentation' % __about__.__package_name__.replace('-', ' ').capitalize(), 265 | __about__.__author__, 266 | __about__.__package_name__, 267 | __about__.__description__, 268 | 'Miscellaneous', 269 | )] 270 | 271 | # Documents to append as an appendix to all manuals. 272 | #texinfo_appendices = [] 273 | 274 | # If false, no module index is generated. 275 | #texinfo_domain_indices = True 276 | 277 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 278 | #texinfo_show_urls = 'footnote' 279 | 280 | # If true, do not generate a @detailmenu in the "Top" node's menu. 281 | #texinfo_no_detailmenu = False 282 | 283 | 284 | # -- Options for Epub output ---------------------------------------------- 285 | 286 | # Bibliographic Dublin Core info. 287 | epub_title = __about__.__package_name__.replace('-', ' ').capitalize() 288 | epub_author = __about__.__author__ 289 | epub_publisher = __about__.__author__ 290 | epub_copyright = copyright 291 | 292 | # The basename for the epub file. It defaults to the project name. 293 | #epub_basename = u'Django Admin Generator' 294 | 295 | # The HTML theme for the epub output. Since the default themes are not optimized 296 | # for small screen space, using the same theme for HTML and epub output is 297 | # usually not wise. This defaults to 'epub', a theme designed to save visual 298 | # space. 299 | #epub_theme = 'epub' 300 | 301 | # The language of the text. It defaults to the language option 302 | # or en if the language is not set. 303 | #epub_language = '' 304 | 305 | # The scheme of the identifier. Typical schemes are ISBN or URL. 306 | #epub_scheme = '' 307 | 308 | # The unique identifier of the text. This can be a ISBN number 309 | # or the project homepage. 310 | #epub_identifier = '' 311 | 312 | # A unique identification for the text. 313 | #epub_uid = '' 314 | 315 | # A tuple containing the cover image and cover page html template filenames. 316 | #epub_cover = () 317 | 318 | # A sequence of (type, uri, title) tuples for the guide element of content.opf. 319 | #epub_guide = () 320 | 321 | # HTML files that should be inserted before the pages created by sphinx. 322 | # The format is a list of tuples containing the path and title. 323 | #epub_pre_files = [] 324 | 325 | # HTML files shat should be inserted after the pages created by sphinx. 326 | # The format is a list of tuples containing the path and title. 327 | #epub_post_files = [] 328 | 329 | # A list of files that should not be packed into the epub file. 330 | epub_exclude_files = ['search.html'] 331 | 332 | # The depth of the table of contents in toc.ncx. 333 | #epub_tocdepth = 3 334 | 335 | # Allow duplicate toc entries. 336 | #epub_tocdup = True 337 | 338 | # Choose between 'default' and 'includehidden'. 339 | #epub_tocscope = 'default' 340 | 341 | # Fix unsupported image types using the PIL. 342 | #epub_fix_images = False 343 | 344 | # Scale large images. 345 | #epub_max_image_width = 0 346 | 347 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 348 | #epub_show_urls = 'inline' 349 | 350 | # If false, no index is generated. 351 | #epub_use_index = True 352 | 353 | 354 | # Example configuration for intersphinx: refer to the Python standard library. 355 | intersphinx_mapping = {'http://docs.python.org/': None} 356 | -------------------------------------------------------------------------------- /docs/django_admin_generator.management.commands.rst: -------------------------------------------------------------------------------- 1 | django\_admin\_generator.management.commands package 2 | ==================================================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | django\_admin\_generator.management.commands.admin\_generator module 8 | -------------------------------------------------------------------- 9 | 10 | .. automodule:: django_admin_generator.management.commands.admin_generator 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | 16 | Module contents 17 | --------------- 18 | 19 | .. automodule:: django_admin_generator.management.commands 20 | :members: 21 | :undoc-members: 22 | :show-inheritance: 23 | -------------------------------------------------------------------------------- /docs/django_admin_generator.management.rst: -------------------------------------------------------------------------------- 1 | django\_admin\_generator.management package 2 | =========================================== 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | 9 | django_admin_generator.management.commands 10 | 11 | Module contents 12 | --------------- 13 | 14 | .. automodule:: django_admin_generator.management 15 | :members: 16 | :undoc-members: 17 | :show-inheritance: 18 | -------------------------------------------------------------------------------- /docs/django_admin_generator.rst: -------------------------------------------------------------------------------- 1 | django\_admin\_generator package 2 | ================================ 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | 9 | django_admin_generator.management 10 | 11 | Module contents 12 | --------------- 13 | 14 | .. automodule:: django_admin_generator 15 | :members: 16 | :undoc-members: 17 | :show-inheritance: 18 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to Django Admin Generator's documentation! 2 | ================================================== 3 | 4 | Contents: 5 | 6 | .. toctree:: 7 | :maxdepth: 4 8 | 9 | usage 10 | django_admin_generator 11 | 12 | Indices and tables 13 | ================== 14 | 15 | * :ref:`genindex` 16 | * :ref:`modindex` 17 | * :ref:`search` 18 | 19 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. linkcheck to check all external links for integrity 37 | echo. doctest to run all doctests embedded in the documentation if enabled 38 | goto end 39 | ) 40 | 41 | if "%1" == "clean" ( 42 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 43 | del /q /s %BUILDDIR%\* 44 | goto end 45 | ) 46 | 47 | if "%1" == "html" ( 48 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 49 | if errorlevel 1 exit /b 1 50 | echo. 51 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 52 | goto end 53 | ) 54 | 55 | if "%1" == "dirhtml" ( 56 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 57 | if errorlevel 1 exit /b 1 58 | echo. 59 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 60 | goto end 61 | ) 62 | 63 | if "%1" == "singlehtml" ( 64 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 68 | goto end 69 | ) 70 | 71 | if "%1" == "pickle" ( 72 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished; now you can process the pickle files. 76 | goto end 77 | ) 78 | 79 | if "%1" == "json" ( 80 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished; now you can process the JSON files. 84 | goto end 85 | ) 86 | 87 | if "%1" == "htmlhelp" ( 88 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can run HTML Help Workshop with the ^ 92 | .hhp project file in %BUILDDIR%/htmlhelp. 93 | goto end 94 | ) 95 | 96 | if "%1" == "qthelp" ( 97 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 98 | if errorlevel 1 exit /b 1 99 | echo. 100 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 101 | .qhcp project file in %BUILDDIR%/qthelp, like this: 102 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\PythonUtils.qhcp 103 | echo.To view the help file: 104 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\PythonUtils.ghc 105 | goto end 106 | ) 107 | 108 | if "%1" == "devhelp" ( 109 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 110 | if errorlevel 1 exit /b 1 111 | echo. 112 | echo.Build finished. 113 | goto end 114 | ) 115 | 116 | if "%1" == "epub" ( 117 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 118 | if errorlevel 1 exit /b 1 119 | echo. 120 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 121 | goto end 122 | ) 123 | 124 | if "%1" == "latex" ( 125 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 129 | goto end 130 | ) 131 | 132 | if "%1" == "text" ( 133 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The text files are in %BUILDDIR%/text. 137 | goto end 138 | ) 139 | 140 | if "%1" == "man" ( 141 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 145 | goto end 146 | ) 147 | 148 | if "%1" == "texinfo" ( 149 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 150 | if errorlevel 1 exit /b 1 151 | echo. 152 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 153 | goto end 154 | ) 155 | 156 | if "%1" == "gettext" ( 157 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 158 | if errorlevel 1 exit /b 1 159 | echo. 160 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 161 | goto end 162 | ) 163 | 164 | if "%1" == "changes" ( 165 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 166 | if errorlevel 1 exit /b 1 167 | echo. 168 | echo.The overview file is in %BUILDDIR%/changes. 169 | goto end 170 | ) 171 | 172 | if "%1" == "linkcheck" ( 173 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 174 | if errorlevel 1 exit /b 1 175 | echo. 176 | echo.Link check complete; look for any errors in the above output ^ 177 | or in %BUILDDIR%/linkcheck/output.txt. 178 | goto end 179 | ) 180 | 181 | if "%1" == "doctest" ( 182 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 183 | if errorlevel 1 exit /b 1 184 | echo. 185 | echo.Testing of doctests in the sources finished, look at the ^ 186 | results in %BUILDDIR%/doctest/output.txt. 187 | goto end 188 | ) 189 | 190 | :end 191 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | -e.[docs,tests] 2 | -------------------------------------------------------------------------------- /docs/usage.rst: -------------------------------------------------------------------------------- 1 | 2 | .. include :: ../README.rst 3 | 4 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | python_files = 3 | django_admin_generator/*.py 4 | test_project/*.py 5 | tests.py 6 | test_*.py 7 | *_tests.py 8 | 9 | addopts = 10 | --cov django_admin_generator 11 | --cov-report term-missing 12 | 13 | DJANGO_SETTINGS_MODULE=test_project.settings 14 | 15 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description_file = README.rst 3 | 4 | [bdist_wheel] 5 | universal = 1 6 | 7 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import setuptools 4 | 5 | # To prevent importing about and thereby breaking the coverage info we use this 6 | # exec hack 7 | about = {} 8 | with open('django_admin_generator/__about__.py') as fp: 9 | exec(fp.read(), about) 10 | 11 | if os.path.isfile('README.rst'): 12 | long_description = open('README.rst').read() 13 | else: 14 | long_description = ('See http://pypi.python.org/pypi/' + 15 | about['__package_name__']) 16 | 17 | if __name__ == '__main__': 18 | setuptools.setup( 19 | name=about['__package_name__'], 20 | version=about['__version__'], 21 | author=about['__author__'], 22 | author_email=about['__author_email__'], 23 | description=about['__description__'], 24 | url=about['__url__'], 25 | license='BSD', 26 | packages=setuptools.find_packages(), 27 | install_requires=[ 28 | 'django-utils2>=2.12.1', 29 | 'python-utils>=3.5.2', 30 | 'six', 31 | ], 32 | extras_require={ 33 | 'docs': [ 34 | 'django>=3.2', 35 | 'mock', 36 | 'sphinx>=1.6.0', 37 | ], 38 | 'tests': [ 39 | 'pytest', 40 | 'pytest-cov', 41 | 'pytest-django', 42 | 'flake8', 43 | ], 44 | }, 45 | long_description=long_description, 46 | classifiers=[ 47 | 'Development Status :: 5 - Production/Stable', 48 | 'Environment :: Web Environment', 49 | 'Intended Audience :: Developers', 50 | 'License :: OSI Approved :: BSD License', 51 | 'Natural Language :: English', 52 | 'Programming Language :: Python :: 3.7', 53 | 'Programming Language :: Python :: 3.8', 54 | 'Programming Language :: Python :: 3.9', 55 | 'Programming Language :: Python :: 3.10', 56 | 'Programming Language :: Python :: 3.11', 57 | ], 58 | ) 59 | -------------------------------------------------------------------------------- /test_project/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wolph/django-admin-generator/bf340fe31c18a52d3f4199d18e45c6d907a1fe5c/test_project/__init__.py -------------------------------------------------------------------------------- /test_project/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "test_project.settings") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError: 10 | # The above import may fail for some other reason. Ensure that the 11 | # issue is really that Django is missing to avoid masking other 12 | # exceptions on Python 2. 13 | try: 14 | import django 15 | assert django 16 | except ImportError: 17 | raise ImportError( 18 | "Couldn't import Django. Are you sure it's installed and " 19 | "available on your PYTHONPATH environment variable? Did you " 20 | "forget to activate a virtual environment?" 21 | ) 22 | raise 23 | execute_from_command_line(sys.argv) 24 | -------------------------------------------------------------------------------- /test_project/requirements.txt: -------------------------------------------------------------------------------- 1 | -e.[tests] 2 | -------------------------------------------------------------------------------- /test_project/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for test_project project. 3 | 4 | Generated by 'django-admin startproject' using Django 1.10.4. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.10/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/1.10/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = 'ty6e!^h*1g9v7vm9plcuo+z0gd%xkf*!#*q&)zb6=fve-f38u%' 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | 'django.contrib.admin', 35 | 'django.contrib.auth', 36 | 'django.contrib.contenttypes', 37 | 'django.contrib.sessions', 38 | 'django.contrib.messages', 39 | 'django.contrib.staticfiles', 40 | 'test_project.test_app', 41 | 'django_admin_generator', 42 | ] 43 | 44 | MIDDLEWARE = [ 45 | 'django.middleware.security.SecurityMiddleware', 46 | 'django.contrib.sessions.middleware.SessionMiddleware', 47 | 'django.middleware.common.CommonMiddleware', 48 | 'django.middleware.csrf.CsrfViewMiddleware', 49 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 50 | 'django.contrib.messages.middleware.MessageMiddleware', 51 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 52 | ] 53 | 54 | ROOT_URLCONF = 'test_project.urls' 55 | 56 | TEMPLATES = [ 57 | { 58 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 59 | 'DIRS': [], 60 | 'APP_DIRS': True, 61 | 'OPTIONS': { 62 | 'context_processors': [ 63 | 'django.template.context_processors.debug', 64 | 'django.template.context_processors.request', 65 | 'django.contrib.auth.context_processors.auth', 66 | 'django.contrib.messages.context_processors.messages', 67 | ], 68 | }, 69 | }, 70 | ] 71 | 72 | WSGI_APPLICATION = 'test_project.wsgi.application' 73 | 74 | 75 | # Database 76 | # https://docs.djangoproject.com/en/1.10/ref/settings/#databases 77 | 78 | DATABASES = { 79 | 'default': { 80 | 'ENGINE': 'django.db.backends.sqlite3', 81 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 82 | } 83 | } 84 | 85 | 86 | # Password validation 87 | # https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators 88 | 89 | AUTH_PASSWORD_VALIDATORS = [ 90 | { 91 | 'NAME': 'django.contrib.auth.password_validation.' 92 | 'UserAttributeSimilarityValidator', 93 | }, 94 | { 95 | 'NAME': 'django.contrib.auth.password_validation.' 96 | 'MinimumLengthValidator', 97 | }, 98 | { 99 | 'NAME': 'django.contrib.auth.password_validation.' 100 | 'CommonPasswordValidator', 101 | }, 102 | { 103 | 'NAME': 'django.contrib.auth.password_validation.' 104 | 'NumericPasswordValidator', 105 | }, 106 | ] 107 | 108 | 109 | # Internationalization 110 | # https://docs.djangoproject.com/en/1.10/topics/i18n/ 111 | 112 | LANGUAGE_CODE = 'en-us' 113 | 114 | TIME_ZONE = 'UTC' 115 | 116 | USE_I18N = True 117 | 118 | USE_L10N = True 119 | 120 | USE_TZ = True 121 | 122 | 123 | # Static files (CSS, JavaScript, Images) 124 | # https://docs.djangoproject.com/en/1.10/howto/static-files/ 125 | 126 | STATIC_URL = '/static/' 127 | -------------------------------------------------------------------------------- /test_project/test_app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wolph/django-admin-generator/bf340fe31c18a52d3f4199d18e45c6d907a1fe5c/test_project/test_app/__init__.py -------------------------------------------------------------------------------- /test_project/test_app/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.8 on 2023-01-25 23:42 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | initial = True 10 | 11 | dependencies = [ 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='DateHierarchyModel', 17 | fields=[ 18 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 19 | ('created_at', models.DateField()), 20 | ], 21 | ), 22 | migrations.CreateModel( 23 | name='SluggedField', 24 | fields=[ 25 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 26 | ('name', models.CharField(max_length=100)), 27 | ('slug', models.SlugField(max_length=100)), 28 | ], 29 | ), 30 | migrations.CreateModel( 31 | name='Spam', 32 | fields=[ 33 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 34 | ('a', models.CharField(max_length=50)), 35 | ], 36 | ), 37 | migrations.CreateModel( 38 | name='ACollectionOfSpamAndEggsAndOtherStuff', 39 | fields=[ 40 | ('spam_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='test_app.spam')), 41 | ('this_should_be_something_really_long_a', models.CharField(max_length=100)), 42 | ('this_should_be_something_really_long_b', models.CharField(max_length=100)), 43 | ('this_should_be_something_really_long_c', models.CharField(max_length=100)), 44 | ('this_should_be_something_really_long_d', models.CharField(max_length=100)), 45 | ], 46 | bases=('test_app.spam',), 47 | ), 48 | migrations.CreateModel( 49 | name='Eggs', 50 | fields=[ 51 | ('spam_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='test_app.spam')), 52 | ('b', models.CharField(max_length=100)), 53 | ], 54 | bases=('test_app.spam',), 55 | ), 56 | ] 57 | -------------------------------------------------------------------------------- /test_project/test_app/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wolph/django-admin-generator/bf340fe31c18a52d3f4199d18e45c6d907a1fe5c/test_project/test_app/migrations/__init__.py -------------------------------------------------------------------------------- /test_project/test_app/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | from .sub_models import eggs, meat, models as sub_models_models 4 | 5 | assert eggs 6 | assert meat 7 | assert sub_models_models 8 | 9 | 10 | class DateHierarchyModel(models.Model): 11 | created_at = models.DateField() 12 | 13 | 14 | class SluggedField(models.Model): 15 | name = models.CharField(max_length=100) 16 | slug = models.SlugField(max_length=100) 17 | -------------------------------------------------------------------------------- /test_project/test_app/sub_models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wolph/django-admin-generator/bf340fe31c18a52d3f4199d18e45c6d907a1fe5c/test_project/test_app/sub_models/__init__.py -------------------------------------------------------------------------------- /test_project/test_app/sub_models/eggs.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | from .meat import Spam 4 | 5 | 6 | class Eggs(Spam): 7 | b = models.CharField(max_length=100) 8 | -------------------------------------------------------------------------------- /test_project/test_app/sub_models/meat.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class Spam(models.Model): 5 | a = models.CharField(max_length=50) 6 | -------------------------------------------------------------------------------- /test_project/test_app/sub_models/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | from test_project.test_app.sub_models.meat import Spam 4 | 5 | 6 | class ACollectionOfSpamAndEggsAndOtherStuff(Spam): 7 | this_should_be_something_really_long_a = models.CharField(max_length=100) 8 | this_should_be_something_really_long_b = models.CharField(max_length=100) 9 | this_should_be_something_really_long_c = models.CharField(max_length=100) 10 | this_should_be_something_really_long_d = models.CharField(max_length=100) 11 | -------------------------------------------------------------------------------- /test_project/test_app/tests.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file demonstrates writing tests using the unittest module. These will pass 3 | when you run "manage.py test". 4 | 5 | Replace this with more appropriate tests for your application. 6 | """ 7 | 8 | from django.test import TestCase 9 | 10 | 11 | class SimpleTest(TestCase): 12 | def test_basic_addition(self): 13 | """ 14 | Tests that 1 + 1 always equals 2. 15 | """ 16 | self.assertEqual(1 + 1, 2) 17 | -------------------------------------------------------------------------------- /test_project/test_app/views.py: -------------------------------------------------------------------------------- 1 | # Create your views here. 2 | -------------------------------------------------------------------------------- /test_project/test_commands.py: -------------------------------------------------------------------------------- 1 | import six 2 | import pytest 3 | 4 | from django_admin_generator.management.commands import admin_generator 5 | 6 | DEFAULTS = { 7 | 'date_hierarchy_names': 'date_joined', 8 | 'date_hierarchy_threshold': 0, 9 | 'list_filter_threshold': 0, 10 | 'raw_id_threshold': 0, 11 | 'prepopulated_field_names': ( 12 | 'username=first_name', 13 | 'last_name=full_name', 14 | 'this_should_be_something_really_long_a=' 15 | 'this_should_be_something_really_long_b'), 16 | } 17 | 18 | DEFAULTS_FILTERED = DEFAULTS.copy() 19 | DEFAULTS_FILTERED['date_hierarchy_threshold'] = 250 20 | DEFAULTS_FILTERED['list_filter_threshold'] = 250 21 | DEFAULTS_FILTERED['raw_id_threshold'] = 250 22 | 23 | 24 | @pytest.fixture 25 | def command(monkeypatch): 26 | return admin_generator.Command() 27 | 28 | 29 | def test_no_app(command): 30 | with pytest.raises(SystemExit): 31 | command.handle('some-non-existing-app') 32 | 33 | 34 | def test_parser(command): 35 | command.create_parser('manage.py', 'admin_generator') 36 | 37 | 38 | def check_output(capsys): 39 | out, err = capsys.readouterr() 40 | # Strip out encodings (and all other comments) so `compile` doesn't break 41 | out = six.u('\n').join(line for line in out.split('\n') 42 | if not line.startswith('#')) 43 | compile(out, 'admin.py', 'exec') 44 | 45 | 46 | @pytest.mark.django_db 47 | def test_app(command, capsys, monkeypatch): 48 | command = admin_generator.Command() 49 | command.handle(app='test_project.test_app', **DEFAULTS) 50 | check_output(capsys) 51 | 52 | 53 | @pytest.mark.django_db 54 | def test_auth(command, capsys): 55 | command.handle(app='django.contrib.auth', **DEFAULTS) 56 | check_output(capsys) 57 | 58 | 59 | @pytest.mark.django_db 60 | def test_auth_user(command, capsys): 61 | command.handle(app='django.contrib.auth', models=['user'], **DEFAULTS) 62 | check_output(capsys) 63 | 64 | 65 | @pytest.mark.django_db 66 | def test_app_filter(command, capsys): 67 | command.handle(app='test_project.test_app', **DEFAULTS_FILTERED) 68 | check_output(capsys) 69 | 70 | 71 | @pytest.mark.django_db 72 | def test_auth_filter(command, capsys): 73 | command.handle(app='django.contrib.auth', **DEFAULTS_FILTERED) 74 | check_output(capsys) 75 | 76 | 77 | @pytest.mark.django_db 78 | def test_auth_user_filter(command, capsys): 79 | command.handle(app='django.contrib.auth', models=['user'], 80 | **DEFAULTS_FILTERED) 81 | check_output(capsys) 82 | 83 | 84 | def test_no_database(command, capsys): 85 | command.handle(app='django.contrib.auth', models=['user'], 86 | no_query_db=True, **DEFAULTS_FILTERED) 87 | check_output(capsys) 88 | 89 | -------------------------------------------------------------------------------- /test_project/urls.py: -------------------------------------------------------------------------------- 1 | """test_project URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/1.10/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.conf.urls import url, include 14 | 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) 15 | """ 16 | from django import urls 17 | from django.contrib import admin 18 | 19 | urlpatterns = [ 20 | urls.path(r'^admin/', admin.site.urls), 21 | ] 22 | -------------------------------------------------------------------------------- /test_project/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for test_project project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.10/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "test_project.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = 3 | py3{7,8,9,10,11}-django32, 4 | py3{8,9,10,11}-django40, 5 | py3{8,9,10,11}-django41, 6 | ; py3{8,9,10,11}-django42, 7 | ; py3{8,9,10,11}-django50, 8 | ; py3{8,9,10,11}-django51, 9 | ; py3{8,9,10,11}-django52, 10 | ; py3{8,9,10,11}-django60, 11 | report, 12 | flake8, 13 | docs 14 | 15 | skip_missing_interpreters = True 16 | ;use_develop = True 17 | 18 | [testenv] 19 | deps = 20 | django32: Django>=3.2,<3.3 21 | django40: Django>=4.0,<4.1 22 | django41: Django>=4.1,<4.2 23 | ; django42: Django>=4.2,<4.3 24 | ; django50: Django>=5.0,<5.1 25 | ; django51: Django>=5.1,<5.2 26 | ; django52: Django>=5.2,<5.3 27 | ; django60: Django>=6.0,<6.1 28 | -e{toxinidir}[tests] 29 | depends = 30 | report: py3{7,8,9,10,11}-django32,py3{8,9,10,11}-django40,py3{8,9,10,11}-django41 31 | 32 | envlist = 33 | py3{7,8,9,10,11}-django32, 34 | py3{8,9,10,11}-django40, 35 | py3{8,9,10,11}-django41, 36 | ; py3{8,9,10,11}-django42, 37 | ; py3{8,9,10,11}-django50, 38 | ; py3{8,9,10,11}-django51, 39 | ; py3{8,9,10,11}-django52, 40 | ; py3{8,9,10,11}-django60, 41 | 42 | setenv = 43 | COVERAGE_FILE = {toxinidir}/.coverage.{envname} 44 | 45 | commands = 46 | py.test --cov-config={toxinidir}/.coveragerc {posargs:-v} 47 | skip_install = True 48 | changedir = {toxinidir} 49 | 50 | [testenv:report] 51 | skip_install = true 52 | deps = coverage 53 | commands = 54 | coverage combine 55 | coverage html 56 | coverage report --fail-under=100 57 | setenv = 58 | COVERAGE_FILE = 59 | 60 | [testenv:flake8] 61 | deps = flake8 62 | commands = flake8 --ignore=W391 django_admin_generator {posargs} 63 | 64 | [testenv:docs] 65 | basepython = python3 66 | deps = -e{toxinidir}[tests,docs] 67 | allowlist_externals = 68 | rm 69 | mkdir 70 | whitelist_externals = 71 | rm 72 | cd 73 | mkdir 74 | commands = 75 | sphinx-build -b html -d docs/_build/doctrees docs docs/_build/html {posargs} 76 | 77 | [testenv:coveralls] 78 | passenv = TRAVIS TRAVIS_JOB_ID TRAVIS_BRANCH 79 | 80 | commands = 81 | pip freeze 82 | python setup.py test 83 | coveralls 84 | 85 | deps = 86 | Django 87 | -e{toxinidir}[tests] 88 | coveralls 89 | 90 | [flake8] 91 | ignore = W391, W504, E741, W503, E131 92 | exclude = 93 | docs, 94 | build, 95 | .tox, 96 | test_project/*.py, 97 | 98 | --------------------------------------------------------------------------------