'Add a link to custom django admin view

I've created a custom admin view as documented here.

class MyAdmin(admin.ModelAdmin):
    def get_urls(self):
        urls = super().get_urls()
        my_urls = [
            path('stats/', self.admin_site.admin_view(self.stats)),
        ]
        return my_urls + urls

    def stats(self, request):
        request.current_app = self.admin_site.name

        context = dict(
           # Include common variables for rendering the admin template.
           self.admin_site.each_context(request),
           # Anything else you want in the context...
           key='blah',
        )
        return TemplateResponse(request, "sometemplate.html", context)

The URL is working and the template is loading. But how, can I get a link to my new custom view into the overview of the Django admin?

enter image description here



Solution 1:[1]

There is already a similar question How do you add a new entry into the django admin index?, but all of the provided answers were not very satisfying for me.

Therefore I ran through the source code and was looking for a possibility which wouldn't involve overriding any templates, neither index.html nor app_index.html.

The file django/contrib/admin/sites.py holds the code responsible for rendering index.html and app_index.html, the first is the template that displays what is illustrated in the question.

The method index renders the template index.html and displays the available apps with the registered model admins. It uses the method get_app_list to get the apps. Within this method the method _build_app_dict is called, which gets the models and the model admins.

The method app_index renders the template app_index.html and displays the registered model admins for a single app. It uses the method _build_app_dict, mentioned before.

Thus I decided to override this method in my custom admin. Based on the example in the question it can look like this (differences to the original example are shown in bold):

class MyAdmin(admin.AdminSite):
    def get_urls(self):
        urls = super().get_urls()
        my_urls = [
            path('stats/', self.admin_site.admin_view(self.stats), name='stats'),
        ]
        return my_urls + urls

    def stats(self, request):
        request.current_app = self.admin_site.name

        context = dict(
           # Include common variables for rendering the admin template.
           self.admin_site.each_context(request),
           # Anything else you want in the context...
           key='blah',
        )
        return TemplateResponse(request, "sometemplate.html", context)

    def _build_app_dict(self, request, label=None):
        # we create manually a dict to fake a model for our view 'stats'
        # this is an example how the dict should look like, i.e. which keys
        # should be present, the actual values may vary
        stats = {
            'name': 'Stats',
            'admin_url': reverse('my_admin:stats'),
            'object_name': 'Stats',
            'perms': {'delete': False, 'add': False, 'change': False},
            'add_url': ''
        }
        # get the app dict from the parent method
        app_dict = super(MyAdmin, self)._build_app_dict(request, label)
        # check if there is value for label, then the app_index will be rendered
        if label:
            # append the manually created dictionary 'stats'
            app_dict['models'].append(stats)
        # otherwise the index will be rendered
        # and we have to get the entry for our app,
        # which is in this case 'traffic'
        # using TrafficConfig.name or TrafficConfig.label
        # might be better than hard coding the value
        else:
            app = app_dict.get('traffic', None)
            # if an entry for 'traffic' has been found
            # we append our manually created dictionary
            if app:
                app['models'].append(stats)
        return app_dict

my_admin = MyAdmin(name='my_admin')
my_admin.register(Traffic)

Now when we open our custom admin we'll see something like this:


TRAFFIC
---------------------------------  
  Traffics    + Add   \ Change  
  Stats               \ Change  

This is because we manipulated the dictionary used to render the template and it uses the values we specified, in this case the most relevant is the name Stats, the admin_url which will call the custom view stats. Since we left add_url empty, there will be no + Add link displayed.

Important is also the penultimate line, where we call our custom admin and pass it a name, it will be used as a url namespace.

EDIT:
Unfortunately I noticed only after posting the answer that the question is asking how to display a link for a custom view created in a ModelAdmin, whereas my answer explains how to do this for a custom admin AdminSite. I hope it still of some help.

Solution 2:[2]

I know this is a bit old but I had the same issue and I found a simpler (yet maybe messier) way of doing it, that doesn't involve overriding templates or methods.

I just created a proxy model in models.py such as:

class Proxy(models.Model):

    id = models.BigAutoField(db_column='id', primary_key=True)

    def __str__(self):
        return "<Label: id: %d>" % self.id

    class Meta:
        managed = False
        verbose_name_plural = 'proxies'
        db_table = 'proxy'
        ordering = ('id',)

Which is just a mysql view that a created from am existing table

 create view proxy
    as select id
    from samples
    LIMIT 10;

And finally in admin.py

@admin.register(Proxy)
class LabelAdmin(admin.ModelAdmin):
    change_list_template = 'label_view.html'
    def changelist_view(self, request, extra_context=None):
      ...
        return render(request, "label_view.html", context)

Solution 3:[3]

Although not a good way, but you can try overriding the default index.html of the django admin as :

   {% for model in app.models %}
        <tr class="model-{{ model.object_name|lower }}">
        {% if model.admin_url %}
            <th scope="row"><a href="{{ model.admin_url }}">{{ model.name }}</a></th>
        {% else %}
            <th scope="row">{{ model.name }}</th>
        {% endif %}

        {% if model.add_url %}
            <td><a href="{{ model.add_url }}" class="addlink">{% trans 'Add' %}</a></td>
        {% else %}
            <td>&nbsp;</td>
        {% endif %}

        {% if model.admin_url %}
            <td><a href="{{ model.admin_url }}" class="changelink">{% trans 'Change' %}</a></td>
        {% else %}
            <td>&nbsp;</td>
        {% endif %}
        </tr>

   {% endfor %}
    <!-- extra link you want to display -->
    <tr>
    <th scope="row"><a href="link_url">link_name</a></th>
    <td><a href="link_url" class="changelink">{% trans 'Change' %}</a></td>
    </tr>

After overriding this block put index.html inside your templates/admin directory of your project. Hope this helps.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Mr Quenonoscaguen
Solution 3 not2acoder