django admin inline many to many custom fields

17,827

Solution 1

class RowInline(admin.TabularInline):
    model = Table.rows.through
    fields = ['name']

This presents a problem because Table.rows.through represents an intermediate model. If you would like to understand this better have a look at your database. You'll see an intermediate table which references this model. It is probably named something like apname_table_rows. This intermeditate model does not contain the field, name. It just has two foreign key fields: table and row. (And it has an id field.)

If you need the name it can be referenced as a readonly field through the rows relation.

class RowInline(admin.TabularInline):
    model = Table.rows.through
    fields = ['row_name']
    readonly_fields = ['row_name']

    def row_name(self, instance):
        return instance.row.name
    row_name.short_description = 'row name'


class TableAdmin(admin.ModelAdmin):
    inlines = [
        RowInline,
    ]
    exclude = ('rows',)

Solution 2

Django can not display it as you expected. Because there is an intermediary join table which joins your tables. In your example:

admin.py:

class RowInline(admin.TabularInline):
    model = Table.rows.through  # You are not addressing directly Row table but intermediary table
    fields = ['name']

As the above note, model in RowInline addressing following table in your database, not Your Row table and model

table: your-app-name_table_row
--------------------------------
id       | int not null
table_id | int
row_id   | int

You can think it like there is an imaginary table in your model that joins the two tables.

class Table_Row(Model):
    table = ForeignKey(Table)
    row = ForeignKey(Row)

So if you edit your Inline as following

class RowInline(admin.TabularInline):
    model = Table.rows.through  # You are not addressing directly Row table but intermediary table
    fields = ['row', 'table']

you will not see any error or exception. Because your model in RowInline addresses an intermediary table and that table do have those fields. Django can virtualize the imaginary table Table_Row up to here and can handle this.

But we can use relations in admin, with using __. If your code do have a ForeignKey relation instead of ManyToManyField relation, then following be valid in your admin

 class Row(models.Model):
    name = models.CharField(max_length=255)

class Table(models.Model):
    rows = models.ForeignKey(Row, blank=True)
    name = models.CharField(max_length=255)

    def __unicode__(self):
        return self.name

and your admin:

class RowInline(admin.TabularInline):
    model = Table
    fields = ['rows__name']

Because you will have real Models and djnago can evaluate __ relation on them

But if you try that in your structure:

 class Row(models.Model):
    name = models.CharField(max_length=255)

class Table(models.Model):
    rows = models.ManyToManyField(Row, blank=True)
    name = models.CharField(max_length=255)

    def __unicode__(self):
        return self.name

and your admin:

class RowInline(admin.TabularInline):
    model = Table.rows.through 
    fields = ['row__name']

it will raise Exception! Because you do not have real table in your model, and django can not evaluate __ relations on virtual models it designs on top of its head.

Conclusion:

In your Inlines addressing ManyToMany relations, you are dealing with an imaginary intermediary model and you can not use fields orexclude attributes on that because your imaginary model do not have those fields and django can not handle relations ober that imaginary table. Following will be acceptable

class RowInline(admin.TabularInline):
    model = Table.rows.through 
    # No fields or exclude declarations in here

and django will display combo boxes for your virtual intermediary table options and add a fancy green + sign to add new records, but you will can not have inline fields to add new records directly to your database within the same single page. Djnago can not handle this on a single page.

You can try creating real intermediary table and show it using through, but thats totally a longer job and I do not test it to see its results.

Update: There is also the reason why django do not let something like that. Consider following:

          Table   | Table_Row |   Row
       -----------+-----------+---------
Start      5      |           |          
Step 1     5      |           |    1
Step 2     5      |    5-1    |    1  

At the beginning, You have a table with no related Rows, you want to add a row to the table... For joining a row with a table, you must first create a row so you execute step 1. After You do create your row, you can create Table_Row record to join these two. So in contains more than a single database insertion. Django crew may avoid such usage since it contains multiple inserts and operation is related more tables.

But this is just an assumption on the reason of the behavior.

Solution 3

In your admin.py try this

    class RowInline(admin.TabularInline):
        model = Table.rows.through
        list_display = ('name',)


    class TableAdmin(admin.ModelAdmin):
        inlines = [
            RowInline,
            ]
        readonly_fields = ('rows',)
Share:
17,827
maazza
Author by

maazza

? ç ?

Updated on June 13, 2022

Comments

  • maazza
    maazza almost 2 years

    Hi I am trying to customize my inlines in django admin.

    Here are my models:

    class Row(models.Model):
        name = models.CharField(max_length=255)
    
    class Table(models.Model):
        rows = models.ManyToManyField(Row, blank=True)
        name = models.CharField(max_length=255)
    
        def __unicode__(self):
            return self.name
    

    and my admin:

    class RowInline(admin.TabularInline):
        model = Table.rows.through
        fields = ['name']
    
    
    class TableAdmin(admin.ModelAdmin):
        inlines = [
            RowInline,
        ]
        exclude = ('rows',)
    

    However I get this error

    ImproperlyConfigured at /admin/table_app/table/1/

    'RowInline.fields' refers to field 'name' that is missing from the form.

    How is that possible ?