简体   繁体   中英

How do I get a hyperlinked foreign key in Django REST Framework serializer?

I am trying to serialize a model in my DRF project. The model contains 2 foreign key fields and some simple data type fields. One of the foreign key fields is to my CustomUser model and this field appears as a hyperlink in my API output which is expected. The other foreign key field is to a different model (shown below). I have been cycling through the same few errors over the past several hours as I've tried various solutions recommended in other StackOverflow threads.

Could someone please show me how to get this second foreign key to appear as a link in my API output?

When my code files appear as they do below, I get this error when trying to access the localhost:8000/trade/create/ and localhost:8000/trader-accounts/ URLs:

django.core.exceptions.ImproperlyConfigured: Could not resolve URL for hyperlinked relationship using view name "traderaccount-detail". You may have failed to include the related model in your API, or incorrectly configured the `lookup_field` attribute on this field.

Of course, I Googled this error messages and portions thereof several times, and went down several DRF rabbit holes trying various things.

trades/models.py

class Trade(models.Model):
    trade_owner = models.ForeignKey(CustomUser, related_name='owners', on_delete=models.RESTRICT)
    trade_account = models.ForeignKey(TraderAccount, related_name='trades', on_delete=models.RESTRICT)

trader_account/models.py

class TraderAccount(models.Model):

    account_owner = models.ForeignKey(
        CustomUser, on_delete=models.CASCADE,
    )
    account_name = models.CharField(
        max_length=200, blank=True, verbose_name='Account Name'
    )

api/serializers.py

class TraderAccountSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = TraderAccount
        fields = ['account_owner', 'account_name',]


class TradeSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Trade
        fields = ['trade_owner', 'trade_account', 'magic_number',]

api/views.py

class TraderAccountView(generics.ListCreateAPIView):
    permission_classes = (IsAuthenticated,)
    queryset = TraderAccount.objects.all()
    serializer_class = TraderAccountSerializer


class TradeCreateView(generics.ListCreateAPIView):
    permission_classes = (IsAuthenticated,)
    queryset = Trade.objects.all()
    serializer_class = TradeSerializer

api/urls.py


router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet)
router.register(r'groups', views.GroupViewSet)

urlpatterns = [
    path('', include(router.urls)),
    path('trader-accounts/', views.TraderAccountView.as_view(), name='trader-accounts'),
    path('trade/create/', views.TradeCreateView.as_view(), name='trade-create'),

When I remove 'trader_account' from api.serializers.TradeSerializer.Meta.fields I can successfully access both the localhost:8000/trade/create/ endpoint and the localhost:8000/trader-accounts/ endpoint.

api/serializers.py

# Omit TraderAccountSerializer class for brevity in this example


class TradeSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Trade
        fields = ['trade_owner', 'magic_number',]  # Removed 'trader_account'

I've been looking at this so long now I can't find the mistake. Really appreciate the help!

Based on this api/views.py you have a TraderAccountView(generics.ListCreateAPIView) . You should rename that to TraderAccountList(generics.ListCreateAPIView) and then add an extra view for the details; TraderAccountDetail(generics.RetrieveUpdateDestroyAPIView)

api/views.py is now:

class TraderAccountList(generics.ListCreateAPIView):
    permission_classes = (IsAuthenticated,)
    queryset = TraderAccount.objects.all()
    serializer_class = TraderAccountSerializer

class TraderAccountDetail(generics.RetrieveUpdateDestroyAPIView):
    permission_classes = (IsAuthenticated,)
    queryset = TraderAccount.objects.all()
    serializer_class = TraderAccountSerializer

Your urlpatterns should now be

router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet)
router.register(r'groups', views.GroupViewSet)

urlpatterns = [
    path('', include(router.urls)),
    path('trader-accounts/', views.TraderAccountList.as_view(), name='trader-accounts'),
    path('trader-accounts/<int:pk>/', views.TraderAccountDetail.as_view()),
]

And remove TradeCreate. As you are now building a RESTful API. to create a trade you do a POST request to the list view to create a new trade.

On the list view you can do GET to get a list of all trades, or a POST to create a new trade. On the detail view, you can do GET to get a specific trade, or a PUT or PATCH to update, or DELETE to delete the trade.

Have you tried making a route for the account in the same manner as user ?

The error message you included says that there is no route with the expected name.

According to the documentation at https://www.django-rest-framework.org/api-guide/serializers/#how-hyperlinked-views-are-determined , for the automapping to work you need a view with the name account-detail which takes 1 integer route parameter.

Hyperlinked serializers are fiddly, as they say, so pay close attention to docs. If you change the view name, you can override it in the serializer.

account = serializers.HyperlinkedRelatedField(
    view_name='account-detail', # or whatever name you give it
    ...
)

Many thanks to Gabbeh for pointing me in the right direction.

I renamed TraderAccountView(generics.ListCreateAPIView) to TraderAccountList(viewsets.ModelViewSet) and added this view to api/views.py :

class TraderAccountDetail(generics.RetrieveUpdateDestroyAPIView):
    permission_classes = (IsAuthenticated,)
    queryset = TraderAccount.objects.all()
    serializer_class = TraderAccountSerializer

The api/urls.py file is now:

router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet)
router.register(r'groups', views.GroupViewSet)
router.register(r'trader-accounts', views.TraderAccountList)

And everything works!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM