Coder Social home page Coder Social logo

alanjds / drf-nested-routers Goto Github PK

View Code? Open in Web Editor NEW
1.6K 21.0 157.0 9.07 MB

Nested Routers for Django Rest Framework

Home Page: https://pypi.org/project/drf-nested-routers/

License: Apache License 2.0

Python 100.00%
django django-rest-framework rest nested-resources hacktoberfest

drf-nested-routers's Introduction

This is a work in progress. It "works for me" at www.apiregistro.com.br, but I cannot warranty that it fully "works everywhere" yet. Join us on Gitter (below) if you need some help.

drf-nested-routers

Join the chat at https://gitter.im/alanjds/drf-nested-routers Build Status

This package provides routers and fields to create nested resources in the Django Rest Framework

Nested resources are needed for full REST URL structure, if one resource lives inside another.

The following example is about Domains and DNS Nameservers. There are many domains, and each domain has many nameservers. The "nameserver" resource does not exist without a domain, so you need it "nested" inside the domain.

Requirements & Compatibility

  • Python (3.8, 3.9, 3.10, 3.11, 3.12)
  • Django (4.2, 5.0)
  • Django REST Framework (3.14, 3.15)

It may work with lower versions, but since the release 0.93.5 is no more tested on CI for Python 3.6 or lower.
And since 0.92.1 is no more tested on CI for Pythons 2.7 to 3.5, Django 1.11 to 2.1 or DRF 3.6 to 3.10.
Before that, the release 0.90.2 was tested also with DRF 2.4.3 up to 3.7.

Installation

You can install this library using pip:

pip install drf-nested-routers

It is not needed to add this library in your Django project's settings.py file, as it does not contain any app, signal or model.

Quickstart

The desired URL signatures are:

/domains/ <- Domains list
/domains/{pk}/ <- One domain, from {pk}
/domains/{domain_pk}/nameservers/ <- Nameservers of domain from {domain_pk}
/domains/{domain_pk}/nameservers/{pk} <- Specific nameserver from {pk}, of domain from {domain_pk}

How to do it (example):

# urls.py
from rest_framework_nested import routers
from views import DomainViewSet, NameserverViewSet
(...)

router = routers.SimpleRouter()
router.register(r'domains', DomainViewSet)

domains_router = routers.NestedSimpleRouter(router, r'domains', lookup='domain')
domains_router.register(r'nameservers', NameserverViewSet, basename='domain-nameservers')
# 'basename' is optional. Needed only if the same viewset is registered more than once
# Official DRF docs on this option: http://www.django-rest-framework.org/api-guide/routers/

urlpatterns = [
    path(r'', include(router.urls)),
    path(r'', include(domains_router.urls)),
]
# views.py

## For Django' ORM-based resources ##

class NameserverViewSet(viewsets.ModelViewSet):
    def get_queryset(self):
        return Nameserver.objects.filter(domain=self.kwargs['domain_pk'])

## OR: non-ORM resources ##

class NameserverViewSet(viewsets.ViewSet):
    def list(self, request, domain_pk=None):
        nameservers = self.queryset.filter(domain=domain_pk)
        (...)
        return Response([...])

    def retrieve(self, request, pk=None, domain_pk=None):
        nameservers = self.queryset.get(pk=pk, domain=domain_pk)
        (...)
        return Response(serializer.data)

Advanced

Hyperlinks for Nested resources

(optional) If you need hyperlinks for nested relations, you need a custom serializer.

There you will inform how to access the parent of the instance being serialized when building the children URL.

In the following example, an instance of Nameserver on /domain/{domain_pk}/nameservers/{pk} is being informed that the parent Domain should be looked up using the domain_pk kwarg from the URL:

# serializers.py
# (needed only if you want hyperlinks for nested relations on API)
from rest_framework_nested.relations import NestedHyperlinkedRelatedField

class DomainSerializer(HyperlinkedModelSerializer):
    class Meta:
        model = Domain

    nameservers = HyperlinkedIdentityField(
        view_name='domain-nameservers-list',
        lookup_url_kwarg='domain_pk'
                        # ^-- Nameserver queryset will .get(domain_pk=domain_pk)
                        #     being this value from URL kwargs
    )

	## OR ##

    nameservers = NestedHyperlinkedRelatedField(
        many=True,
        read_only=True,   # Or add a queryset
        view_name='domain-nameservers-detail',
        parent_lookup_kwargs={'domain_pk': 'domain__pk'}
                            # ^-- Nameserver queryset will .filter(domain__pk=domain_pk)
                            #     being domain_pk (ONE underscore) value from URL kwargs
    )

(optional) If you want a little bit more control over the fields displayed for the nested relations while looking at the parent, you need a custom serializer using NestedHyperlinkedModelSerializer.

from rest_framework.serializers import HyperlinkedModelSerializer
from rest_framework_nested.serializers import NestedHyperlinkedModelSerializer

class NameserverSerializers(HyperlinkedModelSerializer):
	class Meta:
		model = Nameserver
		fields = (...)


class DomainNameserverSerializers(NestedHyperlinkedModelSerializer):
	parent_lookup_kwargs = {
		'domain_pk': 'domain__pk',
	}
	class Meta:
		model = Nameserver
		fields = ('url', ...)


class DomainSerializer(HyperlinkedModelSerializer):
	class Meta:
		model = Domain
		fields = (..., 'nameservers')

	nameservers = DomainNameserverSerializers(many=True, read_only=True)

Infinite-depth Nesting

Example of nested router 3 levels deep. You can use this same logic to nest routers as deep as you need. This example ahead accomplishes the below URL patterns.

/clients/
/clients/{pk}/
/clients/{client_pk}/maildrops/
/clients/{client_pk}/maildrops/{pk}/
/clients/{client_pk}/maildrops/{maildrop_pk}/recipients/
/clients/{client_pk}/maildrops/{maildrop_pk}/recipients/{pk}/
# urls.py
router = DefaultRouter()
router.register(r'clients', ClientViewSet, basename='clients')
## generates:
# /clients/
# /clients/{pk}/

client_router = routers.NestedSimpleRouter(router, r'clients', lookup='client')
client_router.register(r'maildrops', MailDropViewSet, basename='maildrops')
## generates:
# /clients/{client_pk}/maildrops/
# /clients/{client_pk}/maildrops/{pk}/

maildrops_router = routers.NestedSimpleRouter(client_router, r'maildrops', lookup='maildrop')
maildrops_router.register(r'recipients', MailRecipientViewSet, basename='recipients')
## generates:
# /clients/{client_pk}/maildrops/{maildrop_pk}/recipients/
# /clients/{client_pk}/maildrops/{maildrop_pk}/recipients/{pk}/

urlpatterns = [
    path(r'', include(router.urls)),
    path(r'', include(client_router.urls)),
    path(r'', include(maildrops_router.urls)),
]
# views.py
class ClientViewSet(viewsets.ViewSet):
    serializer_class = ClientSerializer

    def list(self, request,):
        queryset = Client.objects.filter()
        serializer = ClientSerializer(queryset, many=True)
        return Response(serializer.data)

    def retrieve(self, request, pk=None):
        queryset = Client.objects.filter()
        client = get_object_or_404(queryset, pk=pk)
        serializer = ClientSerializer(client)
        return Response(serializer.data)


class MailDropViewSet(viewsets.ViewSet):
    serializer_class = MailDropSerializer

    def list(self, request, client_pk=None):
        queryset = MailDrop.objects.filter(client=client_pk)
        serializer = MailDropSerializer(queryset, many=True)
        return Response(serializer.data)

    def retrieve(self, request, pk=None, client_pk=None):
        queryset = MailDrop.objects.filter(pk=pk, client=client_pk)
        maildrop = get_object_or_404(queryset, pk=pk)
        serializer = MailDropSerializer(maildrop)
        return Response(serializer.data)


class MailRecipientViewSet(viewsets.ViewSet):
    serializer_class = MailRecipientSerializer

    def list(self, request, client_pk=None, maildrop_pk=None):
        queryset = MailRecipient.objects.filter(mail_drop__client=client_pk, mail_drop=maildrop_pk)
        serializer = MailRecipientSerializer(queryset, many=True)
        return Response(serializer.data)

    def retrieve(self, request, pk=None, client_pk=None, maildrop_pk=None):
        queryset = MailRecipient.objects.filter(pk=pk, mail_drop=maildrop_pk, mail_drop__client=client_pk)
        maildrop = get_object_or_404(queryset, pk=pk)
        serializer = MailRecipientSerializer(maildrop)
        return Response(serializer.data)
# serializers.py
class ClientSerializer(HyperlinkedModelSerializer):
    class Meta:
        model = Client
        fields = (...)


class MailDropSerializer(NestedHyperlinkedModelSerializer):
    parent_lookup_kwargs = {
        'client_pk': 'client__pk',
    }
    class Meta:
        model = MailDrop
        fields = (...)


class MailRecipientSerializer(NestedHyperlinkedModelSerializer):
    parent_lookup_kwargs = {
        'maildrop_pk': 'mail_drop__pk',
        'client_pk': 'mail_drop__client__pk',
    }
    class Meta:
        model = MailRecipient
        fields = (...)

Testing

In order to get started with testing, you will need to install tox. Once installed, you can then run one environment locally, to speed up your development cycle:

$ tox -e py39-django3.1-drf3.11

Once you submit a pull request, your changes will be run against many environments with GitHub Actions named CI.

License

This package is licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 and can undestand more at http://choosealicense.com/licenses/apache/ on the sidebar notes.

Apache Licence v2.0 is a MIT-like licence. This means, in plain English:

  • It's truly open source
  • You can use it as you wish, for money or not
  • You can sublicence it (change the licence!!)
  • This way, you can even use it on your closed-source project As long as:
  • You cannot use the authors name, logos, etc, to endorse a project
  • You keep the authors copyright notices where this code got used, even on your closed-source project (come on, even Microsoft kept BSD notices on Windows about its TCP/IP stack :P)

drf-nested-routers's People

Contributors

alanjds avatar browniebroke avatar buko106 avatar c17r avatar christiankreuzberger avatar decentral1se avatar dickermoshe avatar faheel avatar gitter-badger avatar grimborg avatar hooksie avatar intgr avatar irtazaakram avatar johnthagen avatar jsenecal avatar krectra avatar macro1 avatar michael-k avatar miketsukami avatar nschlemm avatar paultiplady avatar pddg avatar pyup-bot avatar ryangallen avatar ryanhiebert avatar sidrah-madiha avatar sorenisanerd avatar stuart23 avatar stuross avatar thedrow avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

drf-nested-routers's Issues

'Route' object has no attribute '__dict__'

Environment:

Request Method: GET
Request URL: http://127.0.0.1:8000/

Django Version: 1.5.4
Python Version: 2.7.5
Installed Applications:
('django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.sites',
 'django.contrib.messages',
 'django.contrib.staticfiles',
 'django.contrib.admin',
 'app1',
 'rest_framework')
Installed Middleware:
('django.middleware.common.CommonMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware')


Traceback:
File "D:\Programme\Python2\lib\site-packages\django\core\handlers\base.py" in get_response
  103.                     resolver_match = resolver.resolve(request.path_info)
File "D:\Programme\Python2\lib\site-packages\django\core\urlresolvers.py" in resolve
  319.             for pattern in self.url_patterns:
File "D:\Programme\Python2\lib\site-packages\django\core\urlresolvers.py" in url_patterns
  347.         patterns = getattr(self.urlconf_module, "urlpatterns", self.urlconf_module)
File "D:\Programme\Python2\lib\site-packages\django\core\urlresolvers.py" in urlconf_module
  342.             self._urlconf_module = import_module(self.urlconf_name)
File "D:\Programme\Python2\lib\site-packages\django\utils\importlib.py" in import_module
  35.     __import__(name)
File ".../RestFrameworkExample\RestFrameworkExample\urls.py" in <module>
  13. customer_router = routers.NestedSimpleRouter(router, r'customer', lookup='favourite_dish')
File ".../RestFrameworkExample\rest_framework_nested\routers.py" in __init__
  65.             route_contents = route.__dict__

Exception Type: AttributeError at /
Exception Value: 'Route' object has no attribute '__dict__'

You find my example code here.

Please extend example with NameserverViewSet

Thank you for coding this useful thing. I have a suggestion because I faced a problem while experimenting with your app.

Please consider extending example by providing NameserverViewSet code to make clear how {pk} and domain {domain_pk} should be used in ViewSets.
General example of ViewSet is below, but how to enable filtering by {domain_pk} is not clear, so I have all Nameserver objects listed.

class NameserverViewSet(viewsets.ModelViewSet):
    queryset = Nameserver.objects.all()
    serializer_class = NameserverSerializer

Nested resource does not work as expected.

Maybe I am getting it wrong, but I have the following problem:

/customer/1/ is (as expected) returning:

{
    "id": 1, 
    "user": 1, 
    "favourite_restaurants": [
        1
    ], 
    "favourite_dishes": [
        1, 
        2
    ]
}

However /customer/1/favourite_dishes/ is unexpectedly returning every existing dish:

[
    {
        "id": 1, 
        "name": "Pizza Mozarella"
    }, 
    {
        "id": 2, 
        "name": "Pizza Funghi"
    }, 
    {
        "id": 3, 
        "name": "Spaghetti Carbonara"
    }, 
    {
        "id": 4, 
        "name": "Spaghetti con Pesto"
    }
]

instead of what I expect:

[
    {
        "id": 1, 
        "name": "Pizza Mozarella"
    }, 
    {
        "id": 2, 
        "name": "Pizza Funghi"
    }
]

So, did I misunderstand the provided functionality, or is this a bug?

This is my code:
urls.py

# […]
from rest_framework_nested import routers
from app1.rest_views import CustomerViewSet, DishViewSet
router = routers.SimpleRouter()
router.register(r'customer', CustomerViewSet)
customer_router = routers.NestedSimpleRouter(router, r'customer', lookup='favourite_dish')
customer_router.register(r'favourite_dishes', DishViewSet)

urlpatterns = patterns(
    #url(r'^', include(router.urls)),
    #url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),

    url(r'^', include(router.urls)),
    url(r'^', include(customer_router.urls)),
)

models.py

# […]

class Restaurant(models.Model):
    name = models.TextField()

class Dish(models.Model):
    name = models.TextField()

class Customer(models.Model):
    user = models.OneToOneField(User)
    favourite_restaurants = models.ManyToManyField(Restaurant, blank=True)
    favourite_dishes = models.ManyToManyField(Dish, blank=True)

HyperlinkedRelatedField doesn't work

  1. you assume that there is some "domain" foreign key field in the model
  2. you have a mistake "domain_lokup_field" at line 58, rest_framework_nested/relations.py

many to many relation?

Can we use the nested router lookup field for many to many?
i am using the routing for country/state/city/area

.../county/1/state/2/city/3/area/

My database is using m2m, im not sure how i can config router.

router = DefaultRouter()
router.register(r'country', CountryViewSet)

states_router = routers.NestedSimpleRouter(router, r'country', lookup='xxcountry')
states_router.register(r'states', StateViewSet, base_name='states')

cities_router = routers.NestedSimpleRouter(states_router, r'states', lookup='states')
cities_router.register(r'cities', CityViewSet, base_name='cities')

Models:

class City(models.Model):
    name = models.CharField(max_length=30, unique=True)
    areas = models.ManyToManyField(Area, related_name='areas')

    def __unicode__(self):
        return u"%s" % (self.name)

class State(models.Model):
    name = models.CharField(max_length=30, unique=True)
    cities = models.ManyToManyField(City, related_name='cities')

    def __unicode__(self):
        return u"%s" % (self.name)

class Country(models.Model):
    name = models.CharField(max_length=30, unique=True)
    states = models.ManyToManyField(State, related_name='states')

    def __unicode__(self):
        return u"%s" % (self.name)

NestedHyperlinkedRelatedField should default use_pk_only_optimization to False

NestedHyperlinkedRelatedField, which inherits from DRF's HyperlinkedRelatedField, defaults to using a PKOnlyObject optimized object rather than the full model instance if the lookup_field kwarg is 'pk'. However, this won't work for the nested relationship lookup process because the object literally only has a pk field and any attempt to examine other fields results in an exception.

By overriding use_pk_only_optimization() in the field to return False, it guarantees the full objects will be used and then nested lookups will work correctly. I'll submit a PR too.

Expanded Nested Router Generation Principle

In the original proposal, we see patterns by alanjds:

"""
main_resources/ + {?fields=} + {?filter: __eq= | __ne= | ...} + {?additonal args like : signature= | request_target_for_query_str}

main_resources/{master_pk}
main_resources/{master_pk}/slave_resources/
main_resources/{master_pk}/slave_resources/{slave_pk}
"""

Actually, I do personally believe that is not enough:

(1) one kind resource might intercept another kind of resource
(2) {master_pk} can be repalced by '/'(group select) '{master_selector}'

Hence we have relational pattern for automatic resources generation:

master_resource/ {priority 1}
master_resource/{master_pk}/ {priority 2}
master_resource/>related_resources/ {priority 3}, plural keyword 'related_resources' is reserved
master_resource/>related_resources/{field_name}/ {priority 4}
master_resource/{master_pk}/related_resources/{relate_res_pk or slector expression} {priority 5}

Hereby we expand original url mapping pattern.

Remove trailing slash in nested resources

Is there any way to disable trailing slash for nested resources?

This is my patterns:

1. ^api/ ^$ [name='api-root']
2. ^api/ ^\.(?P<format>[a-z0-9]+)/?$ [name='api-root']
3. ^api/ ^members$ [name='member-list']
4. ^api/ ^members\.(?P<format>[a-z0-9]+)/?$ [name='member-list']
5. ^api/ ^members/(?P<pk>[^/]+)$ [name='member-detail']
6. ^api/ ^members/(?P<pk>[^/]+)\.(?P<format>[a-z0-9]+)/?$ [name='member-detail']
7. ^api/ ^courses$ [name='courses-list']
8. ^api/ ^courses\.(?P<format>[a-z0-9]+)/?$ [name='courses-list']
9. ^api/ ^courses/(?P<pk>[^/]+)$ [name='courses-detail']
10. ^api/ ^courses/(?P<pk>[^/]+)\.(?P<format>[a-z0-9]+)/?$ [name='courses-detail']
11. ^api/ ^courses/(?P<course_pk>[^/]+)/memberships/$ [name='memberships-list']
12. ^api/ ^courses/(?P<course_pk>[^/]+)/memberships/(?P<pk>[^/]+)/$ [name='memberships-detail']

...

Take a look for example at 7 and then at nested 11

And urls.py:

...

router = routers.DefaultRouter(trailing_slash=False)
router.register(r'members', MemberViewSet)
router.register(r'courses', CourseViewSet, base_name='courses')

courses_router = routers.NestedSimpleRouter(router, r'courses', lookup='course')
courses_router.register(r'memberships', MembershipViewSet, base_name='memberships')

...

urlpatterns = [
    url(r'^api/', include(router.urls, namespace='api')),
    url(r'^api/', include(courses_router.urls, namespace='api')),
]

drf permissions

Permissions are not chained, eg when trying to POST for create at an endpoint like 'item/1/subitem', nothing checks if the item 1 belongs to the user.

Accessing URL parameters from Permissions

My application has Orders; each Order has zero or more TrafficInstructions. Users should only be able to create a TrafficInstruction on a given Order if they have permission to edit that Order. I've set up a nested router using the code

order_router = routers.NestedSimpleRouter(router, r'api/orders', lookup='order')
order_router.register(r'traffic_instructions', views_rest.TrafficInstructionsViewSet)

In methods of TrafficInstructionsViewSet I can access the order via the order_pk parameter, but how do I get hold of it in the has_permission method of my associated Permissions class? It doesn't appear to be anywhere on either the request or view parameters. In particular, it's not in request.DATA.

How to use HyperlinkedIndentityField in a 2 level nested routing

If I take your exemple:

/clients/{client_pk}/maildrops/{maildrop_pk}/recipients/

what would be the usage of HyperlinkedIndentityField into let's say "MaildropSerializer" so it would display me this address into a field "recipients" ?

I didn't have any issue implementing it on the root level (in this examplem it would be clients), but I can't figure how to do in nested level

__new__() takes exactly 5 arguments (4 given)

So i am trying your app and i am getting this error __new__() takes exactly 5 arguments (4 given)

from rest_framework_nested import routers

router = routers.SimpleRouter()
router.register(r'women', views.WomenNativePassportViewSet)

women_router = routers.NestedSimpleRouter(router, r'women', lookup='avatar')
women_router.register(r'multimedia', views.MultimediaViewSet)


urlpatterns = patterns('',
    url(r'^$', RedirectView.as_view(url='/api/v1/'), name='home'),
    url(r'^api/v1/', include(routers.urls)),
    url(r'^api/v1/', include(women_router.urls)),
)

My models:

class Woman(models.Model):
    user = models.OneToOneField(settings.AUTH_USER_MODEL)
    avatar = models.ForeignKey(
        'core.Multimedia', blank=True, null=True,
        related_name='user_profiles_avatares'
    )

class Multimedia(models.Model):
    file = models.FileField(upload_to=upload_to, null=True, blank=True)
    thumbnail = models.FileField(upload_to=upload_to, null=True, blank=True)

api root

nested resources not showed in api root

include(nested-router) + HyperlinkedIdentityField

I'm getting improper hyperlinks with the following configuration

# myapp/urls.py

urlpatterns = [
    url(r'^$', RedirectView.as_view(url=reverse_lazy('v1:swagger:django.swagger.base.view'), permanent=False)),
    url(r'^user/', include('myapp.account.urls_v1_0', namespace='user')),
...
    url(r'^sponsor/', include('myapp.sponsor.urls_v1_0', namespace='sponsor')),
...
]
# myapp/sponsor/urls.py
router = routers.DefaultRouter()
router.register(r'sponsor', SponsorViewSet, base_name='sponsor')

member_router = routers.NestedSimpleRouter(router, r'sponsor', lookup='sponsor')
member_router.register(members', MemberViewSet, base_name='members')
...
# serializers.py
...
members = HyperlinkedIdentityField(view_name='v1.0:sponsor:members-list', lookup_url_kwarg='sponsor_pk')
...

In this configuration, the returned hyperlink is:

u'http://localhost/v1.0/sponsor/sponsor/724/followers/'

Changing to:

# myapp/sponsor/urls.py
router = routers.DefaultRouter()
router.register(r'', SponsorViewSet, base_name='sponsor')

member_router = routers.NestedSimpleRouter(router, r'', lookup='sponsor')
member_router.register(members', MemberViewSet, base_name='members')
...

yields:

u'http://localhost/v1.0/sponsor//724/followers/'

That "works," but... So, changing to:

# myapp/sponsor/urls.py
router = routers.DefaultRouter()
router.register(None, SponsorViewSet, base_name='sponsor')

member_router = routers.NestedSimpleRouter(router, None, lookup='sponsor')
member_router.register(members', MemberViewSet, base_name='members')
...

yields:

u'http://localhost/v1.0/sponsor/None/724/followers/'

BaseRouter is simply shoving that prefix value onto a list. My guess is that something in NestedSimpleRouter.init() needs to check for '' or None but I'm not sure where. Or is this simply user error?

Hyperlink field to nested ViewSet

I have a nested comments setup like:
/api/item/{item_pk}/comments/{comment_pk}

Basically, I want to be able to use a HyperLinkedIdentityField (or whatever) to link to the comments provided by the nested router.

Is this currently doable in drf-nested-routers or should I go write a custom router?

404

Hello,

Django 1.9.5
djangorestframework==3.4.4
drf-nested-routers==0.11.1

The code:

router.register('products', ProductViewSet, base_name='products')
product_router = routers.NestedSimpleRouter(router, 'products', lookup='product')
product_router.register('scarfs', ScarfViewSet, base_name='scarfs')

# I removed urlpatterns = patterns('', 
urlpatterns = [
    url(r'^', include(router.urls)),
    url(r'^', include(product_router.urls)),
****

http://localhost:8000/api/v1/products/33/scarfs

return 404, but it should work.
I suppose it's linked to this #72.

Ideas?

Thanks,

D

ObjectDoesNotExist exception

Hi,

I have a nested router set up, and the retrieve-method raises a ObjectDoesNotExist exception instead of NotFound, so DRF doesn't pick it up:

My view:

from rest_framework import viewsets
from rest_framework.response import Response
from arch.utils.jsend import JSendMixin

from ..models import Actor
from .serializers import ActorSerializer

class ActorViewSet(JSendMixin, viewsets.ModelViewSet):
    queryset = Actor.objects.all()
    serializer_class = ActorSerializer


class ActorByOrgViewSet(ActorViewSet):
    def list(self, request, org_pk=None):
        queryset = self.queryset.filter(org=org_pk)
        serializer = ActorSerializer(queryset, many=True)
        return Response(serializer.data)

    def retrieve(self, request, pk=None, org_pk=None):
        try:
            queryset = self.queryset.get(pk=pk, org=org_pk)
        except ObjectDoesNotExist:
            raise NotFound()

        serializer = ActorSerializer(queryset)
        return Response(serializer.data)

(-Note the except ObjectDoesNotExist workaround, I would love to get rid of it!)

and urls.py:

from django.conf.urls import patterns, include, url
from rest_framework.urlpatterns import format_suffix_patterns
from rest_framework_nested import routers

from org.rest.urls import orgs_router
from .views import ActorViewSet, ActorByOrgViewSet

actors_router = routers.DefaultRouter()
actors_router.register(r'actors', ActorViewSet)

actors_by_org_router = routers.NestedSimpleRouter(orgs_router, r'orgs', lookup='org')
actors_by_org_router.register(r'actors', ActorByOrgViewSet)

urlpatterns = [
    url(r'^', include(actors_router.urls)),
    url(r'^', include(actors_by_org_router.urls)),
]

Where the orgs_router imported looks like this:

from django.conf.urls import patterns, include, url
from rest_framework.urlpatterns import format_suffix_patterns
from rest_framework_nested import routers

from .views import OrgViewSet

orgs_router = routers.DefaultRouter()
orgs_router.register(r'orgs', OrgViewSet)

urlpatterns = [
    url(r'^', include(orgs_router.urls)),
]

I'm pretty new to all this, so might be s.th I've missed.

create new nested object good practices

Hi,

I would like create a nested object. So I have a modelViewSet and I ModelSerializer. I overide the create method in ModelSerizer. But does anyone have a good example of overriding .create() in ModelViewSet for the nested model?

Actually, I retrieve the parent resource like this : self.context['request'].parser_context['kwargs']['publication_identifier'] but It's ugly !!!

Thank you,

Implement lookup_value_regex for nested resources

With DRF 3, ViewSet’s lookup_value_regex property isn’t properly passed to the SimpleRouter overridden class.

I think you can remove the SimpleRouter from /rest_framework_nested/routers.py and use the default DRF SimpleRouter.get_lookup_regex

tested on :

  • Django 1.7
  • DRF 3
  • Python 3

parent_prefix and lookup

Is there any documentation on what parent_prefix and lookup and mean/ what they should be set to on a NestedSimpleRouter?

Update release on pypi?

Hi,

I've included the github master of drf-nested-routers in our project and it's really working well, seems to do exactly what we need. We'd really prefer to include a pypi release in our deps rather than building the library for each release, do you have a timescale for the next pypi release?

thanks!

0.9.1 release

It's been a while since the 0.9.0 release.

Please release current master state as 0.9.1 or whatever release as it looks pretty stable.

Suggested view.py documentation change & format_suffix_patterns

I'm relatively new to DRF. Thanks for this extension as it's very helpful. A suggestion for the documentation:

# views.py
class NameserverViewSet(viewsets.ViewSet):
    def list(self, request, domain_pk=None, *args, **kwargs):
        self.queryset = Nameserver.objects.filter(domain=domain_pk)
        (...)
        return super(NameserverViewSet, self).list(request, context={'request': request}, *args, **kwargs)

    def retrieve(self, request, pk=None, domain_pk=None, *args, **kwargs):
        nameservers = Nameserver.objects.filter(pk=pk, domain=domain_pk)
        (...)
        return super(NameserverViewSet, self).retrieve(request, context={'request': request}, *args, **kwargs)

for those of us that would rather take advantage of things like permissions and pagination out of the box. I ended up implementing those manually then kicked myself when I realized the above was much better.

Which leads me to the question: is anyone using format_suffix_patterns with drf-nested-routers? If so, what's the magic?

HyperlinkedIdentityField on 3 level routes

Hi,

First of all, I would like to say, nice work :)

I have a problem, I can't make HyperlinkedIdentityField to produce the urls.

Here is my code:

urls.py

router = routers.SimpleRouter()
router.register(r'users', UserViewSet, base_name='users')

chats_router = routers.NestedSimpleRouter(router, r'users', lookup='user')
chats_router.register(r'chats', ProfileChatViewSet, base_name='chats')

messages_router = routers.NestedSimpleRouter(chats_router, r'chats', lookup='chat')
messages_router.register(r'messages', ProfileMessageViewSet, base_name='messages')

view.py

## User
class UserViewSet(viewsets.ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer

## Chat
class ChatViewSet(viewsets.ModelViewSet):
    queryset = Chat.objects.all()
    serializer_class = ChatSerializer

    def list(self, request, user_pk=None):
        queryset = Chat.objects.filter(user=user_pk)
        serializer = self.serializer_class(queryset, many=True, context={'request': request})
        return Response(serializer.data)

    def retrieve(self, request, pk=None, user_pk=None):
        result = get_object_or_404(self.queryset, pk=pk)
        serializer = self.serializer_class(result, context={'request': request})
        return Response(serializer.data)

## Message
class MessageViewSet(viewsets.ModelViewSet):
    queryset = Message.objects.all()
    serializer_class = MessageSerializer

    def list(self, request, user_pk=None, chat_pk=None):
        queryset = Message.objects.filter(chat=chat_pk)
        serializer = self.serializer_class(queryset, many=True)
        return Response(serializer.data)

    def retrieve(self, request, pk=None, user_pk=None, chat_pk=None):
        result = get_object_or_404(self.queryset, pk=pk)
        serializer = self.serializer_class(result)
        return Response(serializer.data)

serializers.py

## User
class UserSerializer(serializers.ModelSerializer):
    ....
    chats = serializers.HyperlinkedIdentityField(view_name='chats-list', lookup_url_kwarg='user_pk')
    ...

## Chat
class ChatSerializer(serializers.ModelSerializer):
    ...
    messages = serializers.HyperlinkedIdentityField(view_name='messages-list', lookup_url_kwarg='chat_pk')
    ...

## Message
class ProfileMessageSerializer(serializers.ModelSerializer):
    ...

In the user viewset I can get the url for the chats, but in the chat viewset i can't get the url for the messages:

Could not resolve URL for hyperlinked relationship using view name "messages-list". You may have failed to include the related model in your API, or incorrectly configured the `lookup_field` attribute on this field.

Do you have any idea what am doing wrong?

Error after DRF upgrade

After upgrading to DRF 2.4 i get an

__new__ takes exactly 5 Arguments, 4 given in rest_framework_nested/routers.py, line 72.

Do you already have an idea, because I didn't find out what may be the cause.

Feature request: Multiple levels of nested routers (nest routers more than once)

I have models something like:

class A(models.model):
    name=models.CharField(max_length=255)
class B(models.model):
    name=models.CharField(max_length=255)
    parent=models.ForeignKey(A)
class C(models.model):
    name=models.CharField(max_length=255)
    parent=models.ForeignKey(B)

and I want to be able to access C through /a/ID/b/ID/c/ID. I tried to set up my routers like:

router = routers.SimpleRouter()
router.register(r'a', views.AViewSet)
a_router=routers.NestedSimpleRouter(router, r'a', lookup='a')
a_router.register(r'b', views.BViewSet)
b_router=routers.NestedSimpleRouter(a_router, r'b', lookup='b')
b_router.register(r'c', views.CViewSet)

But this produces a 404 at the expected location, giving the URL patterns like:

^a/$
^a/(?P<pk>[^/]+)/$
^a/(?P<a_pk>[^/]+)/b/$
^a/(?P<a_pk>[^/]+)/b/(?P<pk>[^/]+)/$
^b/(?P<b_pk>[^/]+)/c/$
^b/(?P<b_pk>[^/]+)/c/(?P<pk>[^/]+)/$

As you can see the router for the double nested viewset (c) is only prefixed by its immediate prefix, instead of both.

I assume I am doing something wrong, as the code contains stuff about nest_count and so probably supports multiple layers of nesting. However how to achieve what I want is not documented.

Creation of new nested objects

How do you recommend handling the creation of new nested objects at the nested list endpoint and getting them to be bound to the parent?

At the moment I get an integrity error as it is trying to leave the foreign key null, and am having varying success slotting the parent in at different points in the serializer/viewset create/save flow. Closest I've gotten does it after validation has occurred by modifying the data by overriding the serializer create, which means uniqueness constraints I have in place fail to get caught, and thus a db exception is thrown if I pass something invalid rather than a friendly error.

Any thoughts?

Import error for urlparse

    from Api.views import ChannelViewSet, ChannelFieldViewSet, FieldValueViewSet, UserViewSet
  File "/home/patrick/PycharmProjects/Iot/Api/views/__init__.py", line 1, in <module>
    from Api.views.channel import *
  File "/home/patrick/PycharmProjects/Iot/Api/views/channel.py", line 11, in <module>
    from Api.serializers import ChannelSerializer
  File "/home/patrick/PycharmProjects/Iot/Api/serializers.py", line 3, in <module>
    from rest_framework_nested.relations import HyperlinkedIdentityField
  File "/usr/local/lib/python3.4/dist-packages/rest_framework_nested/relations.py", line 15, in <module>
    from rest_framework.compat import urlparse
ImportError: cannot import name 'urlparse'

As a result of:

from rest_framework import serializers
from rest_framework_nested.relations import HyperlinkedIdentityField

from IoT.models import Channel, FieldValue, ChannelField


class ChannelSerializer(serializers.HyperlinkedModelSerializer):
    name = serializers.CharField(required=True, allow_blank=False, max_length=90)
    owner = serializers.ReadOnlyField(source='owner.username')

    class Meta:
        model = Channel
        fields = ('pk', 'name', 'description', 'public', 'owner')

class ChannelFieldSerializer(serializers.HyperlinkedModelSerializer):
    name = serializers.CharField(required=True, allow_blank=False, max_length=90)

    channel = HyperlinkedIdentityField(
        view_name='channel-fields-list',
        lookup_url_kwarg='channel_pk'
    )

    class Meta:
        model = ChannelField
        fields = ('pk', 'name', 'channel')

Django 1.10 Support - NM

django.conf.urls.patterns has been removed on Django 1.10.

EDIT: Sorry, I was in the wrong github repo. You can ignore this.

Error installing from PyPi

Install packages failed: Error occurred when installing package drf-nested-routers. 

The following command was executed:

packaging_tool.py install --build-dir pycharm-packaging1530179480534658711.tmp drf-nested-routers

The error output of the command:


Downloading/unpacking drf-nested-routers
  Downloading drf-nested-routers-0.1.0.tar.gz
  Running setup.py egg_info for package drf-nested-routers
    Traceback (most recent call last):
      File "<string>", line 16, in <module>
      File "pycharm-packaging1530179480534658711.tmp\drf-nested-routers\setup.py", line 11, in <module>
        long_description=open('README.md').read(),
    IOError: [Errno 2] No such file or directory: 'README.md'
    Complete output from command python setup.py egg_info:
    Traceback (most recent call last):

  File "<string>", line 16, in <module>

  File "pycharm-packaging1530179480534658711.tmp\drf-nested-routers\setup.py", line 11, in <module>

    long_description=open('README.md').read(),

IOError: [Errno 2] No such file or directory: 'README.md'

----------------------------------------
Cleaning up...
Command python setup.py egg_info failed with error code 1 in pycharm-packaging1530179480534658711.tmp\drf-nested-routers
Storing complete log in pip.log

Validate lookup (and provide helpful error)

It would be helpful if the NestedMixin did a quality check on the lookup keyword (i.e. that all characters are valid for a regex group name) and provided a descriptive error message if they are not. For example, including a hyphen in this value results in an error like:

django.core.exceptions.ImproperlyConfigured: "^parents/(?P<ui-parent_pk>[^/.]+)/childs/$" is not a valid regular expression: bad character in group name u'ui-parent_pk'

I've customized routers before so it was obvious to me what went wrong, but I doubt its obvious to first-time users of the package. The issue came up because I normally use a pattern like:

api.register(parents', ParentViewSet, base_name='parent')
nested_router = NestedSingletonRouter(api, parents', lookup='parent')
nested_router.register(child', ParentChildViewSet, base_name='parent-child')

I needed to expose less permissive serializers for my UI so I ran into the error making "ui" versions.

api.register(parents', ParentViewSet, base_name='ui-parent')
nested_router = NestedSingletonRouter(api, parents', lookup='ui-parent')  # cannot use a hyphen in lookup
nested_router.register(child', ParentChildViewSet, base_name='ui-parent-child')

This may not be a huge issue, but it seems like an straightforward mistake to make and an easy one to catch/correct. If this sounds reasonable, I should be able to get around to a PR.

Enable Gitter for this repo

Hello @alanjds ,

It would be nice to enable Gitter for this repo so we could discuss ideas and such.

I have few things that I would personally like to ask but don't feel like an issue would be adequate.

Finding URL with reverse not working when using url namespaces

I have the following setup which fails:

url(r'^v3/', include(router.urls, namespace="api-v3")),
reverse("api-v3:tag-list", kwargs={"project_pk": obj}))

Error message:

NoReverseMatch: Reverse for 'tag-list' with arguments '()' and keyword arguments '{'project_pk': 'viel'}' not found. 0 pattern(s) tried: []

When I change it to the following it works:

url(r'^v3/', include(router.urls)),
reverse("tag-list", kwargs={"project_pk": obj})

Are namespaces not supported by drf-nested-routers? Could this be fixed?

New release

Will there be a new release in the near future? I would like to package drf-nested-routers for Debian.

Add convenience mixin to filter queryset based on its parents

I noticed that the documentation states that you can manually filter the queryset based on its parents, when using nested resources. If you're using a nested Serializer, you most likely specify its parent_lookup_kwargs. This dict contains all information to filter the queryset in a generic way.

I was looking for this but didn't find any helpers for this in the library. You can simply add the mixin below, to your ViewSet and it works:

class NestedViewSetMixin(object):
    def get_queryset(self):
        """
        Filter the ``QuerySet`` based on its parents.
        """
        queryset = super().get_queryset()
        if hasattr(self.serializer_class, 'parent_lookup_kwargs'):
            orm_filters = {}
            for query_param, field_name in self.serializer_class.parent_lookup_kwargs.items():
                orm_filters[field_name] = self.kwargs[query_param]
            return queryset.filter(**orm_filters)
        return queryset

Document how to run unit tests

Can you please document how to run the unit tests? I managed to get the router tests running, but it was pretty awkward. Starting with a fresh virtualenv, what needs to be installed, etc.?

Breaks with DRF 2.4 release

example stacktrace:

File "/home/alon/server/django-project/venv/lib/python2.7/site-packages/rest_framework_nested/routers.py", line 72, in __init__
    nested_routes.append(rest_framework.routers.Route(**route_contents))
TypeError: __new__() takes exactly 5 arguments (4 given)

Incompatible with DRF > 3.0.2

This package is incompatible with Django Rest Framework since version 3.0.3. An import in relations.py that drf-nested-routers uses in DRF has been removed and as such it now fails with an ImportError:

from rest_framework.compat import urlparse

You can see the change in DRF here:

encode/django-rest-framework@b33a6cb#diff-ce493f71b3679e91b72126c168670399

I propose this to be fixed by instead using the Django module initially intended to be used:

from django.utils.six.moves.urllib import parse as urlparse

I will submit a PR for this, please accept if you agree with my hypothesis and conclusion.

Nested routers do not include (?P<format>) in regex

Here's an example excerpt of some normal ViewSet URL from Django 1.9.1:

^ ^users/$ [name='user-list']
^ ^users\.(?P<format>[a-z0-9]+)/?$ [name='user-list']

This allows the URL /users.csv/ if a CSV formatter has been installed.

An equivalent url generated by drf-nested-routers:

^ ^users/(?P<user_pk>[^/.]+)/accounts/$ [name='user-account-list']

There is no entry containing the (?P) regex that is required to support view formatters.

Is this a known missing feature, a bug, or a misconfiguration on my part? It's not clear from my reading of the docs how to get the format URLs.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.