0

I have a React application where I'm using Django+REST for the API and backend. On the frontend, I send a POST request to create a Job model and run the job. In the response, I retrieve the ID of the new Job object.

axios.post('http://127.0.0.1:8000/api/jobs/', query)
 .then((resp) => {
   setActiveJobs([...activeJobs, resp.data.id])
 }, (error) => {
   console.log(error)
 })   

When the job finishes running, it creates multiple Result objects associated to it by a ForeignKey. These are my two models:

class Job(models.Model):
    query = models.CharField(max_length=255, blank=True, null=True)
    status = models.CharField(max_length=10, blank=True, null=True)

class Result(models.Model):
    job = models.ForeignKey(Job, blank=False, null=False, on_delete=models.CASCADE)
    result_no = models.IntegerField(blank=False, null=False)
    ... more fields

I'm having trouble using rest_framework in Django to send a GET request using the Job's ID to get a list of all the Result associated with it. I've tried to give custom implementations for retrieve(self, request, pk=None) and get_queryset(self) but so far my approaches have not worked. Ideally, I'd like to call a url such as localhost:8000/api/results/15 where 15 would be the object id of the Job that was created. If there are 5 Result objects that have the job ForeignKey field set as the Job object with an ID of 15, a list of those 5 Results would be returned in the response.

My parsed-down views.py and urls.py are below:

views.py

class ResultsView(viewsets.ModelViewSet):
    serializer_class = ResultSerializer
    queryset = Result.objects.all()

    # one potential solution could be along the lines of this? Does not work tho
    def get_queryset(self):
        job_id = self.kwargs['pk']
        queryset = self.queryset.filter(job = job_id)
        return queryset

urls.py

router = routers.DefaultRouter()                   
router.register(r'results', views.ResultsView, 'my_project')

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include(router.urls)),
    # re_path('^api/results/(?P<job>.+)/$', views.ResultsView),
]

I think there may be an issue with attempting to match an integer ID to a ForeignKey field that is an object, but when I view localhost:8000/api/results/ I am able to see an object where the ForeignKey field job is listed as a plain integer.

Is there an appropriate pattern for a task like this?

jpc
  • 129
  • 3
  • 16
  • What's not working exactly in `retrieve`? Are you getting `Result` object instead of `Job`? Also if 15 is for Job id in `localhost:8000/api/results/15`, wouldn't it make more sense to use `localhost:8000/api/job/15/results`? `results/15` is saying 'I want the result object with result id 15' – Brian Destura Jul 07 '21 at 00:00
  • Does this answer your question? [What's the difference between select\_related and prefetch\_related in Django ORM?](https://stackoverflow.com/questions/31237042/whats-the-difference-between-select-related-and-prefetch-related-in-django-orm) – Ankit Tiwari Jul 07 '21 at 17:59

2 Answers2

0

I believe you need to either use queryparam or kwarg other than pk in get_queryset because base retrieve method filters based on pk

def get_queryset(self):
  queryset = Result.objects.all()
  job_id = self.request.query_params.get('job_id')
  if job_id is not None:
      queryset = queryset.filter(job=job_id)
  return queryset
 #OR
def get_queryset(self):
  job_id = self.kwargs['job_id']
  queryset = self.queryset.filter(job = job_id)
  return queryset

If you are not using query param, router in urls.py needs to be changed to

router.register('results/(?P<job_id>.+)', ResultsView)

However, a better way would be using related name to retrieve all results with certain job id. You could do something like this in JobView

@action(detail="False")
def result_list(self,request, pk):
  job = Job.objects.get(id=pk)
  result_qs = job.result_set.all()
  serializer = ResultSerializer(result_qs, many=True)
  return Response(serializer.data)

The url for this would be /api/jobs/1/result_list/. Here related name is not defined so default is result_set. But we can define it in Result model.

sunsun
  • 506
  • 3
  • 6
0
class ResultsView(viewsets.ModelViewSet):
    serializer_class = ResultSerializer
    queryset = Result.objects.all()

    # one potential solution could be along the lines of this? Does not work tho
    def get_queryset(self):
        job_id = self.kwargs['pk']
        queryset = self.queryset.filter(job = job_id)
        return queryset

The problem here is that self.kwargs['pk'] is an id for a Result, not a Job.

There are two possible solutions here:

  1. Accept queryparams with your view so that the request can specify a job id with something like http://localhost:8000/api/results/?job_id=1. I suggest using django-filter to do this with minimal amounts of code and high amounts of flexibility.

  2. Add an @action to your JobsView that allows you to make a request like http://localhost:8000/api/jobs/1/results/ to get all the requests for a given job.

Code-Apprentice
  • 81,660
  • 23
  • 145
  • 268