8

Is there any way to get a list of all views in an django app? I have googled for answer. All answers shows a way to get list of urls.

Netro
  • 7,119
  • 6
  • 40
  • 58
  • 2
    [This](http://stackoverflow.com/a/1275601/192390) returns a list of _registered_ views. – Ivan Oct 04 '15 at 12:52

4 Answers4

16

Getting list of all the views of a Django project:

To get all the views present in a Django project, we create a function get_all_view_names() which takes urlpatterns as input and returns the complete list of views being used in the project as the output.

First, we import the root_urlconf module using settings.ROOT_URLCONF. Then root_urlconf.urls.urlpatterns will give us the list of project's urlpatterns.

The above urlpatterns list contains RegexURLPattern and RegexURLResolver objects. Accessing .urlpatterns on a RegexURLResolver will further give us a list of RegexURLPattern and RegexURLResolver objects.

A RegexURLPattern object will give us the view name which we are interested in. The callback attribute on it contains the callable view. When we pass either a string in our urls like 'foo_app.views.view_name' representing the path to a module and a view function name, or a callable view, then callback attribute is set to this. Further accessing .func_name will give us the view name.

We call the function get_all_view_names() recursively and add the view names obtained from a RegexURLPattern object to a global list VIEW_NAMES.

from django.conf import settings
from django.core.urlresolvers import RegexURLResolver, RegexURLPattern

root_urlconf = __import__(settings.ROOT_URLCONF) # import root_urlconf module
all_urlpatterns = root_urlconf.urls.urlpatterns # project's urlpatterns
VIEW_NAMES = [] # maintain a global list

def get_all_view_names(urlpatterns):
    global VIEW_NAMES
    for pattern in urlpatterns:
        if isinstance(pattern, RegexURLResolver):
            get_all_view_names(pattern.url_patterns) # call this function recursively
        elif isinstance(pattern, RegexURLPattern):
            view_name = pattern.callback.func_name # get the view name
            VIEW_NAMES.append(view_name) # add the view to the global list 
    return VIEW_NAMES

get_all_view_names(all_urlpatterns)

Getting list of all the views in a Django application:

To get the list of all the views present in a Django application, we will use the get_all_view_names() function defined above.

We will first import all the urlpatterns of the application and pass this list to the get_all_view_names() function.

from my_app.urls import urlpatterns as my_app_urlpatterns # import urlpatterns of the app

my_app_views = get_all_view_names(my_app_urlpatterns) # call the function with app's urlpatterns as the argument

my_app_views gives us the list of all the views present in my_app Django app.

Rahul Gupta
  • 46,769
  • 10
  • 112
  • 126
10

Adding on to above fix by Rahul, if anyone is using Python3, you will need to use __name__ instead of func_name:

...
            view_name = pattern.callback.__name__
...

otherwise you will get the following:

AttributeError: 'function' object has no attribute 'get_all_view_names'

(Thanks to scipy-gitbot at https://github.com/scipy/scipy/issues/2101#issuecomment-17027406

As an alternative, if you are disinclined to using global variables, here is what I ended up using :

all_urlpatterns = __import__(settings.ROOT_URLCONF).urls.urlpatterns
detail_views_list = []

def get_all_view_names(urlpatterns):
    for pattern in urlpatterns:
        if isinstance(pattern, RegexURLResolver):
            get_all_view_names(pattern.url_patterns)
        elif isinstance(pattern, RegexURLPattern):
            detail_views_list.append(pattern.callback.__name__)
get_all_view_names(all_urlpatterns)
all_views_list = []

# remove redundant entries and specific ones we don't care about
for each in detail_views_list:
    if each not in "serve add_view change_view changelist_view history_view delete_view RedirectView":
        if each not in all_views_list:
            all_views_list.append(each)

Then you can just iterate through all_views_list to get the list of filtered views.

update: Mar 1 2018

In Django 2.0, django.core.urlresolvers is moved to django.urls. RegexURLPattern and RegexURLResolver are renamed to URLPattern and URLResolver. So you should use

from django.urls import URLResolver, URLPattern

instead of

from django.core.urlresolvers import RegexURLResolver, RegexURLPattern

if you are using Django 2.

ocavue
  • 283
  • 3
  • 5
Douglas
  • 724
  • 7
  • 11
2

Get all Django and DRF views w/o using global vars

    def get_all_views(urlpatterns, views=None):
        views = views or {}
        for pattern in urlpatterns:
            if hasattr(pattern, 'url_patterns'):
                get_all_views(pattern.url_patterns, views=views)
            else:
                if hasattr(pattern.callback, 'cls'):
                    view = pattern.callback.cls
                elif hasattr(pattern.callback, 'view_class'):
                    view = pattern.callback.view_class
                else:
                    view = pattern.callback
                views[pattern.name] = view

        return views
pymen
  • 5,737
  • 44
  • 35
1

I needed to count the number of views that were local to my project. Here's code that does that.

This differs from current answers because:

  • It groups views by the view object rather than the view name (which could collide with other view names)
  • Views that are under a ModelAdmin class are not included
  • The get_all_local_views function returns only the views within the current directory (or under settings.ROOT_DIR) that aren't under a venv directory
from importlib import import_module
import inspect
from pathlib import Path
import importlib.util

from django.conf import settings
from django.contrib.admin.options import ModelAdmin
from django.urls import URLResolver, URLPattern


def is_modeladmin_view(view):
    """Return True if the view is an admin view."""
    view = inspect.unwrap(view)  # In case this is a decorated view
    self = getattr(view, "__self__", None)
    return self is not None and isinstance(self, ModelAdmin)


def get_all_views(urlpatterns):
    """Given a URLconf, return a set of all view objects."""
    views = set()
    for pattern in urlpatterns:
        if hasattr(pattern, "url_patterns"):
            views |= get_all_views(pattern.url_patterns)
        else:
            if hasattr(pattern.callback, "cls"):
                view = pattern.callback.cls
            elif hasattr(pattern.callback, "view_class"):
                view = pattern.callback.view_class
            else:
                view = pattern.callback
            if not is_modeladmin_view(view):
                views.add(view)
    return views


def get_module_path(module_name):
    """Return the path for a given module name."""
    spec = importlib.util.find_spec(module_name)
    if spec is None:
        raise ImportError(f"Module '{module_name}' not found")
    return Path(spec.origin).resolve()


def is_subpath(path, directory):
    """Return True if path is below directory and isn't within a "venv"."""
    try:
        path.relative_to(directory)
    except ValueError:
        return False
    else:
        # Return True if view isn't under a directory ending in "venv"
        return not any(p.endswith("venv") for p in path.parts)


def get_all_local_views():
    """Return a set of all local views in this project."""
    root_urlconf = import_module(settings.ROOT_URLCONF)
    all_urlpatterns = root_urlconf.urlpatterns
    try:
        root_directory = settings.ROOT_DIR
    except AttributeError:
        root_directory = Path.cwd()  # Assume we're in the root directory
    return {
        view
        for view in get_all_views(all_urlpatterns)
        if is_subpath(get_module_path(view.__module__), root_directory)
    }


all_views = get_all_local_views()
print("Number of local views:", len(all_views))
Trey Hunner
  • 10,975
  • 4
  • 55
  • 114
  • This is the only answer that works with modern Django. The rest refer to URL resolver classes that don't exist anymore. – helmetwearer May 22 '23 at 19:02