Source code for django_admin_generator.management.commands.admin_generator

"""``admin_generator`` management command entry point."""

from __future__ import annotations

import pathlib
import re
import sys
import typing
from collections.abc import Mapping

from django.apps import AppConfig
from django.core.management.base import CommandParser
from django_utils.management.commands import base_command
from python_utils import logger

from django_admin_generator.constants import (
    DATE_HIERARCHY_NAMES,
    DATE_HIERARCHY_THRESHOLD,
    LIST_FILTER_THRESHOLD,
    PREPOPULATED_FIELD_NAMES,
    RAW_ID_THRESHOLD,
    SEARCH_FIELD_NAMES,
)
from django_admin_generator.discovery import get_apps, get_local_apps
from django_admin_generator.generators import AdminApp


[docs] class Command(base_command.CustomBaseCommand): # type: ignore[misc] help = """Generate a `admin.py` file for the given app (models)""" can_import_settings = True requires_system_checks = ('__all__',)
[docs] def add_arguments(self, parser: CommandParser) -> None: super().add_arguments(parser) parser.add_argument( '-s', '--search-field', dest='search_field_names', action='append', default=list(SEARCH_FIELD_NAMES), help='Fields named like this will be added to `search_fields`', ) parser.add_argument( '-d', '--date-hierarchy', dest='date_hierarchy_names', action='append', default=list(DATE_HIERARCHY_NAMES), help='A field named like this will be set as `date_hierarchy`', ) parser.add_argument( '--date-hierarchy-threshold', type=int, default=DATE_HIERARCHY_THRESHOLD, metavar='DATE_HIERARCHY_THRESHOLD', help='If a model has less than DATE_HIERARCHY_THRESHOLD items ' 'it will be added to `date_hierarchy`', ) parser.add_argument( '-p', '--prepopulated-fields', dest='prepopulated_field_names', action='append', default=list(PREPOPULATED_FIELD_NAMES), help='These fields will be prepopulated by the other field.' 'The field names can be specified like `spam=eggA,eggB,eggC`', ) parser.add_argument( '-l', '--list-filter-threshold', type=int, default=LIST_FILTER_THRESHOLD, metavar='LIST_FILTER_THRESHOLD', help='If a foreign key/field has less than LIST_FILTER_THRESHOLD ' 'items it will be added to `list_filter`', ) parser.add_argument( '-r', '--raw-id-threshold', type=int, default=RAW_ID_THRESHOLD, metavar='RAW_ID_THRESHOLD', help='If a foreign key has more than RAW_ID_THRESHOLD items ' 'it will be added to `list_filter`', ) parser.add_argument( '-n', '--no-query-db', action='store_true', dest='no_query_db', help="Don't query the database in order to decide whether " 'fields/relationships are added to `list_filter`', ) parser.add_argument( '-w', '--write', action='store_true', help='Write the output to the admin.py file(s)', ) parser.add_argument( '-o', '--output', default='admin.py', help='Output file name', ) parser.add_argument( '-f', '--force', action='store_true', help='Overwrite the output file if it exists', ) parser.add_argument( '-a', '--append', action='store_true', help='Append the output to the output file if it exists', ) parser.add_argument( 'app', help='App to generate admin definitions for, use `all` to ' 'generate for all local (i.e. not in site-packages) apps', ) parser.add_argument( 'models', nargs='*', help='Regular expressions to filter the models by', ) parser.add_argument( '--disable-json-widget', action='store_true', help='Disable the JSON widget import and formfield override', ) parser.add_argument( '--enable-reversion', action='store_true', help='Enable django-reversion support', ) parser.add_argument( '--reversion-admin-regex', default=r'.*', help='Regular expression to filter the models by for reversion', ) parser.add_argument( '--reversion-admin-class', default='VersionAdmin', help='The base class for the ModelAdmin classes for reversion', ) parser.add_argument( '--reversion-admin-class-import', default='from reversion.admin import VersionAdmin', help='The import statement for the base class for the ModelAdmin ' 'classes for reversion', ) parser.add_argument( '--admin-class', default='admin.ModelAdmin', help='The base class for the ModelAdmin classes', ) parser.add_argument( '--admin-class-import', default='', help='The import statement for the base class for the ModelAdmin ' 'classes if it is not the Django default', ) parser.add_argument( '--disable-auto-complete', action='store_true', help='Disable the auto-complete feature for the many-to-many ' 'fields', ) parser.add_argument( '--auto-complete', action='append', help='Enable the auto-complete feature only for the specified ' 'many-to-many fields', )
[docs] def warning( self, msg: object, *args: object, exc_info: logger._ExcInfoType = None, stack_info: bool = False, stacklevel: int = 1, extra: Mapping[str, object] | None = None, ) -> None: # This replaces the regular warning method from the CustomBaseCommand # since some Django installations capture all logging output # unfortunately. sys.stderr.write(str(msg)) sys.stderr.write('\n')
[docs] def handle( self, app: str | None = None, *args: typing.Any, **kwargs: typing.Any, ) -> None: super().handle(*args, **kwargs) if app == 'all': for app_config in get_local_apps(): self.handle_app(app_config, [], **kwargs) else: installed_apps: dict[str, AppConfig] = dict(get_apps()) target = installed_apps.get(app) if app else None if target is None: self.warning( 'This command requires an existing app name as argument' ) self.warning('Available apps:') for name in sorted(installed_apps): self.warning(f' {name}') sys.exit(1) model_res: list[re.Pattern[str]] = [ re.compile(model, re.IGNORECASE) for model in kwargs.get('models', []) ] self.handle_app(target, model_res, **kwargs)
[docs] def handle_app( self, app: AppConfig, model_res: list[re.Pattern[str]], write: bool = False, output: str | None = None, force: bool = False, append: bool = False, **options: typing.Any, ) -> None: fh: typing.IO[str] if output: if '/' in output or '\\' in output: output_path = pathlib.Path(output) else: output_path = pathlib.Path(app.path) / output print(f'Writing {app.name} to {output_path}', file=sys.stderr) if output_path.exists() and not force and not append: self.warning( f'The output file `{output}` already exists. Use the ' '`-f` or `-a` option to overwrite or append to it.' ) sys.exit(1) fh = ( output_path.open('a' if append else 'w') if write else sys.stdout ) print(f'Writing to {fh}', file=sys.stderr) else: fh = sys.stdout print(AdminApp(app, model_res, **options), file=fh) if output and fh is not sys.stdout: fh.close()