-
-
Save jhgaylor/5873301 to your computer and use it in GitHub Desktop.
class A(models.Model): | |
things = models.ManyToManyField("B", through=ThroughModel) | |
class B(models.Model): | |
text = models.TextField() | |
class ThroughModel(models.Model): | |
a = models.ForeignKey(A) | |
b = models.ForeignKey(B) | |
extra = models.BooleanField() | |
#this will return a list of ThroughModel objects | |
ThroughModel.objects.filter(b=instance_of_b, extra=True) | |
#this will return a list of A objects based on an extra field on the through table | |
A.objects.filter(things__ThroughModel__extra=True) | |
#keep in mind that limiting by one of the foreign keys on the through model is easier | |
A.objects.filter(things=instance_of_b) |
A.objects.filter(things__ThroughModel__extra=True)
How is this possible? Which version of Django? I need it, but trying with 1.11.7 doesn't work. Thanks!
I'm on 2.1.3 and A.objects.filter(throughmodel__extra=True)
works for me.
great 👍
After spending some time on this, I'd second what @Palisand said here:
A.objects.filter(throughmodel__extra=True)
is the correct syntax in Django 2.2. Note that is is lowercase.
Thanks a lot - exactly what I needed. I did have to use the lowercase version for 2.2.
This post really helped me a lot! But I was still missing how to pull these through an API. So here is an example that builds upon yours that uses the many-to-many relationship of Books and Authors with the extra fields extra and external_url in ThroughModel we want to pull through an DRF endpoint. It also lists serializers, views and how things are connected. Hopefully others will find it useful despite the terrible naming :)
Assuming the data in the database looks like this:
Authors
| id | name | biography | date_of_birth |
|----|----------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------|---------------|
| 1 | Daniel Gerhard Brown | Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s | 1964-06-02 |
| 2 | Bob Brown Smith | Lorem Ipsum is simply dummy text of the printing and typesetting industry. | 1978-04-03 |
| 3 | Jane Doe | Lorem Ipsum is simply dummy text of the. | 1998-06-04 |
| 4 | Daniel Crawford | Lorem Ipsum is simply dummy text of the. | 1966-06-06 |
Books
| id | title | description | publisher | release_date |
|----|-------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------|--------------|--------------|
| 1 | The Origin | The origin is a 2017 mystery thriller novel by American utthor Dna Brown and the fifth installment in his Robert Langdon series. | Double Day | 2017-10-03 |
| 2 | Angels and Demons | Angels & Demons is a 2000 bestselling mystery-thriller novel written by American author Dan Brown and published by Pocket Books and then by Corgi Books | Pocket Books | 2017-05-01 |
| | | | | |
ThroughModel
| id | external_url | extra | author_id | book_id |
|----|--------------------|-------|-----------|---------|
| 0 | https://github.com | true | 1 | 1 |
| 1 | https://gitlab.com | false | 1 | 2 |
This is how the models, serializers, and views all connect together.
models.py
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=225)
biography = models.TextField()
date_of_birth = models.DateField()
books = models.ManyToManyField('Book', through='ThroughModel', blank=True, related_name='authors')
class Book(models.Model):
title = models.CharField(max_length=100)
description = models.CharField(max_length=400)
publisher = models.CharField(max_length=400)
release_date = models.DateField()
class ThroughModel(models.Model):
author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='author_books')
book = models.ForeignKey(Book, on_delete=models.CASCADE, related_name='book_authors')
external_url = models.TextField()
extra = models.BooleanField()`
serializers.py
from rest_framework import serializers
from django.contrib.auth.models import User
from .models import Author, Book, ThroughModel
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('username', 'password')
class ThroughModelSerializer(serializers.ModelSerializer):
# as of DRF 3.0 use serializers.ReadOnlyField instead of serializers.Field
author = serializers.ReadOnlyField(source='author.id')
book = serializers.ReadOnlyField(source='book.id')
class Meta:
model = ThroughModel
fields = ('author', 'book', 'external_url', 'extra')
class AuthorSerializer(serializers.ModelSerializer):
# note the use of the ThroughModelSerializer and the same field name as the related_name='author_books' in ThroughModel
author_books = ThroughModelSerializer(many=True)
class Meta:
ordering = ['-id']
model = Author
fields = ("id", "name", "biography", "date_of_birth", "author_books")
extra_kwargs = {'books': {'required': False}}
class BookSerializer(serializers.ModelSerializer):
book_authors = ThroughModelSerializer(many=True)
class Meta:
ordering = ['-id']
model = Book
fields = ("id", "title", "description", "publisher", "release_date", "book_authors")
extra_kwargs = {'authors': {'required': False}}
views.py
from rest_framework import viewsets
from .serializer import UserSerializer, AuthorSerializer, BookSerializer
from .models import Author, Book
from rest_framework import filters
class AuthorViewSet(viewsets.ModelViewSet):
queryset = Author.objects.all()
serializer_class = AuthorSerializer
class BookViewSet(viewsets.ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
filter_backends = [filters.OrderingFilter]
ordering_fields = ['release_date']
Finally, this will give results that look like this:
query /api/authors/
[
{
"id":2,
"name":"Bob Brown Smith",
"biography":"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type.",
"date_of_birth":"1978-04-03",
"author_books":[
]
},
{
"id":3,
"name":"Jane Doe",
"biography":"Lorem Ipsum is simply dummy text of the.",
"date_of_birth":"1998-06-04",
"author_books":[
]
},
{
"id":4,
"name":"Daniel Crawford",
"biography":"Lorem Ipsum is simply dummy text of the.",
"date_of_birth":"1966-06-06",
"author_books":[
]
},
{
"id":1,
"name":"Daniel Gerhard Brown",
"biography":"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.",
"date_of_birth":"1964-06-02",
"author_books":[
{
"author":1,
"book":1,
"external_url":"https://github.com",
"extra":true
},
{
"author":1,
"book":2,
"external_url":"https://gitlab.com",
"extra":false
}
]
}
]
query /api/books/
[
{
"id":1,
"title":"The Origin",
"description":"The origin is a 2017 mystery thriller novel by American utthor Dna Brown and the fifth installment in his Robert Langdon series.",
"publisher":"Double Day",
"release_date":"2017-10-03",
"book_authors":[
{
"author":1,
"book":1,
"external_url":"https://github.com",
"extra":true
}
]
},
{
"id":2,
"title":"Angels and Demons",
"description":"Angels & Demons is a 2000 bestselling mystery-thriller novel written by American author Dan Brown and published by Pocket Books and then by Corgi Books",
"publisher":"Pocket Books",
"release_date":"2017-05-01",
"book_authors":[
{
"author":1,
"book":2,
"external_url":"https://gitlab.com",
"extra":false
}
]
}
]
Oh God, I've been looking for this for 3 hours!
I am glad that people have been able to comment on this to keep it up to date. I had no idea it would still be helping people years later. I am glad it was helpful :)
Someone told me on stack that if all you have is:
class A(models.Model):
things = models.ManyToManyField("B")
class B(models.Model):
text = models.TextField()
(i.e. no extra fields for the relationship), there was a syntax to use in A.objects.filter(...)
to reference the "hidden" through model, in the corresponding way that it can be accessed using:
qs = A.things.through.objects.filter(Q(b__text__icontains="whatever"))
^^^ I know the above works without defining through
. I've tried it multiple times as I was trying to work all this out. But I was unable to find a corresponding way off the A model directly, such as:
qs = A.objects.filter(things__b__text__icontains="whatever")
^^^ which is wrong. So does there exist a way to reference the hidden through model in the filter expression from the class containing the M:M relationship (without explicitly defining through
)? I don't think there is, but you guys seem to know this topic well, so I suspect someone here knows...
I'm just curious. I figured out how to do what I wanted to do and learned a lot about all this, but I'm just wondering if the suggestion I received on stack was wrong - or whether I just couldn't figure it out...
A.objects.filter(throughmodel__extra=True)
It does not work for version 4.0.5
Please help me
Very useful snippet, thanks!