Django + Django Rest Framework: получение корректных связанных объектов на промежуточной модели

У меня есть промежуточная модель со следующими полями:

class UserSkill(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    skill = models.ForeignKey(Skill, on_delete=models.CASCADE, related_name='user_skills')
    disabled = models.BooleanField(default=False)

Как вы можете видеть, она имеет два внешних ключа, один к пользователю auth, а другой к другой таблице под названием skill.

Я пытаюсь получить все навыки, назначенные определенному пользователю, поэтому я делаю следующее get_queryset в моем ViewSet:

class AssignedSkillViewSet(viewsets.ModelViewSet):
    queryset = Skill.objects.all()
    serializer_class = AssignedSkillSerializer
    permission_classes = [permissions.IsAuthenticated]

    def get_queryset(self):
        user = self.request.user
        return Skill.objects.filter(user_skills__user=user, user_skills_user__disabled=False))

Теперь мне также нужно включить информацию о промежуточной модели в API, к которой я могу получить доступ через users_skills связанное имя в сериализаторе DRF, следующим образом:

class AssignedSkillSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Skill
        fields = [
            'id',
            'url',
            'title',
            'description',
            'user_skills',
        ]

Но когда я пытаюсь получить эту информацию, она возвращает ALL user_skills, связанные с назначенным навыком, независимо от того, назначены ли они другим пользователям. Мне нужна информация о связанной модели только для этого пользователя и этого навыка.

Например: Если у меня есть навык под названием Математика, и пользователь под названием Мария

related_skills = Skill.objects.filter(user_skills__user=user, user_skills_user__disabled=False)).user_skills.all()
<
[
  <UserSkill: Math+Jenniffer>,
  <UserSkill: Math+Gabriel>,
  <UserSkill: Math+John>,
  <UserSkill: Math+Maria>,
]
Приведенный выше код вернет:

Мне нужно получить только предмет <UserSkill: Math+Maria>. Список никак не упорядочен, поэтому получение последнего элемента в списке работает не во всех случаях.

Я знаю, что есть что-то, что я, вероятно, упускаю. Я ценю любую помощь или подсказки, которые вы можете мне дать.

Я думаю, что когда вы делаете фильтр:

Skill.objects.filter(
user_skills__user=user,   #condition_1
user_skills_user__disabled=False, #condition_2
).user_skills.all()

Вы уже выполнили запрос, относящийся к модели UserSkill. Потому что фильтр выполняется в модели Skill, а #condition_1 (user_skills__user=user) использует информацию из модели UserSkill для фильтрации по пользователям. Но когда вы делаете .user_skills.all() в конце запроса, вы перекрываете фильтр всеми данными из модели UserSkill.

Чтобы получить список экземпляров UserSkill из фильтра, вы можете попробовать:

UserSkill.objects.filter(
user="Maria", 
skill="Math",
)

Возможно, это поможет
. serializers.py

class SkillSerializer(serializers.ModelSerializer):
    class Meta:
        model = Skill
        fields = ['id', ...]

class UserSkillSerializer(serializers.ModelSerializer):
    skill_detail = SkillSerializer(many=True)
    class Meta:
        model = UserSkill
        fields = ['id', 'user', 'skill_detail']

views.py

class AssignedSkillViewSet(viewsets.ModelViewSet):
    queryset = UserSkill.objects.all()
    serializer_class = UserSkillSerializer
    permission_classes = [permissions.IsAuthenticated]

    def get_queryset(self):
        user = self.request.user
        return UserSkill.objects.filter(user=user, disabled=False))

В этом случае вам нужно использовать объект Prefetch...[Django-doc] с пользовательским кверисетом, который использует те же фильтры, что и ваш основной кверисет, вот так:

from django.db.models import Prefetch


def get_queryset(self):
    user = self.request.user

    return Skill.objects.filter(
        user_skills__user=user, 
        user_skills__user__disabled=False,
    ).prefetch_related(
        "user_skills",
        queryset=UserSkill.objects.filter(
            user=user, 
            user__disabled=False,
        )
    )
Вернуться на верх