Django searching a function for a string within it

I am attempting to setup a database driven search for the User Guide of a system. Overall I am writing a Django command that loops over the URLs in the User Guide, from that I need to also gather the Template that is being rendered by Django at the URL so I can render it to HTML and naturally search it, but gathering the Template is seeming to be quite difficult.

Here is my current code:

import importlib

from django.core.management.base import BaseCommand


def get_user_guide_urls_as_dict():
    """
    Used to return the list of url names and urls found in the application.
    {
       url_name: url
    }
    :return:
    """
    from django.apps import apps
    list_of_all_urls = list()

    class UserGuideConfig:
        def __init__(self):
            pass

    for name, app in apps.app_configs.items():
        # only gather user guide stuff
        if 'user_guide' in app.name:
            mod_to_import = f'apps.{name}.urls'
            urls = getattr(importlib.import_module(mod_to_import), "urlpatterns")
            list_of_all_urls.extend(urls)

    for url in list_of_all_urls:
        # the function/ callback should have the template name I need, 
        # how do I get it rendered as a string
        print(url.callback.__wrapped__)


class Command(BaseCommand):
    """
    Used as a method to generate user guide searchable items in db
    """
    help = ""

    def handle(self, *args, **options):
        # loop urls
        url_dict = get_user_guide_urls_as_dict()

The best way I can think is to take the URL object’s callback, turn it to a string and search for the return render line to parse the template name, but I cannot seem to figure out how to do that. For example the function:

def users(request):
    return render(request, 'user_guide/templates/user_guide/admin/users.html', {})

Answer

Well the inspect module comes to the rescue here. It allows me to gather the source of the function I am looking at.

Here is the updated code:

import importlib
import inspect
from io import StringIO
from pprint import pprint

from django.core.management.base import BaseCommand
from django.template.loader import render_to_string
from django.apps import apps
from bs4 import BeautifulSoup


def get_user_guide_items():
    """
    Used to return the list of url names and urls found in the
    application as well as the rendered HTML of the associated template.
    :return:
    """

    list_of_user_guide_items = list()

    class UserGuideItem:
        def __init__(self, url_name, url_link, template_path):
            self.url_name = url_name
            # the link does not have full path yet
            self.url_link = f'user_guide/{url_link}'
            self.template_path = template_path
            # now we can render to string
            self.template_html = render_to_string(template_path)
            # and use bs4 to extract title
            soup = BeautifulSoup(self.template_html, 'html.parser')
            self.template_title = soup.title

        def __str__(self):
            return f'{self.url_name}: {self.url_link}. {self.template_title}:{self.template_path}'

    for name, app in apps.app_configs.items():
        # only gather user guide stuff
        if 'user_guide' in app.name:
            mod_to_import = f'apps.{name}.urls'
            urls = getattr(importlib.import_module(mod_to_import), "urlpatterns")
            count = 0
            for url in urls:
                initial_count = count
                # get source of user guide
                function_source = inspect.getsource(url.callback.__wrapped__)
                # now find the template
                source = StringIO(function_source)
                for line in source:
                    # we always need to check for rendering, this is where the template is
                    if 'render(' in line:
                        path = line.split("'")[1::2][0]  # the [1::2] slices off the ', the [0] is the only url there
                        list_of_user_guide_items.append(UserGuideItem(url_name=url.name, url_link=url.pattern, template_path=path))
                        count += 1
                if initial_count == count:
                    # this url does not have a render method
                    # print(function_source)
                    pass
    return list_of_user_guide_items

class Command(BaseCommand):
    """
    Used as a method to generate user guide searchable items in db
    """
    def handle(self, *args, **options):
        url_items = get_user_guide_items()
        for item in url_items:
            print(item)