52

I want to create custom page for admin panel without model. For first i copy index.html to project folder:

mysite/
    templates/
        admin/
            index.html

Then add to apps block my code:

<div class="module">
    <table summary="{% blocktrans with name="preferences" %}Models available in the preferences application.{% endblocktrans %}">
        <caption><a href="preferences" class="section">{% blocktrans with name="preferences" %}Preferences{% endblocktrans %}</a></caption>
            <tr>
                <th scope="row"><a href="preferences">Preferences</a></th>
                <td><a href="preferences" class="changelink">{% trans 'Change' %}</a></td>
            </tr>
    </table>
</div>

This works good, then I create new page /templates/admin/preferences/preferences.html and add to urls.py:

url(r'^admin/preferences/$', TemplateView.as_view(template_name='admin/preferences/preferences.html')),

And add code to preferences.html:

{% extends "admin/base_site.html" %}
{% block title %}Test page{% endblock %}

Run it and see message with error "The requested admin page does not exist.". What I do wrong?

Simeon Visser
  • 118,920
  • 18
  • 185
  • 180
Gr1N
  • 1,337
  • 3
  • 14
  • 19
  • To which `urls.py` did you add the URL? It may be that a more general regex in Django's admin is capturing the `/admin/preferences` so it never reaches your URL regex. – Simeon Visser Apr 07 '12 at 11:21
  • I have only one urls.py at /mysite/mysite/urls.py, I think that this is not error in urls because I haven't error with urls patterns. – Gr1N Apr 07 '12 at 11:27
  • You won't get an error message if this is the case. Have you tried changing the URL to something else to see if you do get the admin page in that case? For example, `^testadmin/preferences/$` ? – Simeon Visser Apr 07 '12 at 11:30
  • With this url works good. I can see my preferencs page, but I want use ^admin/preferences/$ url. – Gr1N Apr 07 '12 at 11:35

8 Answers8

36

You need to add your admin URL before the URL patterns of the admin itself:

urlpatterns = patterns('',
   url(r'^admin/preferences/$', TemplateView.as_view(template_name='admin/preferences/preferences.html')),
   url(r'^admin/', include('django.contrib.admin.urls')),
)

This way the URL won't be processed by Django's admin.

Simeon Visser
  • 118,920
  • 18
  • 185
  • 180
26

Years go by and still a relevant answer to this can be posted.

Using Django 1.10+ you can do:

security/admin.py (this is your app's admin file)

from django.contrib import admin
from django.conf.urls import url
from django.template.response import TemplateResponse
from security.models import Security


@admin.register(Security)
class SecurityAdmin(admin.ModelAdmin):

    def get_urls(self):

        # get the default urls
        urls = super(SecurityAdmin, self).get_urls()

        # define security urls
        security_urls = [
            url(r'^configuration/$', self.admin_site.admin_view(self.security_configuration))
            # Add here more urls if you want following same logic
        ]

        # Make sure here you place your added urls first than the admin default urls
        return security_urls + urls

    # Your view definition fn
    def security_configuration(self, request):
        context = dict(
            self.admin_site.each_context(request), # Include common variables for rendering the admin template.
            something="test",
        )
        return TemplateResponse(request, "configuration.html", context)

security/templates/configuration.html

{% extends "admin/base_site.html" %}
{% block content %}
...
{% endblock %}

See Official ModelAdmin.get_urls description (make sure you select proper Django version, this code is valid for 1.10 above)

Rui Carvalho
  • 3,376
  • 1
  • 21
  • 18
  • It's working without the security. I get an error while importing: ModuleNotFoundError: No module named 'security' – Rob Michiels Aug 05 '22 at 10:14
  • Can anyone explain to me why this solution uses both decorator and extends ModelAdmin? – kta May 21 '23 at 09:38
13

You should be using admin's get_urls.

Mitar
  • 6,756
  • 5
  • 54
  • 86
  • 14
    `get_urls` is a method of `ModelAdmin` which in turn needs a `Model` but the OP specifically wants "to create custom page for admin panel **without model**". (Emphasis added.) – Louis Jan 23 '15 at 13:05
  • 1
    He means the get_urls of AdminSite – pondermatic Jan 21 '20 at 03:28
  • Yes, see here: https://docs.djangoproject.com/en/dev/ref/contrib/admin/#adding-views-to-admin-sites – Mitar Jan 21 '20 at 04:00
6

If you want to create a custom page just to place there an arbitrary form to handle user input, you may give django-etc a try. There's etc.admin.CustomModelPage you can use:

    # admin.py
    from etc.admin import CustomModelPage

    class MyPage(CustomModelPage):
    
        title = 'My custom page'  # set page title

        # Define some fields you want to proccess data from.
        my_field = models.CharField('some title', max_length=10)

        def save(self):
            # Here implement data handling.
            super().save()

    # Register the page within Django admin.
    MyPage.register()
idle sign
  • 1,164
  • 1
  • 12
  • 19
5

Here's an example of everything that should be needed (as of Django 1.6) for a custom admin page that is linked to from a button next to the "History" button in the top right of an object's detail page:

https://gist.github.com/mattlong/4b64212e096766e058b7

mateolargo
  • 1,022
  • 10
  • 16
5

Full example:

from django.urls import path
from django.contrib import admin
from django.db import models

class DummyModel(models.Model):
    class Meta:
        verbose_name = 'Link to my shiny custom view'
        app_label = 'users'  # or another app to put your custom view

@admin.register(DummyModel)
class DummyModelAdmin(admin.ModelAdmin):
    def get_urls(self):
        view_name = '{}_{}_changelist'.format(
                DummyModel._meta.app_label, DummyModel._meta.model_name)
        return [
            path('my_view/', MyCustomView.as_view(), name=view_name)
        ]

With this approach Django's makemigrations command will create DB migration to create table for DummyModel.

Mark Mishyn
  • 3,921
  • 2
  • 28
  • 30
2

Extending the AdminSite class worked best for me, as per django's documentation. This solution protects the page(s) under the admin site login mechanism, and setting it up is easier than it may look:

  1. Where you have other admin code (eg. myapp/admin.py), extend the default class:
from django.contrib.admin import AdminSite

class CustomAdminSite(AdminSite):

    def get_urls(self):
        custom_urls = [
            path('admin/preferences/', self.admin_view(views.my_view)),
        ]
        admin_urls = super().get_urls()
        return custom_urls + admin_urls  # custom urls must be at the beginning


site = CustomAdminSite()

# you can register your models on this site object as usual, if needed
site.register(Model, ModelAdmin)
  1. Implement the view
def my_view(request):
    return render(request, 'admin/preferences/preferences.html')
  1. Use that admin site in urls.py, instead of the default one
from myapp import admin

# now use admin.site as you would use the default django one
urlpatterns = [
    # ...
    path('admin/', admin.site.urls),
    # ...
]
Arnaud P
  • 12,022
  • 7
  • 56
  • 67
0

If you want to hook a page into the existing admin site, then you can do the following, which is based on #arnaud-p's answer above. Arnaud's answer didn't work for me, as subclassing adminsite's get_url function lost access to existing admin pages until I added the registry as follows. Using the following method, your additional pages will require staff access, and you don't need to change your urls.py, so this is great for making admin pages for apps etc... You can pass each_context in the view in order to get permissions etc. works for django 3.2.9

In admin.py

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

from . import views

class CustomAdminSite(admin.AdminSite):
    
    def get_urls(self):
        self._registry = admin.site._registry
        admin_urls = super().get_urls() 
        custom_urls = [
            path('preferences/', views.Preferences.as_view(admin=self), name="preferences"),
        ]
        return custom_urls + admin_urls # custom urls must be at the beginning

    def get(self):
        request.current_app == self.name
        return super().get(request)

    def get_app_list(self, request):
        app_list = super().get_app_list(request)
        app_list += [
            {
                "name": "My Custom Preferences App",
                "app_label": "Preferences",
                # "app_url": "/admin/test_view",
                "models": [
                    {
                        "name": "Preferences",
                        "object_name": "preferences",
                        "admin_url": "/admin/preferences",
                        "view_only": True,
                    }
                ],
            }
        ]
        return app_list

site = CustomAdminSite()

the view...

class Preferences(views.generic.ListView):
    admin = {}
    def get(self, request):
        ctx = self.admin.each_context(request)
        return render(request, 'admin/preferences/preferences.html', ctx)

the template...

{% extends "admin/base_site.html" %}
{% block content %}
...HELLO WORLD!
{% endblock %}
miller the gorilla
  • 860
  • 1
  • 7
  • 19
  • I edited this, as my original, which had ```admin_urls = admin.site.urls```lost the get_app_list override from the super class, so it was never called. In order to get get_app_list to work, and obtain the normal app models registered, I had to set the customadmin._registry to admin.site._registry. – miller the gorilla May 23 '22 at 13:41