32

I defined several models: Journals, volumes, volume_scanInfo etc.

A journal can have more volumes and a volume can have more scanInfo.

What I want to do is:

  • in the admin page of journals I want to have the list of the volumes inline (done)
  • connect each volume of the previous list to its admin page where I can show the form to edit the volume and the list of its "scan info" inline.

so I want to have something like:

Journal #1 admin page
[name]
[publisher]
[url]
.....
list of volumes inline
    [volume 10] [..(other fields)..]   <a href="/link/to/volume/10">Full record</a>
    [volume 20] [..(other fields)..]   <a href="/link/to/volume/20">Full record</a>

Then

Volume #20 admin page
[volume number]
[..(other fields)...]
......
list of the scan info inline
    [scan info 33] [..(other fields)..]   <a href="/link/to/scaninfo/33">Full record</a>
    [scan info 44] [..(other fields)..]   <a href="/link/to/scaninfo/44">Full record</a>

What I tried to do is defining a model method that create the code and trying to use it inside the class that defines "volume inline" in the admin, but it doesn't work.

In other words

the model "Volume" has inside something like:

def selflink(self):
    return '<a href="/admin/journaldb/volume/%s/">Full record</a>' % self.vid
selflink.allow_tags = True

and

class VolumeInline(admin.TabularInline):
    fields = ['volumenumber', 'selflink']
    model = Volume
    extra = 1

But this gives the following error:

Exception Value: 'VolumeInline.fields' refers to field 'selflink' that is missing from the form.

Any idea?

Thanks, Giovanni

Serjik
  • 10,543
  • 8
  • 61
  • 70
Giovanni Di Milia
  • 13,480
  • 13
  • 55
  • 67

6 Answers6

29

UPDATE: Since Django 1.8, this is built in.

See this answer and the official documentation.

OLD ANSWER:

At the end I found a simple solution.

I create a new template called linked.html that is a copy of tabular.html and I added this code to create the link.

{% if inline_admin_form.original.pk %}
          <td class="{{ field.field.name }}">
              <a href="/admin/{{ app_label }}/{{ inline_admin_formset.opts.admin_model_path }}/{{ inline_admin_form.original.pk }}/">Full record</a>
          </td>
{% endif %}

then I created a new model LinkedInline inheriting InlineModelAdmin

#override of the InlineModelAdmin to support the link in the tabular inline
class LinkedInline(admin.options.InlineModelAdmin):
    template = "admin/linked.html"
    admin_model_path = None

    def __init__(self, *args):
        super(LinkedInline, self).__init__(*args)
        if self.admin_model_path is None:
            self.admin_model_path = self.model.__name__.lower()

Then when I define a new inline, I have only to use my LinkedInline instead of the normal InlineModelAdmin.

I hope it can be useful for other people.

Giovanni

Community
  • 1
  • 1
Giovanni Di Milia
  • 13,480
  • 13
  • 55
  • 67
  • 1
    When you save a child model page does it go back to the parent one ? – Pierre de LESPINAY Nov 09 '11 at 07:47
  • If you are editing a complete volume model linked to a journal, when you save, does it automatically return to the journal edition page ? – Pierre de LESPINAY Dec 01 '11 at 08:56
  • 3
    No, if you are inside the complete volume admin page, after saving you stay there. But since the volume has as foreign key the journal, I extended the foreign key form to print a link back to the journal, so you can go back with just one click. – Giovanni Di Milia Dec 01 '11 at 12:40
  • How does the LinkedInline find the template file? Did you put the template file into an existing template directory, instead of in a 'module' with the LinkedInline.py file? – RonLugge Sep 03 '13 at 22:56
  • Ignore my last comment... turns out the real issue is that apparently my remote object doesn't have a primary key (which is BS, because I defined one...) – RonLugge Sep 04 '13 at 00:04
25

Update:

As of Django 1.8, this is now built-in.

Answer for Django <= 1.7:

Keep your code in models.py with conditional case:

def selflink(self):
    if self.id:
        return "<a href='/link/to/volume/%s' target='_blank'>Edit</a>" % str(self.id)
    else:
        return "Not present"

selflink.allow_tags = True

In admin.py, add selflink as readonly field:

class VolumeInline(admin.TabularInline):
    readonly_fields = ['selflink',]
    model = Volume

That worked for me.

hurturk
  • 5,214
  • 24
  • 41
14

Here's a reusable mixin based on some of the other answers. This is handy because it works with both Tabular and Stacked inlines, and doesn't cruft up your model or admin code.

# put this somewhere like admin_helpers.py
from django.core.urlresolvers import reverse

class InlineEditLinkMixin(object):
    readonly_fields = ['edit_details']
    edit_label = "Edit"
    def edit_details(self, obj):
        if obj.id:
            opts = self.model._meta
            return "<a href='%s' target='_blank'>%s</a>" % (reverse(
                'admin:%s_%s_change' % (opts.app_label, opts.object_name.lower()),
                args=[obj.id]
            ), self.edit_label)
        else:
            return "(save to edit details)"
    edit_details.allow_tags = True

# admin.py

class VolumeInline(InlineEditLinkMixin, admin.TabularInline):
    fields = ['foo', 'bar', 'edit_details']

class JournalAdmin(admin.ModelAdmin):
    inlines = [VolumeInline]

class ScanInfoInline(InlineEditLinkMixin, admin.StackedInline):
    fields = ['foo', 'bar', 'edit_details']

class JournalAdmin(admin.ModelAdmin):
    inlines = [ScanInfoInline]
blueyed
  • 27,102
  • 4
  • 75
  • 71
Jack Cushman
  • 2,110
  • 22
  • 15
  • 3
    very helpful and neat. I need to add this on top of your code `from django.core.urlresolvers import reverse` – Charlesliam Jan 24 '14 at 03:02
  • I wonder if this could be made to work with many2many fieldsets automatically, where you have `model = ModelA.modelbs.through`? It would need to detect this (maybe via `_meta.auto_created`?) and create the edit link accordingly. – blueyed Jun 20 '14 at 00:36
9

In Django 1.8+ this is now much easier. Just add show_change_link = True to your TabularInline or StackedInline subclass, like this:

class VolumeInline(admin.TabularInline):
    fields = ['volumenumber']
    model = Volume
    extra = 1
    show_change_link = True

And Django will automatically add a link to the full change form for each inline item, if the model has its own registered ModelAdmin.

Carl Meyer
  • 122,012
  • 20
  • 106
  • 116
3

Did you try the Reversing admin URL system ?
That could give something like that (in the journal page):

<ul>
{% for volume in original.volume_set.all %}
  <li>
    <a href="{% url admin:yourapp_volume_change volume.id %}">Edit {{ volume }}
    </a>
  </li>
{% endfor %}
</ul>
Pierre de LESPINAY
  • 44,700
  • 57
  • 210
  • 307
2

After some fiddling, I was able to make this work in an InlineAdmin and a TabularInline, using reverse(). At least with TabularInline, the field you want to link must be listed in 'readonly_fields'

# create a read-only inline with the first field linked
from django.core import urlresolvers
class YOUR_MODEL_Inline(LinkedTabularInline):
    max_num = 0 # remove the "Add another ..." link
    model = YOUR_MODEL_NAME
    fk_name = "YOUR_FOREIGN_KEY_NAME"
    fields = [ 'link_name', ] # , 'field1', 'field2', 'etc' ]
    readonly_fields = fields
    can_delete = False
    def link_name(self, obj):
        if obj.pk:
            url = urlresolvers.reverse('admin:%s_%s_change'
                % (obj._meta.app_label, obj._meta.module_name), args=[obj.id])
            # obj.MODEL_FIELD can be a text string or whatever you want
            return '<a href="{0}">{1}</a>'.format(url, obj.MODEL_FIELD) 
    link_name.allow_tags = True
    link_name.short_description = "MODEL_FIELD"

If you want to link to the change list instead of the change view, you can modify the reverse() call. changelist does not require an object id.

    url = urlresolvers.reverse('admin:%s_%s_changelist' 
        % (obj._meta.app_label, obj._meta.module_name))
    return '<a href="{0}">{1}</a>'.format(url, obj.name)

And if you want to link to a subset of objects, you can add parameters to the URL:

    return '<a href="{0}?YOUR_MODEL_FIELD__id__exact={1}">{2}</a>'.format(url, obj.id, obj.name)
Eli Burke
  • 2,729
  • 27
  • 25