MarkD
MarkD

Reputation: 4954

Django REST Framework- how can I create or update a foreign key object when POSTing parent

I am currently working on an API view, for a Job Planning system. Each Job, has a single JobPlan (which has a start and end date) and each JobPlan, may have several JobPlanManagers, users assigned to manage said Job. User one might be in charge of one month, and User two might overlap User 1, but extend the job by a couple of weeks. Thus, when User 2 POSTS their new JobPlanManager instance, they need to either create a new JobPlan (if no plan yet exists) or update the existing JobPlan, to extend the start and end appropriately. The user's POST data would include their user ID, the Job ID, and a start and end date, something like:

{
  "manager": (User ID),
  "job_plan": {
    "id": null,
    "job": {
      "id": (existing Job ID)
    },
    "start": "2018-02-01",
    "end": "2018-02-28"
  }
}

Additionally, I would like the return of this POST call to include all fields for JobPlan and Job, nested: e.g.:

{
  "id": (created JobPlanManager instance ID)
  "manager": (User ID),
  "job_plan": {
    "id": (New or existing JobPlan ID)
    "job": {
      "id": (Existing Job ID),
      "name": "Existing Job Name"
    }
    "start": "2018-01-02",
    "end": "2018-02-28"
  }
}

My models look like:

class Job(models.Model)
    name = models.CharField()


class JobPlan(models.Model):
    job = models.ForeignKey(Job, unique=True)
    start = models.DateField()
    end = models.DateField()


class JobPlanManager(models.Model):
    job_plan = models.ForeignKey(JobPlan)
    manager = models.ForeignKey(User)
    is_open = models.Boolean(default=False)
    delta = models.IntegerField()

For my serializers, I am doing this in separate calls (e.g.- first create/update a JobPlan, then pass the returned id along with the user ID to create my new JobPlanManager), and they look like this:

class JobSerializer(serializers.ModelSerializer):
    class Meta:
        model = Job
        fields = ('id', 'name',)
        read_only_fields('id')


class JobPlanSerializer(serializers.ModelSerializer):
    job = JobSerializer(many=False)

    class Meta:
        model = JobPlan
        fields = ('id', 'job', 'start', 'end',)
        read_only_fields('id')

    def create(self, validated_data):
        i_job = validated_data.get('job', None)
        start = validated_data.get('start', None)
        end = validated_data.get('end', None)
        if JobPlan.objects.filter(job=i_job).exists():
            jobplan = JobPlan.objects.get(job=i_job)
            jobplan.start = min(jobplan.start, start)
            jobplan.end = max(jobplan.end, end)
            jobplan.save()
        else:
            jobplan = JobPlan.objects.create(job=i_job, start=start, end=end)
        return jobplan

class JobPlanManagerSerializer(serializers.ModelSerializer):
    job_plan = JobPlanSerializer(many=False)

    class Meta:
        model = JobPlanManager
        fields = ('job_plan', 'manager', 'is_open', 'delta',)
        read_only_fields('id')

Does anyone know of a way, that I can do this all in one call (as described above) and get the return described above from said call?

Upvotes: 1

Views: 320

Answers (1)

Gabriel Muj
Gabriel Muj

Reputation: 3815

I use this trick to make it work:

class JobPlanSerializer(serializers.ModelSerializer):
    job = PrimaryKeyRelatedField()

    class Meta:
        ...

    def create(self, validated_data):
        ...

    def to_representation(self, instance):
        ret = super().to_representation(instance)
        ret['job'] = JobSerializer(context=self.context).to_representation(instance.job)
        return ret

When posting data just send "job": (Existing Job ID)

instead of "job": {"id": (existing Job ID)}

Upvotes: 1

Related Questions