title: Django REST framework use AJAX url to point api detail url
body: <p>I'm working on database program using Django. The program appends the database from user's forms/imported files, allows listing of data per category, and inline editing per category.</p>
  
  <p>I did a lot of research (since i'm new in django), and so far I've been successful in generating forms, and and customized tables by combining rest framework and datatables.</p>
  
  <p>The problem occurs when I tried to implement inline editing to my tables. I using the plugin datatables editor. My views and serializer seem to work since i can <code>POST</code>, <code>PUT</code> and <code>DELETE</code> in the Api Root. But then I can't make the ajax url point to the methods api detail views, I need the <code>pk</code>in the url to be variable to match the selection:</p>
  
  <p>my serializer.py</p>
  
  <pre><code>class TankSerializer(serializers.ModelSerializer):
  modified_date = serializers.DateTimeField(format='%d/%m/%Y %H:%M', input_formats=None)
  updated_by = serializers.SlugRelatedField(slug_field='last_name', queryset=User.objects.all())
  pid = serializers.SlugRelatedField(slug_field='number', queryset=PID.objects.all())
  zone = serializers.SlugRelatedField(slug_field='number', queryset=ProcessZone.objects.all())
  medium = serializers.SlugRelatedField(slug_field='code', queryset=Medium.objects.all())
  revision = serializers.SlugRelatedField(slug_field='code', queryset=Revision.objects.all())
  supplier = serializers.SlugRelatedField(slug_field='name', queryset=Supplier.objects.all())
  po = serializers.SlugRelatedField(slug_field='number', queryset=PO.objects.all())
  material = serializers.SlugRelatedField(slug_field='name', queryset=Material.objects.all())
  
  DT_RowId = serializers.SerializerMethodField()
  
  def get_DT_RowId(self, tank):
      return 'row_%d' % tank.pk
  
  class Meta:
      model = Tank
      fields = (
          'DT_RowId', 'tag', 'description', 'pid', 'zone', 
          'medium', 'revision', 'supplier', 'po', 'material', 
          'volume', 'hight','modified_date','updated_by', 'created_by'
      )
  </code></pre>
  
  <p>my views.py</p>
  
  <pre><code>@login_required
  @ensure_csrf_cookie
  def edit_tanks(request, project_code):
      project = get_object_or_404(Project, code=project_code)
      return render(request, 'edit_tanks.html', {'project': project})
  
  class TankViewSet(viewsets.ModelViewSet):
      queryset = Tank.objects.all().order_by('tag')
      serializer_class = TankSerializer
      lookup_filed = 'pk'
  
      @action(methods=['get'], detail=True)   
      def get(self, request, pk, project_code, format=None):
          serializer = self.serializer_class(self.queryset, many=True)
          return Response(serializer.data)
  
      @action(methods=['post'], detail=True)
      def post(self, request, pk=None, format=None):
          serializer = self.serializer_class(data=request.data)
          if serializer.is_valid():
              serializer.save()
              return Response(serializer.data, status.HTTP_201_CREATED)
          return Response(serializer.errors, status.HTTP_400_BAD_REQUEST)
  
      @action(methods=['patch'], detail=True)
      def patch(self, request, pk, format=None):
          queryset = self.get_object(pk)
          serializer = self.serializer_class(queryset, data=request.data, partial=True)
          if serializer.is_valid():
              serializer.save()
              return Response(serializer.data)
          return Response(serializer.errors, status.HTTP_400_BAD_REQUEST)
  
      @action(methods=['put'], detail=True)
      def put(self, request, pk, format=None):
          queryset = self.get_object(pk)
          serializer = self.serializer_class(queryset, data=request.data)
          if serializer.is_valid():
              serializer.save()
              return Response(serializer.data)
          return Response(serializer.errors, status.HTTP_400_BAD_REQUEST)
  
      @action(methods=['delete'], detail=True)
      def delete(self, request, pk, format=None):
          queryset = self.get_object(pk)
          queryset.delete()
          return Response(status.HTTP_204_NO_CONTENT)
  </code></pre>
  
  <p>my urls.py</p>
  
  <pre><code>router = routers.DefaultRouter()
  router.register(r'edit_tanks', views.TankViewSet, 'edit_tanks')
  
  urlpatterns = [
      re_path(r'^projects/(?P&lt;project_code&gt;\d+)/engineering/equipment/edit_tanks/api/', include(router.urls)),
      re_path(r'^projects/(?P&lt;project_code&gt;\d+)/engineering/equipment/edit_tanks/$', views.edit_tanks, name='edit_tanks'),
      path('admin/', admin.site.urls),
  ]
  </code></pre>
  
  <p>my template edit_tanks.html</p>
  
  <pre><code>{% block content %}
    &lt;div class="row"&gt;
      &lt;div class="col-sm-12"&gt;
        &lt;table id="edit_tanks" class="stripe row-border order-column" cellspacing="0" style="table-layout: auto; width:100%;"&gt;
          &lt;thead&gt;
              &lt;tr&gt;
                  &lt;th&gt;&lt;/th&gt;
                  &lt;th&gt;Tag&lt;/th&gt;
                  &lt;th&gt;Description&lt;/th&gt;
                  &lt;th&gt;PID&lt;/th&gt;
                  &lt;th&gt;Zone&lt;/th&gt;
                  &lt;th&gt;Medium&lt;/th&gt;
                  &lt;th&gt;Revision&lt;/th&gt;
                  &lt;th&gt;Supplier&lt;/th&gt;
                  &lt;th&gt;PO&lt;/th&gt;
                  &lt;th&gt;Material&lt;/th&gt;
                  &lt;th&gt;Volume&lt;/th&gt;
                  &lt;th&gt;Height&lt;/th&gt;
                  &lt;th&gt;Last updated&lt;/th&gt;
                  &lt;th&gt;Updated by&lt;/th&gt;
              &lt;/tr&gt;
          &lt;/thead&gt;
  
        &lt;/table&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  
  {% endblock %}
  
  {% block extra_js %}
  
  &lt;script type="text/javascript"&gt;
  
  
  var editor;
  
  $(document).ready(function() {
      editor = new $.fn.dataTable.Editor( {
  
          ajax: {
              create: {
                  type: 'POST',
                  url: 'api/edit_tanks/?format=datatables',
              },
              edit: {
                  type: 'PUT',
                  url: 'api/edit_tanks/{{tank_pk}}/',
              },
              remove: {
                  type: 'DELETE',
                  url: 'api/edit_tanks/{{tank_pk}}/',
              }
          },
  
          table: "#edit_tanks",
          fields: [ {
                  label: "Tag:",
                  name: "tag"
              }, {
                  label: "Description:",
                  name: "description"
              }, {
                  label: "Ravision:",
                  name: "revision.code",
                  type: "select"
              }, {
                  label: "PO:",
                  name: "po.number",
                  type: "select"
              },
          ]
      } );    
  
      // Activate an inline edit on click of a table cell
      $('#edit_tanks').on( 'click', 'tbody td:not(:first-child)', function (e) {
          editor.inline( this );
      } );
  
      var table = $('#edit_tanks').DataTable({
          "processing": true,
          "serverSide": true,
          ajax: {
              url: "api/edit_tanks/?format=datatables",
              data: { 
              csrfmiddlewaretoken: "{{ csrf_token }}"
              }
          },
          dom: 'Bfrtip',
          language: {
              search: "_INPUT_",
              searchPlaceholder: "Search..."
          },
  
          "columns": [
              {
                  data: null,
                  defaultContent: '',
                  className: 'select-checkbox',
                  orderable: false
              },
              {"data": "tag"},
              {"data": "description"},
              {"data": "pid", "number": "pid.number"},
              {"data": "zone", "number": "zone.number"},
              {"data": "medium", "code": "medium.code"},
              {"data": "revision", "code": "revision.code"},
              {"data": "supplier", "name": "supplier.name"},
              {"data": "po", "number": "po.number"},
              {"data": "material", "name": "material.name"},
              {"data": "volume"},
              {"data": "hight"},
              {"data": "modified_date"},
              {"data": "updated_by", "last_name": "user.last_name"},
          ],
          autoFill: {
              columns: ':not(:first-child)',
              editor:  editor
          },
          keys: {
              columns: ':not(:first-child)',
              editor:  editor
          },
          select: {
              style:    'os',
              selector: 'td:first-child',
              blurable: true
          },
          buttons: [ 
              { extend: "create", editor: editor},
              { extend: "edit",   editor: editor },
              {
                  extend: "selected",
                  text: 'Duplicate',
                  action: function ( e, dt, node, config ) {
                      // Start in edit mode, and then change to create
                      editor
                          .edit( table.rows( {selected: true} ).indexes(), {
                              title: 'Duplicate record',
                              buttons: 'Create from existing'
                          } )
                          .mode( 'create' );
                  }
              },
              { extend: "remove", editor: editor }
              ]
      });
  
  });
  
  &lt;/script&gt;
  {% endblock %}
  </code></pre>
  
  <p>When I try edit or inline editing i get the </p>
  
  <blockquote>
    <p>error 404  JSON: {"detail": "Not found."}</p>
  </blockquote>
  
  <p>When i replace <code>pk</code> in the ajax url with a specific number, then i get </p>
  
  <blockquote>
    <p>error 400</p>
  </blockquote>
  
  <p>and JSON complains that all fields are required when they are not!</p>
  
  <p>I've been stuck at this point for a while, and i can't find an answer that solves my problem.... any help would be appreciated!</p>