80

I'm putting together the admin for a satchmo application. Satchmo uses OneToOne relations to extend the base Product model, and I'd like to edit it all on one page.

Is it possible to have a OneToOne relation as an Inline? If not, what is the best way to add a few fields to a given page of my admin that will eventually be saved into the OneToOne relation?

for example:

class Product(models.Model):
    name = models.CharField(max_length=100)
    ...

class MyProduct(models.Model):
    product = models.OneToOne(Product)
    ...

I tried this for my admin but it does not work, and seems to expect a Foreign Key:

class ProductInline(admin.StackedInline):
    model = Product
    fields = ('name',)

class MyProductAdmin(admin.ModelAdmin):
    inlines = (AlbumProductInline,)

admin.site.register(MyProduct, MyProductAdmin)

Which throws this error: <class 'satchmo.product.models.Product'> has no ForeignKey to <class 'my_app.models.MyProduct'>

Is the only way to do this a Custom Form?

edit: Just tried the following code to add the fields directly... also does not work:

class AlbumAdmin(admin.ModelAdmin):
    fields = ('product__name',)
Super Kai - Kazuya Ito
  • 22,221
  • 10
  • 124
  • 129
Jiaaro
  • 74,485
  • 42
  • 169
  • 190

5 Answers5

87

It's perfectly possible to use an inline for a OneToOne relationship. However, the actual field defining the relationship has to be on the inline model, not the parent one - in just the same way as for a ForeignKey. Switch it over and it will work.

Edit after comment: you say the parent model is already registered with the admin: then unregister it and re-register.

from original.satchmo.admin import ProductAdmin

class MyProductInline(admin.StackedInline):
    model = MyProduct

class ExtendedProductAdmin(ProductAdmin):
    inlines = ProductAdmin.inlines + (MyProductInline,)

admin.site.unregister(Product)
admin.site.register(Product, ExtendedProductAdmin)

Update 2020 (Django 3.1.1)

This method is still working but some types has changed in new Django version since inlines in ExtendedProductAdmin should now be added as list and not tuple, like this:

class ExtendedProductAdmin(ProductAdmin):
    inlines = ProductAdmin.inlines + [MyProductInline]

Or you will get this error:

    inlines = ProductAdmin.inlines + (MyProductInline,)
TypeError: can only concatenate list (not "tuple") to list
Zat42
  • 2,471
  • 4
  • 22
  • 36
Daniel Roseman
  • 588,541
  • 66
  • 880
  • 895
  • 3
    unfortunately the parent model is already registered with admin, and I would prefer not to go off patching/forking satchmo itself – Jiaaro Nov 16 '09 at 20:15
  • 2
    Any reccomendation for adding multiple sub-types in this way (since they'll all be a OneToOne to product)? – Jiaaro Nov 17 '09 at 13:18
  • 27
    I'm in the same boat where I have a one-to-one field and I need to switch the foreign keys between tables in order to be able to implement an inline one-to-one. I'd rather not have to change my database table to be able to achieve what I want - inlining the parent model into the child. Is there any way at all do this? Ideally, seeing as it's a one-to-one relationship, it shouldn't really matter which way around you do it and it should be at least technically feasible. Without doing much admin customisation I'm not sure what I'd be getting into! – Michael Bylstra Aug 07 '12 at 14:56
  • 1
    I agree with Michael. For me switching on which table the One2One is completely changes the logic in an undesired way. I don't want to disrupt the logic of my application only for the inline in the admin to work. – Karina Klinkevičiūtė Apr 08 '21 at 10:46
  • 1
    Same situation as Karina. It would be unlogical to change the location of the One2One field – Skratt Feb 27 '22 at 19:35
8

Maybe use inheritance instead OneToOne relationship

class Product(models.Model):
    name = models.CharField(max_length=100)
    ...

class MyProduct(Product):
    .....

Or use proxy classes

class ProductProxy(Product)
    class Meta:
        proxy = True

in admin.py

class MyProductInlines(admin.StackedInline):
    model = MyProduct

class MyProductAdmin(admin.ModelAdmin):
    inlines = [MyProductInlines]

    def queryset(self, request):
        qs = super(MyProductAdmin, self).queryset(request)
        qs = qs.exclude(relatedNameForYourProduct__isnone=True)
        return qs

admin.site.register(ProductProxy, MyProductAdmin)

In this variant your product will be in inline.

Alexey
  • 812
  • 13
  • 22
  • 2
    Could you expand on this? It is unclear to me what is going on – bryanph Nov 04 '15 at 09:17
  • I didn't knew about [Proxy Models](https://docs.djangoproject.com/en/dev/topics/db/models/#proxy-models) either – artu-hnrq Jun 09 '21 at 16:52
  • It seems method `queryset` on class `MyProductAdmin` should be renamed to `get_queryset` and also call to `super()` should use `get_queryset` as well. – M.z.D Mar 06 '23 at 12:09
5

Referring to the last question, what would be the best solution for multiple sub-types. E.g class Product with sub-type class Book and sub-type class CD. The way shown here you would have to edit a product the general items plus the sub-type items for book AND the sub-type items for CD. So even if you only want to add a book you also get the fields for CD. If you add a sub-type e.g. DVD, you get three sub-type field groups, while you actually only want one sub-type group, in the mentioned example: books.

Henri
  • 875
  • 2
  • 14
  • 22
2

You can also try setting 'parent_link=True' on your OneToOneField?

https://docs.djangoproject.com/en/dev/topics/db/models/#specifying-the-parent-link-field

stephendwolff
  • 1,382
  • 1
  • 13
  • 27
  • 3
    This doesn't seem to work for admin. This is probably only meant to create a back link to child in the parent class for ORM. – Divick Nov 11 '15 at 18:17
-1

Jun, 2022 Update:

Yes, it's possible to have inline for one-to-one relation.

For example, as shown below, if "MyProduct" class has "models.OneToOneField()" referring to "Product" class which means "MyProduct" class has the ForeignKey referring to "Product" class:

# "models.py"

from django.db import models

class Product(models.Model):
    name = models.CharField(max_length=100)

class MyProduct(models.Model):
    name = models.CharField(max_length=100)
    product = models.OneToOneField( # Here
        Product, 
        on_delete=models.CASCADE,
        primary_key=True
    )

Then, you can inline "MyProduct" class under "Product" class as shown below:

# "admin.py"

from django.contrib import admin
from .models import Product, MyProduct

class MyProductInline(admin.TabularInline):
    model = MyProduct

@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
    inlines = (MyProductInline, )

Oppositely, as shown below, if "Product" class has "models.OneToOneField()" referring to "MyProduct" class which means "Product" class has the ForeignKey referring to "MyProduct" class:

# "models.py"

from django.db import models

class MyProduct(models.Model):
    name = models.CharField(max_length=100)

class Product(models.Model):
    name = models.CharField(max_length=100)
    my_product = models.OneToOneField( # Here
        MyProduct, 
        on_delete=models.CASCADE,
        primary_key=True
    )

Then, you can inline "Product" class under "MyProduct" class as shown below:

# "admin.py"

from django.contrib import admin
from .models import Product, MyProduct

class ProductInline(admin.TabularInline):
    model = Product

@admin.register(MyProduct)
class MyProductAdmin(admin.ModelAdmin):
    inlines = (ProductInline, )
Super Kai - Kazuya Ito
  • 22,221
  • 10
  • 124
  • 129