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?