3

After a lot of searching and only finding a few techniques that will allow me to do this (and even fewer with working examples), I bring it to you.

Following is a class structure similar to that with which I'm working:

# sources/models.py
from django.db import models

class Author(models.Model):
    name = models.CharField(max_length=256)
    slug = models.SlugField()


class Source(models.Model):
    author = models.ForeignKey(Author)
    url = models.URLField(help_text='The URL where a copy of the source can be found.')


class Book(Source):
    title = models.CharField(max_length=256)
    page = models.PositiveSmallIntegerField(help_text='Page where the source text appears.')


class MagazineArticle(Source):
    magazine_name = models.CharField(max_length=256)
    issue_date = models.DateField()
    title = models.CharField(max_length=256)

And in a separate app, I would have this:

# excerpts/models.py
from django.db import models
from sources.models import Source

class Excerpt(models.Model):
    excerpt = models.TextField()
    source = models.ForeignKey(Source)
    # Perhaps should be:
    # source = models.OneToOneField(Source)

The catch being that in the admin, I want to be able to create either a Book or a MagazineArticle as the source for an excerpt without having separate fields in the excerpt for each.

One way I've read about doing this that might work is generic relations, possibly using an abstract base class instead, but I haven't found any examples that make sense in my context.

What are some methods of executing this (preferably with examples)?

radicalbiscuit
  • 487
  • 1
  • 6
  • 18

2 Answers2

0

Either one should work. This is how you would do it with an abstract base class:

class Excerpt(models.Model):
    excerpt = models.TextField()
    source = models.ForeignKey(Source)

    class Meta:
        abstract = True

class Book(Excerpt):
    pass
class Magazine(Excerpt):
    pass

Now you can do:

book = Book.objects.all()
magazine = Magazine.objects.filter(source=1)
dan-klasson
  • 13,734
  • 14
  • 63
  • 101
  • 1
    I think you misunderstood my model relations. A Book is not an Excerpt, neither is a MagazineArticle. Those are Sources for Excerpts. And Excerpt should not be an abstract class. If anything, Source might be abstract. – radicalbiscuit Jul 21 '13 at 22:53
  • @radicalbiscuit don't think of it as true inheritance. Think of excerpt as a [mix-in](http://stackoverflow.com/q/533631/1075247). – AncientSwordRage Feb 03 '15 at 09:34
  • @Pureferret indeed, I understand that aspect, but this solution still doesn't solve my problem (which I haven't revisited in quite some time). This structure would limit each `Book` or `Magazine` to a single excerpt, where I was expecting to be able to assign an arbitrary number of excerpts to a single source. I'll have to take a look at my problem again. It's been a long time and I've had more practice, so I might be able to work out a usable solution addressing my original concerns. – radicalbiscuit Feb 03 '15 at 19:31
  • @radicalbiscuit What about a generic foreign key? – AncientSwordRage Feb 03 '15 at 19:33
  • 1
    @Pureferret I think that's the conclusion I came to when I was working on a similar problem for a different project just a few months ago. When I originally asked this question, `GenericForeignKey` was too mysterious and felt too kludgy to me. Now I think that's likely the best solution. – radicalbiscuit Feb 03 '15 at 20:50
  • @radicalbiscuit Can you post a working example for the solution? – pymd Sep 15 '16 at 19:12
0

Your code is already the correct way to achieve what you want. What you have there is multi-table inheritance. Source has its own table and all the subclasses (Book, MagazineArticle) have their own. Any books or magazines you create will automatically create a source as well on the database side; while also behaving as a 'source with extra fields' when you are referring to the subclass models. Also note that one-to-one fields are created from subclass to base and base class to subclass. This is how the admin should look:

# admin.py
# imports go here...
source = Source() 
source.save()
excerpt1 = Excerpt(source=source)
book = Book() 
book.save()
except2 = Excerpt(source=book.source)  # source=book may also work; haven't checked... 
book2 = excerpt2.source.book
if book is book2:
    except2.save() # only save this if my code is correct... 
Seyi Shoboyejo
  • 489
  • 4
  • 11