These are my models:
class Speed(models.Model):
id = models.UUIDField(primary_key=True, default=uuid4)
name = models.CharField(_('name'), max_length=128)
is_public = models.BooleanField(default=True)
user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, on_delete=models.CASCADE)
feedback = models.ManyToManyField(settings.AUTH_USER_MODEL, through='SpeedFeedback', related_name='+')
class SpeedFeedback(models.Model):
class Meta:
constraints = [
models.UniqueConstraint(fields=('user', 'speed'), name="fb_unique_user_speed")
]
# aka direction
vote = models.IntegerField(_('vote'), choices=Vote.choices, default=Vote.DEFAULT_STATE)
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
speed = models.ForeignKey(Speed, on_delete=models.CASCADE)
it’s an upvote/downvote system where each User may vote for some Speed object and each Speed object can have as many votes as there are users.
What I want to achieve is a query that returns all Speed objects and each Speed object should have additional fields from SpeedFeedback- vote and id, but only from an authenticated user (or each Speed object should have additional SpeedFeedback object as a field so I can get id and vote)
Here is the only way I was able to achieve something like that:
Speed.objects.filter(
Q(is_public=True) | Q(user=self.request.user)
).prefetch_related(
Prefetch('speedfeedback_set', queryset=SpeedFeedback.objects.filter(
user=self.request.user
), to_attr='speed_feedback'
)
)
and then by using serializer.MethodField get these fields:
def get_speed_feedback(self, obj):
if len(obj.speed_feedback) == 1:
feedback = obj.speed_feedback[-1]
return {
'id': feedback.id,
'vote': feedback.vote
}
return None
previously I tried to do the same with a subquery however I was able to retrieve only one column vote:
Speed.objects.filter(
Q(is_public=True) | Q(user=user)
).annotate(
speed_feedback=Subquery(
SpeedFeedback.objects.filter(
speed=OuterRef('pk'), user=user
).values('vote')
)
)
I feel like there is some simple way to achieve this with a subquery, especially that plural values method bothers me, however if I add values('vote', 'id') it wants an output_field and it seems like all of them are used for a single field.
In the documentation it says
There are times when a single column must be returned from a
Subquery,
as if it returns multiple columns by default, maybe I’m reading it wrong.
I have also found possibly some solution here however it requires Postgres/Psycopg being set up, and I would rather have something that works with SQLite for now, it also seems too complex for my case.
So is there some better way or is my query that uses prefetch_related good enough?