text: # -*- coding: utf-8 -*-
  """
  The Django Admin Generator is a project which can automatically generate
  (scaffold) a Django Admin for you. By doing this it will introspect your
  models and automatically generate an Admin with properties like:
  
   - `list_display` for all local fields
   - `list_filter` for foreign keys with few items
   - `raw_id_fields` for foreign keys with a lot of items
   - `search_fields` for name and `slug` fields
   - `prepopulated_fields` for `slug` fields
   - `date_hierarchy` for `created_at`, `updated_at` or `joined_at` fields
  
  The original source and latest version can be found here:
  https://github.com/WoLpH/django-admin-generator/
  """
  
  import re
  import sys
  
  from django.conf import settings
  from django.db import models
  
  from django_extensions.compat import get_apps, get_models_for_app
  from django_extensions.management.color import color_style
  from django_extensions.management.utils import signalcommand
  from django_extensions.compat import CompatibilityLabelCommand as LabelCommand
  
  # Configurable constants
  MAX_LINE_WIDTH = getattr(settings, 'MAX_LINE_WIDTH', 78)
  INDENT_WIDTH = getattr(settings, 'INDENT_WIDTH', 4)
  LIST_FILTER_THRESHOLD = getattr(settings, 'LIST_FILTER_THRESHOLD', 25)
  RAW_ID_THRESHOLD = getattr(settings, 'RAW_ID_THRESHOLD', 100)
  
  LIST_FILTER = getattr(settings, 'LIST_FILTER', (
      models.DateField,
      models.DateTimeField,
      models.ForeignKey,
      models.BooleanField,
  ))
  
  SEARCH_FIELD_NAMES = getattr(settings, 'SEARCH_FIELD_NAMES', (
      'name',
      'slug',
  ))
  
  DATE_HIERARCHY_NAMES = getattr(settings, 'DATE_HIERARCHY_NAMES', (
      'joined_at',
      'updated_at',
      'created_at',
  ))
  
  PREPOPULATED_FIELD_NAMES = getattr(settings, 'PREPOPULATED_FIELD_NAMES', (
      'slug=name',
  ))
  
  PRINT_IMPORTS = getattr(settings, 'PRINT_IMPORTS', '''# -*- coding: utf-8 -*-
  from django.contrib import admin
  
  from .models import %(models)s
  ''')
  
  PRINT_ADMIN_CLASS = getattr(settings, 'PRINT_ADMIN_CLASS', '''
  
  class %(name)sAdmin(admin.ModelAdmin):%(class_)s
  admin.site.register(%(name)s, %(name)sAdmin)
  ''')
  
  PRINT_ADMIN_PROPERTY = getattr(settings, 'PRINT_ADMIN_PROPERTY', '''
      %(key)s = %(value)s''')
  
  
  class UnicodeMixin(object):
      """Mixin class to handle defining the proper __str__/__unicode__
      methods in Python 2 or 3."""
  
      if sys.version_info[0] >= 3:  # Python 3
          def __str__(self):
              return self.__unicode__()
      else:  # Python 2
          def __str__(self):
              return self.__unicode__().encode('utf8')
  
  
  class AdminApp(UnicodeMixin):
      def __init__(self, app, model_res, **options):
          self.app = app
          self.model_res = model_res
          self.options = options
  
      def __iter__(self):
          for model in get_models_for_app(self.app):
              admin_model = AdminModel(model, **self.options)
  
              for model_re in self.model_res:
                  if model_re.search(admin_model.name):
                      break
              else:
                  if self.model_res:
                      continue
  
              yield admin_model
  
      def __unicode__(self):
          return ''.join(self._unicode_generator())
  
      def _unicode_generator(self):
          models_list = [admin_model.name for admin_model in self]
          yield PRINT_IMPORTS % dict(models=', '.join(models_list))
  
          admin_model_names = []
          for admin_model in self:
              yield PRINT_ADMIN_CLASS % dict(
                  name=admin_model.name,
                  class_=admin_model,
              )
              admin_model_names.append(admin_model.name)
  
      def __repr__(self):
          return '<%s[%s]>' % (
              self.__class__.__name__,
              self.app,
          )
  
  
  class AdminModel(UnicodeMixin):
      PRINTABLE_PROPERTIES = (
          'list_display',
          'list_filter',
          'raw_id_fields',
          'search_fields',
          'prepopulated_fields',
          'date_hierarchy',
      )
  
      def __init__(self, model, raw_id_threshold=RAW_ID_THRESHOLD,
                   list_filter_threshold=LIST_FILTER_THRESHOLD,
                   search_field_names=SEARCH_FIELD_NAMES,
                   date_hierarchy_names=DATE_HIERARCHY_NAMES,
                   prepopulated_field_names=PREPOPULATED_FIELD_NAMES, **options):
          self.model = model
          self.list_display = []
          self.list_filter = []
          self.raw_id_fields = []
          self.search_fields = []
          self.prepopulated_fields = {}
          self.date_hierarchy = None
          self.search_field_names = search_field_names
          self.raw_id_threshold = raw_id_threshold
          self.list_filter_threshold = list_filter_threshold
          self.date_hierarchy_names = date_hierarchy_names
          self.prepopulated_field_names = prepopulated_field_names
  
      def __repr__(self):
          return '<%s[%s]>' % (
              self.__class__.__name__,
              self.name,
          )
  
      @property
      def name(self):
          return self.model.__name__
  
      def _process_many_to_many(self, meta):
          raw_id_threshold = self.raw_id_threshold
          for field in meta.local_many_to_many:
              related_model = getattr(field.related, 'related_model', field.related.model)
              related_objects = related_model.objects.all()
              if related_objects[:raw_id_threshold].count() < raw_id_threshold:
                  yield field.name
  
      def _process_fields(self, meta):
          parent_fields = meta.parents.values()
          for field in meta.fields:
              name = self._process_field(field, parent_fields)
              if name:
                  yield name
  
      def _process_foreign_key(self, field):
          raw_id_threshold = self.raw_id_threshold
          list_filter_threshold = self.list_filter_threshold
          max_count = max(list_filter_threshold, raw_id_threshold)
          related_model = getattr(field.related, 'related_model', field.related.model)
          related_count = related_model.objects.all()
          related_count = related_count[:max_count].count()
  
          if related_count >= raw_id_threshold:
              self.raw_id_fields.append(field.name)
  
          elif related_count < list_filter_threshold:
              self.list_filter.append(field.name)
  
          else:  # pragma: no cover
              pass  # Do nothing :)
  
      def _process_field(self, field, parent_fields):
          if field in parent_fields:
              return
  
          self.list_display.append(field.name)
          if isinstance(field, LIST_FILTER):
              if isinstance(field, models.ForeignKey):
                  self._process_foreign_key(field)
              else:
                  self.list_filter.append(field.name)
  
          if field.name in self.search_field_names:
              self.search_fields.append(field.name)
  
          return field.name
  
      def __unicode__(self):
          return ''.join(self._unicode_generator())
  
      def _yield_value(self, key, value):
          if isinstance(value, (list, set, tuple)):
              return self._yield_tuple(key, tuple(value))
          elif isinstance(value, dict):
              return self._yield_dict(key, value)
          elif isinstance(value, str):
              return self._yield_string(key, value)
          else:  # pragma: no cover
              raise TypeError('%s is not supported in %r' % (type(value), value))
  
      def _yield_string(self, key, value, converter=repr):
          return PRINT_ADMIN_PROPERTY % dict(
              key=key,
              value=converter(value),
          )
  
      def _yield_dict(self, key, value):
          row_parts = []
          row = self._yield_string(key, value)
          if len(row) > MAX_LINE_WIDTH:
              row_parts.append(self._yield_string(key, '{', str))
              for k, v in value.items():
                  row_parts.append('%s%r: %r' % (2 * INDENT_WIDTH * ' ', k, v))
  
              row_parts.append(INDENT_WIDTH * ' ' + '}')
              row = '\n'.join(row_parts)
  
          return row
  
      def _yield_tuple(self, key, value):
          row_parts = []
          row = self._yield_string(key, value)
          if len(row) > MAX_LINE_WIDTH:
              row_parts.append(self._yield_string(key, '(', str))
              for v in value:
                  row_parts.append(2 * INDENT_WIDTH * ' ' + repr(v) + ',')
  
              row_parts.append(INDENT_WIDTH * ' ' + ')')
              row = '\n'.join(row_parts)
  
          return row
  
      def _unicode_generator(self):
          self._process()
          for key in self.PRINTABLE_PROPERTIES:
              value = getattr(self, key)
              if value:
                  yield self._yield_value(key, value)
  
      def _process(self):
          meta = self.model._meta
  
          self.raw_id_fields += list(self._process_many_to_many(meta))
          field_names = list(self._process_fields(meta))
  
          for field_name in self.date_hierarchy_names[::-1]:
              if field_name in field_names and not self.date_hierarchy:
                  self.date_hierarchy = field_name
                  break
  
          for k in sorted(self.prepopulated_field_names):
              k, vs = k.split('=', 1)
              vs = vs.split(',')
              if k in field_names:
                  incomplete = False
                  for v in vs:
                      if v not in field_names:
                          incomplete = True
                          break
  
                  if not incomplete:
                      self.prepopulated_fields[k] = vs
  
          self.processed = True
  
  
  class Command(LabelCommand):
      help = '''Generate a `admin.py` file for the given app (models)'''
      args = "[app_name]"
      can_import_settings = True
  
      def add_arguments(self, parser):
          parser.add_argument(
              '-s', '--search-field', action='append',
              default=SEARCH_FIELD_NAMES,
              help='Fields named like this will be added to `search_fields`'
              ' [default: %(default)s]')
          parser.add_argument(
              '-d', '--date-hierarchy', action='append',
              default=DATE_HIERARCHY_NAMES,
              help='A field named like this will be set as `date_hierarchy`'
              ' [default: %(default)s]')
          parser.add_argument(
              '-p', '--prepopulated-fields', action='append',
              default=PREPOPULATED_FIELD_NAMES,
              help='These fields will be prepopulated by the other field.'
              'The field names can be specified like `spam=eggA,eggB,eggC`'
              ' [default: %(default)s]')
          parser.add_argument(
              '-l', '--list-filter-threshold', type=int,
              default=LIST_FILTER_THRESHOLD, metavar='LIST_FILTER_THRESHOLD',
              help='If a foreign key has less than LIST_FILTER_THRESHOLD items '
              'it will be added to `list_filter` [default: %(default)s]')
          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` [default: %(default)s]')
  
      @signalcommand
      def handle(self, *args, **kwargs):
          self.style = color_style()
          installed_apps = dict((a.__name__.rsplit('.', 1)[0], a) for a in get_apps())
  
          # Make sure we always have args
          if not args:
              args = [False]
          app = args[0]
          if not installed_apps.get(app):
              print(self.style.WARN('This command requires an existing app name as argument'))
              print(self.style.WARN('Available apps:'))
              for app in sorted(installed_apps):
                  print(self.style.WARN('    %s' % app))
              sys.exit(1)
  
          model_res = []
          for arg in args[1:]:
              model_res.append(re.compile(arg, re.IGNORECASE))
  
          self.handle_app(app, model_res, **kwargs)
  
      def handle_app(self, app, model_res, **options):
          print(AdminApp(app, model_res, **options))
  