Admin, using one model to list records and another to change and add records

Hi,

for performance reasons I would like to base change_list admin form on one model and adding and changing records based on another model. The idea is to create ModelAdmin instances for both models, and customize view based model to change Add record url and Change record urls for individual records. In order to do that I overrode ModelAdmin add_view method and pointed to custom change_list_object_tools.html template. The problem is it does not work, meaning Add record url does not change.

Here are my models:

class TeleCenters(models.Model):
    id = models.BigAutoField(primary_key=True)
    name = models.CharField(unique=True, max_length=1024)
    phone = models.CharField(max_length=64, blank=True, null=True)
    voip = models.CharField(max_length=8, blank=True, null=True)
    ip_network = models.CharField(max_length=18)
    active = models.BooleanField()
    date_of_install = models.DateField()
    location = models.ForeignKey('Locations', models.DO_NOTHING)
    departments = models.ManyToManyField(Departments, through='TeleCentersDepartmentsLink')
    tele_center_types = models.ManyToManyField(TeleCenterTypes, through="TeleCentersTeleCenterTypesLink")

    def __str__(self):
        return self.name

    class Meta:
        managed = False
        db_table = 'tele_centers'
        verbose_name_plural = "Telemedicinski centri"
class TeleCentersListView(models.Model):
    id = models.BigIntegerField(primary_key=True)
    naziv = models.CharField(max_length=32)
    vrste_centara = models.CharField(max_length=32)
    ustanova = models.CharField(max_length=1024)
    lokacija = models.CharField(max_length=1024)
    adresa = models.CharField(max_length=1024)
    pbr = models.CharField(max_length=5)
    mjesto = models.CharField(max_length=1024)
    specijalnosti = models.CharField(max_length=2048)
    ip_network = models.CharField(max_length=18)
    voip = models.CharField(max_length=4)
    tel = models.CharField(max_length=16)
    carnet_ip_network = models.CharField(max_length=18)

    class Meta:
        managed = False  # Created from a view. Don't remove.
        db_table = 'tele_centers_list_view'
        verbose_name_plural = "Pregled telemedicinskih centara"

TeleCentersListView is based on database view which gives me all the information I need and has same ids as TeleCenters.

for each of the models I created admin forms:

@admin.register(TeleCenters)
class TeleCentersAdmin(admin.ModelAdmin):
    inlines = (TeleCentersDepartmentsLinksInline, TeleCentersTeleCenterTypesLinksInline)
    search_fields = ['name', 'ip_network', 'voip', 'phone']
    list_display = ['name', 'active', 'ip_network', 'voip',  'phone', 'location'] #,  'status'
    list_filter = ['location', 'name']
    autocomplete_fields = ['location', ]

    def response_add(self, request, obj, post_url_continue="../%s/"):
        if '_continue' not in request.POST:
            return HttpResponseRedirect(reverse('admin:healthnet_telecenterslistview_changelist'))
        else:
            return super(MyModelAdmin, self).response_add(request, obj, post_url_continue)

    def response_change(self, request, obj):
        if '_continue' not in request.POST:
            return HttpResponseRedirect(reverse('admin:healthnet_telecenterslistview_changelist'))
        else:
            return super(MyAdmin, self).response_change(request, obj)

@admin.register(TeleCentersListView)
class TeleCentersListViewAdmin(admin.ModelAdmin):
    search_fields = ['ustanova',  'lokacija', 'mjesto', 'adresa', 'ip_network', 'carnet_ip_network']
    list_display = ['change_record', 'vrste_centara', 'ustanova', 'lokacija', 'adresa', 'pbr', 'mjesto', 'specijalnosti', 'ip_network', 'voip', 'tel', 'carnet_ip_network']
    list_filter = ['ustanova', 'ip_network']
    change_list_template = 'admin/custom_change_list.html'

    def change_record(self, obj):
        link = reverse('admin:healthnet_telecenters_change', args=[obj.id])
        return format_html('<a href="{}"><b>{}</b></a>', link, obj.naziv)
    change_record.short_description = "Naziv"

    def add_view(self, request, form_url='', extra_context=None):
        extra_context = extra_context or {}
        add_new_record_model = 'TeleCenters'
        extra_context["add_model_url"] = 'admin:healthnet_%s_add' %(add_new_record_model.lower(), )
        print('Extra context: ', extra_context)
        return super(TeleCentersListViewAdmin, self).add_view(request, form_url, extra_context=extra_context)

changed template’s paths are:
///templates/admin/custom_change_list_object_tools.html and
///templates/admin/custom_change_list.html

and custom_change_list_object_tools.html looks like:

{% load i18n admin_urls %}

{% block object-tools-items %}
  {% if has_add_permission %}
  <li>
    {% url cl.add_model_url as add_url %}
    <a href="{% add_preserved_filters add_url is_popup to_field %}" class="addlink">
      {% blocktranslate with cl.opts.verbose_name as name %}Add {{ name }}{% endblocktranslate %}
    </a>
  </li>
  {% endif %}
{% endblock %}

What am I doing wrong? Is there a better way to accomplish what I want to do?

To answer myself:

there were two major errors

  1. ModelAdmin method to be customized is actualy changelist_view (not add_view)
  2. Template that needed to be changed is change_list_object_tools.html, changed version should be placed in //templates/admin/// directory

So, relevant admin classes look like:

@admin.register(TeleCenters)
class TeleCentersAdmin(admin.ModelAdmin):
    inlines = (TeleCentersDepartmentsLinksInline, TeleCentersTeleCenterTypesLinksInline)
    search_fields = ['name', 'ip_network', 'voip', 'phone']
    list_display = ['name', 'active', 'ip_network', 'voip',  'phone', 'location'] #,  'status'
    list_filter = ['location', 'name']
    autocomplete_fields = ['location', ]

    def response_add(self, request, obj, post_url_continue="../%s/"):
        if '_continue' not in request.POST:
            return HttpResponseRedirect(reverse('admin:healthnet_telecenterslistview_changelist'))
        else:
            return super(TeleCentersAdmin, self).response_add(request, obj, post_url_continue)

    def response_change(self, request, obj):
        if '_continue' not in request.POST:
            return HttpResponseRedirect(reverse('admin:healthnet_telecenterslistview_changelist'))
        else:
            return super(TeleCentersAdmin, self).response_change(request, obj)

    def has_module_permission(self, request):
        return False

    # list_select_related = ['location']

@admin.register(TeleCentersListView)
class TeleCentersListViewAdmin(admin.ModelAdmin):
    search_fields = ['ustanova',  'lokacija', 'mjesto', 'adresa', 'ip_network', 'carnet_ip_network']
    list_display = ['change_record', 'vrste_centara', 'ustanova', 'lokacija', 'adresa', 'pbr', 'mjesto', 'specijalnosti', 'ip_network', 'voip', 'tel', 'carnet_ip_network']
    list_filter = ['ustanova', 'ip_network']
    # change_list_template = 'admin/custom_change_list.html'

    def change_record(self, obj):
        link = reverse('admin:healthnet_telecenters_change', args=[obj.id])
        return format_html('<a href="{}"><b>{}</b></a>', link, obj.naziv)
    change_record.short_description = "Naziv"

    def changelist_view(self, request, extra_context=None): # , form_url=''
        extra_context = extra_context or {}
        add_new_record_model = 'TeleCenters'
        extra_context["add_model_url"] = 'admin:healthnet_%s_add' %(add_new_record_model.lower(), )
        extra_context["add_model_name"] = 'Telemedicine center'
        # print('Extra context: ', extra_context)
        return super(TeleCentersListViewAdmin, self).changelist_view(request, extra_context=extra_context) # form_url

and model template change_list_object_tools.html is now:

{% load i18n admin_urls %}

{% block object-tools-items %}
  {% if has_add_permission %}
  <li>
    {% comment %}
    {% url cl.opts|admin_urlname:'add' as add_url %}
    <a href="{% add_preserved_filters add_url is_popup to_field %}" class="addlink">
      {% blocktranslate with cl.opts.verbose_name as name %}Add  custom {{ name }}{% endblocktranslate %}
    {% url cl.opts.add_model_url as add_url %}
    <a href="{{ cl.opts.add_model_url }}" class="addlink">
    {% endcomment %}
    <a href="/healthnet/azuriranje/healthnet/telecenters/add/" class="addlink">
      {% blocktranslate with cl.opts.verbose_name as name %}Add {{ add_model_name }}{% endblocktranslate %}
    </a>
  </li>
  {% endif %}
{% endblock %}

Directory where customized template change_list_object_tools.html should be put is

<project>/<app>/templates/admin/<app>/<model>/