Annotate queryset models with fields from "through" model

Viewed 26

This seems like it should be easy, but I've been unable to find an answer despite lots of googling.

I have two models joined by a many-to-many relationship through a third model that has some additional fields.

class User(AbstractUser):
    pass

class Project(models.Model):
    name = models.CharField(max_length=100, unique=True, blank=False)
    description = models.CharField(max_length=255, blank=True)
    users = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name='projects', through='UserProject')

    # additional project fields

class UserProject(models.Model):
    project = models.ForeignKey(Project, on_delete=models.CASCADE)
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    favorite = models.BooleanField(default=False)
    last_used = models.DateTimeField(default=timezone.now)

what I want is to be able to access a given user's projects along with the favorite and last_used fields for that user for each project.

I cannot figure out how to do that using annotations or any other method on a call to user.projects. If I call the through relationship directly (i.e., user.userproject_set), I can add fields from the project with a single sql query by calling, e.g., user.userproject_set.select_related('project').annotate(name='project.name'), but then the models in the resulting queryset are UserProjects, not Projects, which is not what I want.

Writing a sql query to do what I want would be trivial (it would basically be the same query Django produces to join the Project fields to the UserProject queryset). Is there a way to get Django to add fields from the through table to the models in a queryset accessed through one side of a many-to-many relationship?

1 Answers

Just use the related name to access the related objects. Using this, you should get Project objects:

user.projects.all()
Related