Inline-like solution for Django Admin where Admin contains ForeignKey to other model
Asked Answered
M

4

17

I have several Customers who book Appointments. Each Appointment has exactly one customer, though a customer can be booked for multiple appointments occurring at different times.

class Customer(model.Model):
    def __unicode__(self):
        return u'%s' % (self.name,)
    name = models.CharField(max_length=30)
    # and about ten other fields I'd like to see from the admin view.

class Appointment(models.Model):
    datetime = models.DateTimeField()
    customer = models.ForeignKey("Customer")
    class Meta:
        ordering = ('datetime',)

Now when an admin goes to browse through the schedule by looking at the Appointments (ordered by time) in the admin, sometimes they want to see information about the customer who has a certain appointment. Right now, they'd have to remember the customer's name, navigate from the Appointment to the Customer admin page, find the remembered Customer, and only then could browse their information.

Ideally something like an admin inline would be great. However, I can only seem to make a CustomerInline on the Appointment admin page if Customer had a ForeignKey("Appointment"). (Django specifically gives me an error saying Customer has no ForeignKey to Appointment). Does anyone know of a similar functionality, but when Appointment has a ForeignKey('Customer')?

Note: I simplified the models; the actual Customer field currently has about ~10 fields besides the name (some free text), so it would be impractical to put all the information in the __unicode__.

Matejka answered 21/6, 2011 at 1:2 Comment(0)
S
4

Completing @John's answer from above - define what you would like to see on the your changelist:

return '<a href="%s">%s</a>' % (
                     reverse('admin:applabel_customer_change', (self.customer.id,)),
                     self.customer.name # add more stuff here
             )

And to add this to the change form, see: Add custom html between two model fields in Django admin's change_form

Sessions answered 21/6, 2011 at 10:6 Comment(1)
It looks like at some point Django added ModelAdmin.list_display_links to abstract away the code for your solution.Hecto
C
7

There is no easy way to do this with django. The inlines are designed to follow relationships backwards.

Potentially the best substitute would be to provide a link to the user object. In the list view this is pretty trivial:

Add a method to your appointment model like:

def customer_admin_link(self):
    return '<a href="%s">Customer</a>' % reverse('admin:app_label_customer_change %s') % self.id
customer_admin_link.allow_tags = True
customer_admin_link.short_description = 'Customer'

Then in your ModelAdmin add:

list_display = (..., 'customer_admin_link', ...)

Another solution to get exactly what you're looking for at the cost of being a bit more complex would be to define a custom admin template. If you do that you can basically do anything. Here is a guide I've used before to explain: http://www.unessa.net/en/hoyci/2006/12/custom-admin-templates/

Basically copy the change form from the django source and add code to display the customer information.

Critta answered 21/6, 2011 at 3:6 Comment(1)
Thanks!! for valuable information but after doing all these things I'm getting error AttributeError: 'NoneType' object has no attribute 'id' Also, the provided link is not working :(Conakry
S
4

Completing @John's answer from above - define what you would like to see on the your changelist:

return '<a href="%s">%s</a>' % (
                     reverse('admin:applabel_customer_change', (self.customer.id,)),
                     self.customer.name # add more stuff here
             )

And to add this to the change form, see: Add custom html between two model fields in Django admin's change_form

Sessions answered 21/6, 2011 at 10:6 Comment(1)
It looks like at some point Django added ModelAdmin.list_display_links to abstract away the code for your solution.Hecto
R
1

In the ModelAdmin class for your Appointments, you should declare the following method:

class MySuperModelAdmin(admin.ModelAdmin):
  def get_form(self, request, obj=None, **kwargs):

    if obj:
      # create your own model admin instance here, because you will have the Customer's
      # id so you know which instance to fetch
      # something like the following
      inline_instance = MyModelAdminInline(self.model, self.admin_site)
      self.inline_instances = [inline_instance]

    return super(MySuperModelAdmin, self).get_form(request, obj, **kwargs)

For more information, browser the source for that function to give you an idea of what you will have access to.

https://code.djangoproject.com/browser/django/trunk/django/contrib/admin/options.py#L423

Rondure answered 21/6, 2011 at 15:9 Comment(1)
What should MyModelAdminInline be based on? Does it extend one of the Inline classes or the ModelAdmin class? (I've tried both but keep getting errors along the lines of X is not callable, has no attribute '_meta', adding a __call implementation helps a bit but I don't know what to create/return.) An example MyModelAdminInline would be quite helpful!Gosh
M
0

There is a library you can use it. https://github.com/daniyalzade/django_reverse_admin

But if you want to use link to object in showing table you can like this code:

def customer_link(self, obj):
    if obj.customer:
        reverse_link = 'admin:%s_%s_change' % (
            obj.customer._meta.app_label, obj.customer._meta.model_name)
        link = reverse(reverse_link, args=[obj.customer.id])
        return format_html('<a href="%s">More detail</a>' % link)
    return format_html('<span >-</span>')

customer_link.allow_tags = True
customer_link.short_description = 'Customer Info'

And in list_display:

list_display = (...,customer_link,...)
Mutism answered 16/2, 2023 at 5:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.