3
votes

I've extensively researched this fairly common issue, but none of the fixes worked for me. I'm building a Django project in REST framework and want to use hyperlinked relations. The User can have many Cars and Routes, which are independent. A Route is a collection of Positions.

These are my serializers:

class CarSerializer(serializers.HyperlinkedModelSerializer):
    user = serializers.Field(source='user.username')
    class Meta:
        model = Car
        fields = ('url', 'make', 'year', 'car_model', 'user')

class PositionSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Position
        fields = ('url', 'drive_route', 'timestamp', 'latitude', 'longitude', 'altitude','speed','heading', 'accuracy', 'altitude_accuracy')

class DrivingRouteSerializer(serializers.HyperlinkedModelSerializer):
    position = serializers.HyperlinkedRelatedField(view_name='position', many=True)
    user = serializers.Field(source='user.username')
    class Meta:
        model = DrivingRoute
        fields = ('url', 'id', 'route', 'position', 'user')

class UserSerializer(serializers.HyperlinkedModelSerializer):
    routes = serializers.HyperlinkedRelatedField(view_name='routes-detail', many=True)
    car = serializers.HyperlinkedRelatedField(view_name='car-detail', many=True)
    class Meta:
        model = User
        fields = ('url', 'username', 'routes', 'car')

And here are the views:

class CarViewSet(viewsets.ModelViewSet):
    queryset = Car.objects.all()
    serializer_class = CarSerializer
    permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
    def pre_save(self, obj):
        obj.user = self.request.user

class DrivingRouteViewSet(viewsets.ModelViewSet):
    queryset = DrivingRoute.objects.all()
    serializer_class = DrivingRouteSerializer
    permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
    def pre_save(self, obj):
        obj.user = self.request.user

class PositionViewSet(viewsets.ModelViewSet):
    queryset = Position.objects.all()
    serializer_class = PositionSerializer

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

And, for what it's worth, the URLs. I am using the Default Router, just as in the Django REST Framwork tutorial.

router = DefaultRouter()
router.register(r'car', views.CarViewSet)
router.register(r'routes', views.DrivingRouteViewSet)
router.register(r'position', views.PositionViewSet)
router.register(r'users', views.UserViewSet)

Overall, this is almost exactly the same as in the tutorial. Loading the 'routes', 'car', and 'position' URLS works fine, but the 'users' URL throws the error "Could not resolve URL for hyperlinked relationship using view name 'routes-detail'."

2
In my case the problem was I was namespacing my urls so user-detail was invalid while foo:user-detail was valid, reading the source code I ended up adding extra_kwargs = {'url': {'view_name': 'foo:user-detail'}} to the Serializer inside the Meta class. However there must be a better solution for that otherwise I will have to that in every single Serializer. Not too DRY. =/Danilo Cabello

2 Answers

8
votes

The view_name should typically be [route]-detail for routers, where [route] is the name of the model you registered the ViewSet under.

In your case, the view_name should be position-detail, not just position. You are also using routes-detail instead of drivingroutes-detail, which is using the long name because your model is DrivingRoute and not Route. You can override this by setting a base_name (third parameter) when using register on the router.

router = DefaultRouter()
router.register(r'car', views.CarViewSet)
router.register(r'routes', views.DrivingRouteViewSet, "routes")
router.register(r'position', views.PositionViewSet)
router.register(r'users', views.UserViewSet)
1
votes

You have the following views:

  • car-list
  • car-detail
  • drivingroute-list
  • drivingroute-detail
  • position-list
  • position-detail
  • user-list
  • user-detail

If we take for example the following route:

router.register(r'car', views.CarViewSet)

It means that it is accessible under http://yourapi.com/car/ for car-list and under http://yourapi.com/car/1/ for car-detail (where 1 is the id).

The view names get built out of the class name minus suffix ViewSet plus list or detail.

Change your code according to these rules and please try again.

Cheers!