Describing Relationships: Django's ManyToMany Through
In this post I'll describe extending many-to-many relationships in Django to support additional columns on the junction table. I'll be using the following table structure as the starting place. Imagine an RSS aggregator consisting of feeds, articles and categories. Articles come from a feed and may belong to any number of categories.
Suppose I wanted to implement a white-listing feature, wherein articles would only get added to categories if they matched a list of keywords assigned to that category. This is a good time to use a many-to-many through relationship. The idea here is that we are describing the intersection of two objects:
Django Implementation
Here is how the schema translates into Django model definitions:
class Feed(models.Model):
name = models.CharField(max_length=255)
url = models.URLField()
# Note the `through` keyword argument:
categories = models.ManyToManyField(Category, through='FeedCategoryRelationship')
source = models.ForeignKey(Source)
last_download = models.DateField(auto_now=True)
new_articles_added = models.PositiveSmallIntegerField(default=0, editable=False)
active = models.BooleanField(default=True)
...
class FeedCategoryRelationship(models.Model):
feed = models.ForeignKey(Feed)
category = models.ForeignKey(Category)
white_list = models.ManyToManyField(WhiteListFilter, blank=True)
...
Accessing related data is a snap:
def perform_download(self):
"""Download articles associated with this feed"""
for category in self.categories.all():
# Directly query the junction model:
relationship_queryset = FeedCategoryRelationship.objects.filter(feed=self, category=category)
for relationship in relationship_queryset.all():
whitelist = []
for white_list in relationship.white_list.all():
whitelist += white_list.keywords.split(',')
...
Admin Interface
By default, the FeedCategoryRelationship
is not exposed in either the Category
or the Feed
admin. To include the junction table in the Feed
and Category
admin interfaces, you can create an Inline
:
class FeedCategoryRelationshipInline(admin.TabularInline):
model = FeedCategoryRelationship
extra = 1
class FeedAdmin(admin.ModelAdmin):
inlines = (FeedCategoryRelationshipInline,)
class CategoryAdmin(admin.ModelAdmin):
inlines = (FeedCategoryRelationshipInline,)
prepopulated_fields = { "slug": ("name",) }
Further Reading
Comments (0)
Commenting has been closed.