drf-extensions icon indicating copy to clipboard operation
drf-extensions copied to clipboard

Nested routers/urls are not filtering

Open krunal10 opened this issue 7 years ago • 3 comments

Hi all,

I can't get the my Router to filter my requests based on the "parents_query_lookup".

Here's my code:

urls.py:

from rest_framework_extensions.routers import ExtendedSimpleRouter
from .views import OrganizationViewSet, GroupViewSet, BootGroupViewSet


router = ExtendedSimpleRouter()
(router.register(r'organizations', OrganizationViewSet,
                 base_name='organization')
    .register(r'groups', GroupViewSet,  base_name='organizations-group',
              parents_query_lookups=['resource__organization'])
    .register(r'boot_groups', BootGroupViewSet,
              base_name='organizations-groups-boot_group',
              parents_query_lookups=['group__resource__organization', 'group']))

urlpatterns = router.urls

views.py:

from rest_framework.viewsets import ModelViewSet
from rest_framework_extensions.mixins import NestedViewSetMixin
from .models import Organization, OrganizationSerializer, \
    Group, GroupSerializer, BootGroup, BootGroupSerializer


class OrganizationViewSet(NestedViewSetMixin, ModelViewSet):
    queryset = Organization.objects.all()
    serializer_class = OrganizationSerializer


class GroupViewSet(NestedViewSetMixin, ModelViewSet):
    queryset = Group.objects.all()
    serializer_class = GroupSerializer


class BootGroupViewSet(NestedViewSetMixin, ModelViewSet):
    queryset = BootGroup.objects.all()
    serializer_class = BootGroupSerializer

enums.py:

class ResourceTypeEnum:

    RESOURCE_TYPE_GROUP = 'group'
    RESOURCE_TYPE_VM = 'vm'

    RESOURCE_TYPE_CHOICES = (
        (RESOURCE_TYPE_GROUP, RESOURCE_TYPE_GROUP),
        (RESOURCE_TYPE_VM, RESOURCE_TYPE_VM)
    )

models.py:

from django.db.models import Model
from rest_framework.serializers import ModelSerializer

from .enums import ResourceTypeEnum


class Organization(Model):
    organization_id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=255)
    parent = models.ForeignKey('self', blank=True, null=True)

    class Meta:
        unique_together = (("name", "parent"))
        verbose_name = "Organization"
        verbose_name_plural = "Organizations"
        app_label = 'api_manager'
        db_table = 'organization'

    def __unicode__(self):
        return self.name


class OrganizationSerializer(ModelSerializer):
    class Meta:
        model = Organization
        fields = ('name', 'parent')
        depth = 2


class Resource(Model):
    resource_id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=55)
    type = models.CharField(
        max_length=5, choices=ResourceTypeEnum.RESOURCE_TYPE_CHOICES)
    organization = models.ForeignKey(Organization)

    class Meta:
        verbose_name = "Resource"
        verbose_name_plural = "Resources"
        app_label = 'api_manager'
        db_table = 'resource'

    def __unicode__(self):
        return self.name


class ResourceSerializer(ModelSerializer):
    class Meta:
        model = Resource
        fields = ('name', 'type', 'organization')
        depth = 2


class Group(Model):
    resource = models.OneToOneField(Resource, primary_key=True)
    is_consistent = models.BooleanField()
    parent = models.ForeignKey('self', blank=True, null=True)

    class Meta:
        verbose_name = "Group"
        verbose_name_plural = "Groups"
        app_label = 'api_manager'
        db_table = 'group'

    def __unicode__(self):
        return "%s: %s" % (self.resource.organization, self.resource)


class GroupSerializer(ModelSerializer):
    class Meta:
        model = Group
        fields = ('resource', 'is_consistent', 'parent')
        depth = 2


class BootGroup(Model):
    boot_group_id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=100)
    boot_order = models.PositiveSmallIntegerField(default=1)
    group = models.ForeignKey(Group)

    class Meta:
        unique_together = (("boot_order", "group"))
        verbose_name = "BootGroup"
        verbose_name_plural = "BootGroups"
        app_label = 'api_manager'
        db_table = 'boot_group'

    def __unicode__(self):
        return "%s: %s" % (self.group.resource, self.name)


class BootGroupSerializer(ModelSerializer):
    class Meta:
        model = BootGroup
        fields = ('name', 'boot_order', 'group')
        depth = 2


class Vm(Model):
    resource = models.OneToOneField(Resource, primary_key=True)
    hostname = models.CharField(max_length=200, blank=True, null=True)
    group = models.ForeignKey(Group, blank=True, null=True)
    boot_group = models.ForeignKey(BootGroup, blank=True, null=True)

    class Meta:
        verbose_name = "Vm"
        verbose_name_plural = "Vms"
        app_label = 'api_manager'
        db_table = 'vm'

    def __unicode__(self):
        return "%s: %s" % (self.resource.organization, self.resource)


class VmSerializer(ModelSerializer):
    class Meta:
        model = Vm
        fields = ('resource', 'hostname', 'group',  'boot_group')
        depth = 2

No matter what I try, something like "organizations/1/groups" returns all of the Group models, regardless of Organization. Am I missing something? Thanks in advance for the help.

krunal10 avatar Sep 06 '17 04:09 krunal10

@krunal10 I know this is an old issue, but since I just ran into this myself I'll post the answer. You need to define the queryset for BootGroupViewSet and GroupViewSet to actually filter based on the parent lookup. Both of them are just doing a .all() on the tables for the queryset. If you implement get_queryset(self) in the viewset instead of setting the class property queryset, and return your filtered objects based on that key (you can use self.kwargs['parent_lookup_<replace_key_from_router>']) to get at the url parameter.

programatt avatar Feb 14 '18 17:02 programatt

Why should self.kwargs['parent_lookup_<replace_key_from_router>'] be used when NestedViewSetMixin has code to alter the queryset? I don't think using the key and filtering the data yourself in your ViewSet is how this feature should be used.

saadullahaleem avatar Sep 06 '18 04:09 saadullahaleem

This is how I got the filtering to work:

    def get_queryset(self):
        queryset = Submission.objects.filter(user=self.request.user)
        return self.filter_queryset_by_parents_lookups(queryset)

This is essentially what the get_queryset method in NestedViewSetMixin does.

Also, I don't understand why the get_queryset method in NestedViewSetMixin calls super().get_queryset. It isn't extending any class.

saadullahaleem avatar Sep 06 '18 07:09 saadullahaleem