4

My django-admin page is located at

http://127.0.0.1:8000/admin/

Suppose there are 3 users in my website.

  1. Superuser
  2. Staff
  3. End user

If anyone of these three users tries to access this link http://127.0.0.1:8000/admin/, 1st and 2nd can access it. And End User is not able to access it. Till this, it is fine.

What I want is to do is to show Error 404 to End User. He/She should never know that this link is for admin page. Moreover, if anyone is not logged in, they should also see same 404 Page.

I have referred this link but it takes me to another default page.

PFA for the same


enter image description here


PS: I am using Signin With Google, so it should not redirect me there.

I am trying this since 3 continous days, so any help is highly appreciated. Thanks in advance.

Django-Version: 3. 0. 5

ajinzrathod
  • 925
  • 10
  • 28

2 Answers2

5

You first need to make a custom decorator that would give a 404 if the user is not a staff:

from django.http import Http404
from functools import wraps

def staff_required(func):
    @wraps(func)
    def wrapper(request, *args, **kwargs):
        if request.user.is_staff:
            return func(request, *args, **kwargs)
        raise Http404()
    return wrapper

Next we will use this decorator as described in your linked question,:

from django.contrib import admin

admin.site.login = staff_required(admin.site.login)

urlpatterns = [
    path('admin/', admin.site.urls),
]

Edit: Above method was a bit hacky it would show the login pages url to the user even if it gives a 404 error. It would be better to make a custom admin site class and use it. The admin site has a method admin_view which decorates the admin views which we shall override. We shall do this in the file admin.py in the projects main app (let's say the project is named myproject)

from functools import update_wrapper

from django.contrib import admin
from django.http import (
    Http404, HttpResponseRedirect,
)
from django.urls import reverse
from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_protect


class MyAdminSite(admin.AdminSite):
    def admin_view(self, view, cacheable=False):
        def inner(request, *args, **kwargs):
            if not self.has_permission(request):
                if request.path == reverse('admin:logout', current_app=self.name):
                    index_path = reverse('admin:index', current_app=self.name)
                    return HttpResponseRedirect(index_path)
                raise Http404()
            return view(request, *args, **kwargs)
        if not cacheable:
            inner = never_cache(inner)
        # We add csrf_protect here so this function can be used as a utility
        # function for any view, without having to repeat 'csrf_protect'.
        if not getattr(view, 'csrf_exempt', False):
            inner = csrf_protect(inner)
        return update_wrapper(inner, view)

Now we need to replace the default admin site with our custom admin site. To do this we will follow Overriding the default admin site [Django docs]:

In the projects main app's apps.py file (assuming project to be named myproject):

from django.contrib.admin.apps import AdminConfig

class MyAdminConfig(AdminConfig):
    default_site = 'myproject.admin.MyAdminSite'

Now in settings.py we will replace 'django.contrib.admin' with our own config class:

INSTALLED_APPS = [
    ...
    'myproject.apps.MyAdminConfig',  # replaces 'django.contrib.admin'
    ...
]
Abdul Aziz Barkat
  • 19,475
  • 3
  • 20
  • 33
  • In which file should I write `custom decorator` for admin. Never used this before. Any referral link is also appreciatable – ajinzrathod Mar 17 '21 at 13:16
  • 1
    @ajinzrathod althoug you can write this in your `urls.py` it would be better to write it in some different file let's say named `decorators.py` (do remember to import the decorator if you write in different file). – Abdul Aziz Barkat Mar 17 '21 at 13:17
  • `wrapper() missing 1 required positional argument: 'request'` Screenshot: https://imgur.com/bI7bdT1 – ajinzrathod Mar 17 '21 at 13:26
  • @ajinzrathod perhaps `method_decorator` is not needed? So `admin.site.login = staff_required(admin.site.login)`. Anyway this method is still a bit hacky I have added a better way to do this in the answer. – Abdul Aziz Barkat Mar 17 '21 at 14:07
  • Thank you :) You rock – ajinzrathod Mar 17 '21 at 14:19
  • You said "Above method was a bit hacky it would show the login pages url to the user even if it gives a 404 error" I did not get this line ? – ajinzrathod Mar 17 '21 at 14:21
  • 1
    @ajinzrathod In that method we are decorating `admin.site.login`, which basically means overriding the login view. What would happen is the user would first be redirected to the login view and then they would be given a 404, so because of the redirect the url (visible in the browsers navigation bar) would have changed to `/admin/login/` even if they had only written `/admin/` which would give an indication that there is actually a page there. – Abdul Aziz Barkat Mar 17 '21 at 14:24
  • Appreciatiable explanation :) – ajinzrathod Mar 17 '21 at 14:27
0

A simpler way is to create your own middleware that whenever the path starts with /admin/ and the user is logged in but doesn't have is_staff set to True (both staff and superusers have it), then raise the 404.

from django.http import Http404


class NoDjangoAdminForEndUserMiddleware:

    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):

        if request.path.startswith("/admin/"):
            if request.user.is_authenticated and not request.user.is_staff:
                raise Http404()

        response = self.get_response(request)

        return response

Then, to activate it, add it to the MIDDLEWARE list in your Django settings.

You'll still see the URL with http://127.0.0.1:8000/admin/, despite seeing a 404 page. If you want to redirect the user to a different page, instead of raising the Http404(), then return an HttpResponseRedirect().

Tiago Martins Peres
  • 14,289
  • 18
  • 86
  • 145