Installed apps in Django are a convenient way to reuse your code (or someone else's) as long as the code matches your expectations. For apps that provide models or mixins or similar data functions, it's usually easy to use the documentation and integrate the app into your project. For apps that define views, templates, and URLs, it may take more work to integrate.

For instance, while developing this site, I developed a blog app to handle posts like this. It's a general blog setup with support for multiple authors and scaleable numbers of articles, comments, users, categories, etc. like would be expected from a blog app. For a one-human shop like mine, many of those URLs and views will not be used and I wanted to be lazy and not create templates for those views. I also didn't want to rewrite the app's URL configuration per installation. So, I began the search to disable those unused URLs.

The URLs involved in the app are of the form

urlpatterns = [
    ...
    path("overview/", views.over_view, name="app:overview"),
    path("article/<slug:slug>/", views.article_view, name="app:article"),
    path("author/<uuid:id>/", views.author_view, name="app:author"),
    path("category/<slug:slug>/", views.category_view, name="app:category"),
    ...
]

and are stored in the app's URL configuration (app/urls.py). I want the overview, article, and category URLs but I don't need the author URL since it's just me. There are other URLs; some have parameters and some don't. The goal is to direct the unused URLs to the overview and to do this from the project's URL configuration (project/urls.py). So, the URL configuration needs to look something like

urlpatterns = [
    ...
    # Other project and other app URLs.
    ...
    # Redirected URLs.
    # path("app/author/<uuid:id>/", view function, name),
    # Include the app URL configuration.
    path("app/", include("app.urls")),
    ...
]

which means that the view function must be imported, the view function must accomodate any URL parameters, and the URL must be named.

As a matter of design, I always include a catch-all view in my apps, usually named over_view. In my blog app, it originally had the signature

def over_view(request):
    """Render the blog overview."""
    ...

and is located in app/views.py. Since I want to use it project/urls.py, I have to import it

from app.views import over_view

I'm using the Django import conventions so I'm not configuring app as a module by adding things to app/__init__.py. Now, the project URL configuration can change to

from app.views import over_view


urlpatterns = [
    ...
    # Other project and other app URLs.
    ...
    # Redirected URLs.
    # path("app/author/<uuid:id>/", over_view, name),
    # Include the app URL configuration.
    path("app/", include("app.urls")),
    ...
]

Next, give the path a name to prevent any errors. I stick with names like the originals, like 'app_author', but since I don't intend to use them it shouldn't matter. Using 'app:author' would lead to a warning about ambiguous URLs since Django constructs its namespaced URLs as 'namespace:name'. Some testing indicates that it doesn't really matter, but I don't like warnings so I'll use 'app_author'.

from app.views import over_view


urlpatterns = [
    ...
    # Other project and other app URLs.
    ...
    # Redirected URLs.
    path("app/author/<uuid:id>/", over_view, name="app_author"),
    # Include the app URL configuration.
    path("app/", include("app.urls")),
    ...
]

The last problem is telling the over_view function to ignore arguments it doesn't understand. Currently, it will fail to run and complain about the unknown argument id. Let's change the signature to accept any named argument without changing the function, which effectively ignores the argument.

def over_view(request, **kwargs):
    """Render the blog overview."""
    ...

**kwargs should swallow any named parameters which are dutifully ignored.

That effectively ignores the app_author URL. This also works for URLs without parameters or with different parameters

from app.views import over_view


urlpatterns = [
    ...
    # Other project and other app URLs.
    ...
    # Redirected URLs.
    path("app/author/<uuid:id>/", over_view, name="app_author"),
    path("app/authors/", over_view, name="app_authors"),
    path("app/image/<slug:slug>/", over_view, name="app_image"),
    # Include the app URL configuration.
    path("app/", include("app.urls")),
    ...
]

effectively hiding the author, authors, and image and redirecting them to the over_view. Since the redirected URLs are before the app's default URLs, they are intercepted and redirected before the app handles them while the rest of the app's URLs are not changed.

There are other ways to customize an app's URLs. You can install an app and use its internals to reconstruct the views and URLs as you like, but you will need to be familiar with the app's code. You can install an app and edit its installed URL configuration, but that must be done every time you install the app. This method is an easy way to redirect some of the defined URLs and use the rest, which is probably the largest use case.