Django re_path to catch all URLs except /admin/ not working properly

I'm trying to define a re_path in Django that catches all URLs except those that start with /admin/. My goal is to redirect unknown URLs to a custom view (RedirectView), while ensuring that all admin URLs (including subpaths like /admin/mymodel/something/) are excluded.

from django.urls import re_path, path
from django.contrib import admin
from . import views

urlpatterns = [
    path('admin/', admin.site.urls),  # Ensure Django admin URLs are handled
]

# Catch-all pattern that should exclude /admin/ URLs
urlpatterns += [
    re_path(r"^.*/(?!admin/$)[^/]+/$", views.RedirectView.as_view(), name="my-view"),
]

Issue:

URLs like localhost:8000/admin/mymodel/something/ are correctly ignored, which is good.

However, single-segment URLs like localhost:8000/something/ are not being matched, even though they should be redirected.

adjust your re_path that should exclude /admin/ URLs

    urlpatterns += [
    re_path(r"^(?!admin/).*", views.RedirectView.as_view(), name="my-view"),
]

My goal is to redirect unknown URLs to a custom view (RedirectView), while ensuring that all admin URLs (including subpaths like /admin/mymodel/something/) are excluded.

The good news is, Django matches paths top-to-bottom. So if the path is an admin/… it will first look for the admin.site.urls. Unless you set the .final_catch_all_view attribute [Django-doc] of the admin site to False, it has a catchall that will handle thus all paths with code/.

You thus can use a catchall with:

from django.contrib import admin
from django.urls import path, re_path

from . import views

urlpatterns = [
    path('admin/', admin.site.urls),  # Ensure Django admin URLs are handled
]
urlpatterns += [
    re_path(r'\A.*/\Z', views.RedirectView.as_view(), name='my-view'),
]

I would also include a trailing slash to let Django's APPEND_SLASH [Django-doc] logic first let do some work if the path has no trailing slash. But if not, you can remove that.

The new_pattern below might do the trick:

PATTERNS

original_pattern = r"^.*/(?!admin/$)[^/]+/$"

new_pattern = r"^[^/]*/(?!admin/)(?:[^/\n]+/)+$"

Demo to original_pattern: https://regex101.com/r/fp5aYg/1

Demo to new_pattern: https://regex101.com/r/2AWQcK/5

NOTES

The new_pattern can start with any non-forward slash characters or no characters. First forward slash / must not be followed by admin/. Each forward slash / must be followed by at least one or more non-forward slash characters [^/]+ followed by a forward slash /: /([^/]+/)+. The string MUST end in a forward slash /$. There must be at least two forward slashes in the string separated by a non-forward slash character. For example, the match will not match strings with two consecutive forward slashes like these: //something//something//, or something/something//something

  • ^[^/]* Start at the beginning of the string. Capture 0 or more non-forward slash characters [^/], i.e. match everything before the first forward slash /.

  • / the first forward slash.

  • (?!admin/) The first slash cannot be followed by admin/

  • (?:...)+ Non-capturing group (?:...) that repeats 1 or more times (+).

  • [^/\n]+ Capture any non-forward slash [^/], non-newline \n character 1 or more times. (Note: I had to ad the newline character \n to get the multi-line demo to work correctly.)

  • [^/\n]+/ A non-forward slash character strings are followed by one forward slash /.

  • $ End of string $ follows the second or later forward slash /$.

TEST STRINGS:

localhost:8000/admin/mymodel/something/
localhost:8000/something
localhost:8000/something/
localhost:8000/something/something/something/
localhost:8000
something/
/someting/a/
/someting//a/a/
/someting//
/someting//something//
/someting/something//
/someting//something//
something/something/
something/admin/something/
/something/admin/something/
/admin/
/admin/green/yellow/
/admin/hello/
/admin/hello/hello/
/admin/hello/hello/hello/
/admin/hello/hello
admin/green/yellow/
admin/hello/
admin/
/notadmin/path/something/
/notadmin/path/something
/something/admin/something/
/what/is/happening/
/no_admin/yellow/
/no_admin/yellow/green/red/

MATCHES:

localhost:8000/something/
localhost:8000/something/something/something/
/someting/a/
something/something/
/something/admin/something/
admin/green/yellow/
admin/hello/
/something/admin/something/
/what/is/happening/
/no_admin/yellow/
/no_admin/yellow/green/red/

NON-MATCHES:

localhost:8000/admin/mymodel/something/
localhost:8000/something
localhost:8000
something/
/someting//a/a/
/someting//
/someting//something//
/someting/something//
/someting//something//
something/admin/something/
/admin/
/admin/green/yellow/
/admin/hello/
/admin/hello/hello/
/admin/hello/hello/hello/
/admin/hello/hello
admin/
/notadmin/path/something
Back to Top