Django admin - OneToOneField inline throws "ForeignKey exception"

I have a very simple application that currently declares two models, one is called "Content" and just contains the content data, and the other is "Page" which includes "Content" as OneToOneField.

The reason I did this is because I can have "Page" as a specific concrete class that I am using, and when other models in other modules I plan to need page data, they can just include "Content" like OneToOneField. I did it this way to avoid inheritance and use composition.

models.py:

from django.db import models

class Content(models.Model):
    """Basic page data which can be used by other modules"""
    title = models.CharField(max_length=200)
    html_title = models.CharField(max_length=200)
    meta_desc = models.CharField(max_length=200)
    keywords = models.CharField(max_length=200)
    content = models.TextField()

class Page(models.Model):
    """Concrete implementation of a basic page managed by the admin"""
    slug = models.SlugField()
    content = models.OneToOneField(Content)

    def __str__(self):
        return self.content.title

      

admin.py:

from django.contrib import admin
from content.models import Page, Content

class ContentInline(admin.TabularInline):
    model = Content
    fields = ('title', 'html_title', 'meta_desc', 'keywords', 'content')

class PageAdmin(admin.ModelAdmin):
    fields = ('slug',)
    inlines = [ContentInline]

      

On the admin page I am getting this exception:

Exception at /admin/content/page/add/
<class 'content.models.Content'> has no ForeignKey to <class 'content.models.Page'>

      

What says is of course correct, but I can't seem to find a way to do what I want, which is to include the inline side of the non-defining side of the relationship. I do not want to declare a relationship in "Content", since then I will need to define each relationship to it within this class, which will inject dependencies to other modules, which, in my opinion, should not know anything.

Using Django 1.6 in Python 3.3.

Edit: As pointed out in the comments, I decided to use inheritance. My first concern with this was that I wanted the flexibility to be able to compose classes from several other classes. However, since the Django ORM supports multiple inheritance, and if I realized this method is called "mixins" (new to Python), I would have gotten somewhere much sooner.

Example mixins with models:

from django.db import models

class Content(models.Model):
    """Basic page data which can be used by other modules"""
    title = models.CharField(max_length=200)
    html_title = models.CharField(max_length=200)
    meta_desc = models.CharField(max_length=200)
    keywords = models.CharField(max_length=200)
    content = models.TextField()

    def __str__(self):
        return self.title

    class Meta:
        abstract = True

class Data(models.Model):
    data_name = models.CharField(max_length=200)

    class Meta:
        abstract = True

class Page(Content, Data):
    """Concrete implementation of a basic page managed by the admin"""
    slug = models.SlugField()

      

And then I can just use it as one model in admin.py.

+2


source to share


2 answers


Another solution is moving OneToOneField

from Content

toPage

class Content(models.Model):
    """Basic page data which can be used by other modules"""
    title = models.CharField(max_length=200)
    html_title = models.CharField(max_length=200)
    meta_desc = models.CharField(max_length=200)
    keywords = models.CharField(max_length=200)
    content = models.TextField()
    page = models.OneToOneField(Page, primary_key=True, related_name="content")

class Page(models.Model):
    """Concrete implementation of a basic page managed by the admin"""
    slug = models.SlugField()

    def __str__(self):
        return self.content.title

      

You can still do page.content

and the inline form will work out of the box

EDIT:

One of the downsides to this approach is that it will allow the user to create the page without assigning any content to it (in which case it fails page.content

)



It is very easy to solve this problem by creating a custom form

class ContentAdminForm(forms.ModelForm):

    def __init__(self, *args, **kwargs):
        kwargs["empty_permitted"] = False
        super(ContentAdminForm, self).__init__(*args, **kwargs)

      

Then on the admin page

class ContentInline(admin.TabularInline):

    model = Content
    form = ContentAdminForm
    fields = ('title', 'html_title', 'meta_desc', 'keywords', 'content')

      

0


source


If you don't want to change your models at all, there is a django module for displaying the non-defining inline side: django_reverse_admin

You need to add django_reverse_admin to your requirements.txt file:

-e git+https://github.com/anziem/django_reverse_admin.git#egg=django_reverse_admin

      



Then import it:

admin.py

from django.contrib import admin
from django_reverse_admin import ReverseModelAdmin

from content.models import Page, Content

# don't need to define an inline anymore for Content

class PageAdmin(ReverseModelAdmin):
    fields = ('slug',)

    inline_reverse = ['content']
    inline_type = 'tabular'  # or could be 'stacked'

      

-1


source







All Articles