1

I'm building an SMS API that talks to a Django database, which is a list of contact information for several hundred people. The fields are as follows: first name, last name, phone number and job title.

I'm getting responses when I use this url on my local server:

http://localhost:8000/sources/id

What I'd like to do is make requests to the same database using this url:

http://localhost:8000/sources/first_name-last_name

I've investigated multiple questions about field lookups, but there hasn't been anything helpful. Here's what my serializers.py look like:

from rest_framework import serializers, viewsets
from text_source.models import Source

class SourceSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Source
        fields = ('id','first_name','last_name','title','organization','city','cell_phone','office_phone')
        lookup_field = 'first_name'

class SourceViewSet(viewsets.ModelViewSet):
    queryset = Source.objects.all()
    serializer_class = SourceSerializer
    lookup_field = ('first_name')

I'm not sure if using /first_name-last_name as an end point for the url is best practice, but, in theory for what I'm doing, it will work.

Ideally, I'd like for someone to type FIRSTNAME LASTNAME in the text and have the API return the correct information by connecting the full name to the ID in the database. Any tips to accomplishing that would be greatly appreciated.

urls.py

from django.conf.urls import include, url
from django.contrib import admin
from django.views.generic.base import TemplateView
from django.contrib.auth.models import User

from rest_framework import routers, serializers, viewsets
from app import views
from app.serializers import SourceSerializer, SourceViewSet

router = routers.DefaultRouter()
router.register(r'sources', SourceViewSet)

urlpatterns = [

    url(r'^page/$', TemplateView.as_view(template_name='base.html')),

    url(r'^page/', include(router.urls)),

    url(r'^admin/', include(admin.site.urls)),

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

    url(r'^page/incoming/message$', views.incoming_message)
]
2
  • In the view, why are you enclosing lookup_field = ('first_name') in round brackets? Commented Sep 22, 2015 at 20:53
  • Read here about a nice mixin you can use to lookup multiple fields (lower end of the page, look for MultipleFieldLookupMixin). Then make sure you update urls.py to match and name the keyword arguments correctly (name & surname) Commented Sep 22, 2015 at 21:23

3 Answers 3

1

I would try doing something like the example below. You can change the kwargs to whatever you end up using.

serializers:

class SourceUrlField(serializers.HyperlinkedIdentityField):
    def get_url(self, obj, view_name, request, format):
        kwargs = {
            'first_name': obj.first_name,
            'last_name': obj.last_name
        }
        return reverse(view_name, kwargs=kwargs,
                       request=request, format=format)

class SourceSerializer(serializers.HyperlinkedModelSerializer):
    url = SourceUrlField("view_name")

    class Meta:
        model = Source
        fields = (
            'id',
            'url',
            'first_name',
            'last_name',
            'title',
            'organization',
            'city',
            'cell_phone',
            'office_phone',
        )

I would avoid using first_name and last_name if possible, based on Joey's reasoning. However, the example I just showed you will work for whichever kwargs you choose to use.

Sign up to request clarification or add additional context in comments.

3 Comments

Excellent answer, I am learning something new. If I understand correctly, that allows the Source object to be serialized with the url field populated with the required format. My question is: will the lookup work when such url is used?
@Pynchia Yes, it should.
@AldoTheApache you need to change "view_name" in url = SourceUrlField("view_name") to the name of the view you are actually using. That was just a placeholder.
0

I think you may run into issues with /sources/first_name-last_name as your endpoint, as you can never guarantee uniqueness of names, and names could also contain dashes. I would recommend keeping /sources/id (and presumably /sources/) as your endpoints, and simply allow for filtering.

You can read up in the DRF documentation for more details on filtering, but a basic example (after installing django-filter) would look like:

from rest_framework import filters, serializers, viewsets
from text_source.models import Source

class SourceSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Source
        fields = ('id','first_name','last_name','title','organization','city','cell_phone','office_phone')


class SourceViewSet(viewsets.ModelViewSet):
    queryset = Source.objects.all()
    serializer_class = SourceSerializer
    filter_backends = (filters.DjangoFilterBackend,)
    filter_fields = ('first_name', 'last_name')

Once you had this, you could structure URLs along the lines of /sources/?first_name=Bob&last_name=Dobbs to perform your search and return the ID.

Also note, your example has a very important error in it. You specify the lookup_field on the ViewSet as ('first_name'). This exact syntax, since there is only one element in the tuple, will have a very much unwanted side effect, by treating the string 'first_name' as an iterable, creating a tuple looking like ('f', 'i', 'r', 's', 't', '_', 'n', 'a', 'm', 'e'). What you would want to use in order to prevent this is ('first_name', ). Note the trailing comma.

Comments

0

You could override lookup_field on runtime, depending on the attribute you got in GET params. You can't use kwargs for this, as long as you want to define only one view and url.

Sample url:-

/sources/?parameter=first_name&first_name='someone'

Change your viewset accordingly:-

class SourceViewSet(viewsets.ModelViewSet):
     queryset = Source.objects.all()
     serializer_class = SourceSerializer
     lookup_field = ('first_name')

     def initial(self, request, *args, **kwargs):
         parameter = request.data.get('parameter')
         self.lookup_field = parameter
         kwargs[parameter] = request.data.get(parameter)

         super(SourceViewSet, self).initial(request, *args, **kwargs)

2 Comments

You could create a full_name field in your serializer and use that parameter, maintaining the encapsulation in models.
You then need to override get_queryset too

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.