123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247 |
- # -*- coding: utf-8 -*-
- import functools
- import json
- import re
- import django
- from django.conf import settings
- from django.contrib.admindocs.views import simplify_regex
- from django.core.exceptions import ViewDoesNotExist
- from django.core.management.base import BaseCommand, CommandError
- from django.utils import translation
- from django_extensions.management.color import color_style, no_style
- from django_extensions.management.utils import signalcommand
- if django.VERSION >= (2, 0):
- from django.urls import URLPattern, URLResolver # type: ignore
- class RegexURLPattern: # type: ignore
- pass
- class RegexURLResolver: # type: ignore
- pass
- class LocaleRegexURLResolver: # type: ignore
- pass
- def describe_pattern(p):
- return str(p.pattern)
- else:
- try:
- from django.urls import RegexURLPattern, RegexURLResolver, LocaleRegexURLResolver # type: ignore
- except ImportError:
- from django.core.urlresolvers import RegexURLPattern, RegexURLResolver, LocaleRegexURLResolver # type: ignore
- class URLPattern: # type: ignore
- pass
- class URLResolver: # type: ignore
- pass
- def describe_pattern(p):
- return p.regex.pattern
- FMTR = {
- 'dense': "{url}\t{module}\t{url_name}\t{decorator}",
- 'table': "{url},{module},{url_name},{decorator}",
- 'aligned': "{url},{module},{url_name},{decorator}",
- 'verbose': "{url}\n\tController: {module}\n\tURL Name: {url_name}\n\tDecorators: {decorator}\n",
- 'json': '',
- 'pretty-json': ''
- }
- class Command(BaseCommand):
- help = "Displays all of the url matching routes for the project."
- def add_arguments(self, parser):
- super().add_arguments(parser)
- parser.add_argument(
- "--unsorted", "-u", action="store_true", dest="unsorted",
- help="Show urls unsorted but same order as found in url patterns"
- )
- parser.add_argument(
- "--language", "-l", dest="language",
- help="Only show this language code (useful for i18n_patterns)"
- )
- parser.add_argument(
- "--decorator", "-d", action="append", dest="decorator", default=[],
- help="Show the presence of given decorator on views"
- )
- parser.add_argument(
- "--format", "-f", dest="format_style", default="dense",
- help="Style of the output. Choices: %s" % FMTR.keys()
- )
- parser.add_argument(
- "--urlconf", "-c", dest="urlconf", default="ROOT_URLCONF",
- help="Set the settings URL conf variable to use"
- )
- @signalcommand
- def handle(self, *args, **options):
- style = no_style() if options['no_color'] else color_style()
- language = options['language']
- if language is not None:
- translation.activate(language)
- self.LANGUAGES = [(code, name) for code, name in getattr(settings, 'LANGUAGES', []) if code == language]
- else:
- self.LANGUAGES = getattr(settings, 'LANGUAGES', ((None, None), ))
- decorator = options['decorator']
- if not decorator:
- decorator = ['login_required']
- format_style = options['format_style']
- if format_style not in FMTR:
- raise CommandError(
- "Format style '%s' does not exist. Options: %s" % (
- format_style,
- ", ".join(sorted(FMTR.keys())),
- )
- )
- pretty_json = format_style == 'pretty-json'
- if pretty_json:
- format_style = 'json'
- fmtr = FMTR[format_style]
- urlconf = options['urlconf']
- views = []
- if not hasattr(settings, urlconf):
- raise CommandError("Settings module {} does not have the attribute {}.".format(settings, urlconf))
- try:
- urlconf = __import__(getattr(settings, urlconf), {}, {}, [''])
- except Exception as e:
- if options['traceback']:
- import traceback
- traceback.print_exc()
- raise CommandError("Error occurred while trying to load %s: %s" % (getattr(settings, urlconf), str(e)))
- view_functions = self.extract_views_from_urlpatterns(urlconf.urlpatterns)
- for (func, regex, url_name) in view_functions:
- if hasattr(func, '__globals__'):
- func_globals = func.__globals__
- elif hasattr(func, 'func_globals'):
- func_globals = func.func_globals
- else:
- func_globals = {}
- decorators = [d for d in decorator if d in func_globals]
- if isinstance(func, functools.partial):
- func = func.func
- decorators.insert(0, 'functools.partial')
- if hasattr(func, '__name__'):
- func_name = func.__name__
- elif hasattr(func, '__class__'):
- func_name = '%s()' % func.__class__.__name__
- else:
- func_name = re.sub(r' at 0x[0-9a-f]+', '', repr(func))
- module = '{0}.{1}'.format(func.__module__, func_name)
- url_name = url_name or ''
- url = simplify_regex(regex)
- decorator = ', '.join(decorators)
- if format_style == 'json':
- views.append({"url": url, "module": module, "name": url_name, "decorators": decorator})
- else:
- views.append(fmtr.format(
- module='{0}.{1}'.format(style.MODULE(func.__module__), style.MODULE_NAME(func_name)),
- url_name=style.URL_NAME(url_name),
- url=style.URL(url),
- decorator=decorator,
- ).strip())
- if not options['unsorted'] and format_style != 'json':
- views = sorted(views)
- if format_style == 'aligned':
- views = [row.split(',', 3) for row in views]
- widths = [len(max(columns, key=len)) for columns in zip(*views)]
- views = [
- ' '.join('{0:<{1}}'.format(cdata, width) for width, cdata in zip(widths, row))
- for row in views
- ]
- elif format_style == 'table':
- # Reformat all data and show in a table format
- views = [row.split(',', 3) for row in views]
- widths = [len(max(columns, key=len)) for columns in zip(*views)]
- table_views = []
- header = (style.MODULE_NAME('URL'), style.MODULE_NAME('Module'), style.MODULE_NAME('Name'), style.MODULE_NAME('Decorator'))
- table_views.append(
- ' | '.join('{0:<{1}}'.format(title, width) for width, title in zip(widths, header))
- )
- table_views.append('-+-'.join('-' * width for width in widths))
- for row in views:
- table_views.append(
- ' | '.join('{0:<{1}}'.format(cdata, width) for width, cdata in zip(widths, row))
- )
- # Replace original views so we can return the same object
- views = table_views
- elif format_style == 'json':
- if pretty_json:
- return json.dumps(views, indent=4)
- return json.dumps(views)
- return "\n".join([v for v in views]) + "\n"
- def extract_views_from_urlpatterns(self, urlpatterns, base='', namespace=None):
- """
- Return a list of views from a list of urlpatterns.
- Each object in the returned list is a three-tuple: (view_func, regex, name)
- """
- views = []
- for p in urlpatterns:
- if isinstance(p, (URLPattern, RegexURLPattern)):
- try:
- if not p.name:
- name = p.name
- elif namespace:
- name = '{0}:{1}'.format(namespace, p.name)
- else:
- name = p.name
- pattern = describe_pattern(p)
- views.append((p.callback, base + pattern, name))
- except ViewDoesNotExist:
- continue
- elif isinstance(p, (URLResolver, RegexURLResolver)):
- try:
- patterns = p.url_patterns
- except ImportError:
- continue
- if namespace and p.namespace:
- _namespace = '{0}:{1}'.format(namespace, p.namespace)
- else:
- _namespace = (p.namespace or namespace)
- pattern = describe_pattern(p)
- if isinstance(p, LocaleRegexURLResolver):
- for language in self.LANGUAGES:
- with translation.override(language[0]):
- views.extend(self.extract_views_from_urlpatterns(patterns, base + pattern, namespace=_namespace))
- else:
- views.extend(self.extract_views_from_urlpatterns(patterns, base + pattern, namespace=_namespace))
- elif hasattr(p, '_get_callback'):
- try:
- views.append((p._get_callback(), base + describe_pattern(p), p.name))
- except ViewDoesNotExist:
- continue
- elif hasattr(p, 'url_patterns') or hasattr(p, '_get_url_patterns'):
- try:
- patterns = p.url_patterns
- except ImportError:
- continue
- views.extend(self.extract_views_from_urlpatterns(patterns, base + describe_pattern(p), namespace=namespace))
- else:
- raise TypeError("%s does not appear to be a urlpattern object" % p)
- return views
|