5

I have one class representing a Job, one class representing a Tag, which describes a Job and then I have a class making a relationship (junction table) between them, so a Job can be described by multiple Tags:

class JobTag(models.Model):
    job = models.ForeignKey(Job, unique=False, related_name='jobtags')
    tag = models.ForeignKey(Tag, unique=False, related_name='Tag_For_Job')

    created_time = models.DateTimeField(auto_now_add = True)
    modified_time = models.DateTimeField(auto_now = True)

    class Meta:
        unique_together = ('job', 'tag',)

    def __unicode__(self):
        return 'Tag '+self.tag.name +' for job '+ self.job.name

Then I have serializers:

class TagSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Tag
        fields = ('url','name','badge_pic')
        read_only_fields = ('name','badge_pic')

class JobTagSerializer(serializers.HyperlinkedModelSerializer):
    tag = TagSerializer()
    class Meta:
        model = JobTag
        fields = ('tag',)
        depth=1

class JobSerializer(serializers.HyperlinkedModelSerializer):
    jobtags=JobTagSerializer(many=True)
    class Meta:
        model = Job
        fields = ('url','name', 'employer','jobtags','description')
        read_only_fields = ('employer',)

So the http response on GET request is:

{
        "url": "http://127.0.0.1:8000/api/jobs/2/",
        "name": "Odprac mi sneh",
        "employer": "http://127.0.0.1:8000/api/users/4/",
        "jobtags": [
            {
                "tag": {
                    "url": "http://127.0.0.1:8000/api/tags/2/",
                    "name": "Odhadzovanie snehu",
                    "badge_pic": "http://127.0.0.1:8000/media/pictures/tags/0005.jpg"
                }
            }
        ],
        "description": "blablabla"
    }

My question is pretty much obvious, how can I create a job instance and persist them with related JobTags with one POST http request?

I tried to repeat this method http://www.django-rest-framework.org/api-guide/serializers/#writable-nested-representations.

class JobSerializer(serializers.HyperlinkedModelSerializer):
        jobtags=JobTagSerializer(many=True)
        class Meta:
            model = Job
            fields = ('url','name', 'employer','jobtags','description')
            read_only_fields = ('employer',)
        def create(self, validated_data):
            jobtag_data = validated_data.pop('jobtags')
            job = Job.objects.create(**validated_data)
            JobTag.objects.create(job=job, **jobtag_data)
            return job

But it returned "create() argument after ** must be a mapping, not list" error, so what should request.data json looks like?

Or this approach cannot be used in my case and should I do something completly different?

I would appreciate any help.

Edit

if i try to access the list:

def create(self, validated_data):
        jobtag_data = validated_data.pop('jobtags')
        job = Job.objects.create(**validated_data)
        JobTag.objects.create(job=job, **jobtag_data[0])
        return job

I gen another error:"Cannot assign "OrderedDict()": "JobTag.tag" must be a "Tag" instance." So im guessing my posted json is in wrong format? I tried topost data this way:

{
        "name": "Odprac mi sneh",
        "jobtags": [
            {
                "tag": {
                    "url": "http://127.0.0.1:8000/api/tags/2/"
                }
            }
        ],
        "description": "veela sneu nemam ruky makam makam makamam",           
    }
Matúš Bartko
  • 2,425
  • 2
  • 30
  • 42
  • Can you not iterate over `jobtag_data` and create a `JobTag` instance for each item? – Fiver Feb 09 '15 at 22:43
  • It looks like like the jobtags in validated_data are represented by some OrdedDict, which is not acceptable, I made edit to the question – Matúš Bartko Feb 09 '15 at 22:53

2 Answers2

4

I believe you should provide the id and not the url of each tag in your POST data, like so:

{
    "name": "Odprac mi sneh",
    "tags": [
        {
            "id": 2
        },
        {
            "id": 3
        }
    ],
    "description": "veela sneu nemam ruky makam makam makamam"
}

Then, in your create method you should be able to iterate over the tags:

def create(self, validated_data):
    tag_data = validated_data.pop('tags')
    job = Job.objects.create(**validated_data)

    for tag in tag_data:
        JobTag.objects.create(job=job, tag_id=tag["id"])

    return job
Fiver
  • 9,909
  • 9
  • 43
  • 63
  • This way the validation of serialised data doesnt understand your "tags" field , I suppose this could work if i rewrite my serialisers from hyperlinked serialisation to model serialisers, so the foreignkey relationship is represented by PrimaryKey, not the url. But I would still rather remain the hyperlink serialsation. Anyway many thanks for help, I really appreciated it. – Matúš Bartko Feb 10 '15 at 16:08
  • No problem, glad you got it sorted out. – Fiver Feb 10 '15 at 16:19
4

If anyone else is facing this, the most suitable solution I figured out, so that the hyperlinked serialisation of jobtags was used when object is created and nested serialisation was used as an output is this:

I wrote serializers for each of these cases, for serialisation of data sent to client:

class JobTagNestedSerializer(serializers.HyperlinkedModelSerializer):
    tag = TagSerializer()
    class Meta:
        model = JobTag
        fields = ('tag',)
        depth=1



class JobNestedSerializer(serializers.HyperlinkedModelSerializer):
    jobtags=JobTagNestedSerializer(many=True,read_only=True)
    class Meta:
        model = Job
        fields = ('url','name', 'employer','jobtags','description')
        read_only_fields = ('employer',)

and for creating new Job, so for data sent from client to DRF:

class JobTagSerializer(serializers.HyperlinkedModelSerializer):
        class Meta:
            model = JobTag
            fields = ('tag',)

class JobCreateSerializer(serializers.HyperlinkedModelSerializer):
        jobtags=JobTagSerializer(many=True,required=False)
        class Meta:
            model = Job
            fields = ('url','name', 'employer','jobtags','description')
            read_only_fields = ('employer',)

        def create(self, validated_data):
            tag_data = validated_data.pop('jobtags')
            job = Job.objects.create(**validated_data)
            for tag in tag_data:
                d=dict(tag)
                JobTag.objects.create(job=job, tag_id=d['tag'].pk)
            return job

So DRF is expecting the POST json from client to looks like:

{
        "name": "Odprac mi sneh",
        "employer": "http://127.0.0.1:8000/api/users/4/",
        "jobtags": [
            {
                "tag": "http://127.0.0.1:8000/api/tags/2/"
            },
            {
                "tag": "http://127.0.0.1:8000/api/tags/5/"
            }
        ],
        "description": "veela sneu nemam ruky makam makam makamam"
    }
Matúš Bartko
  • 2,425
  • 2
  • 30
  • 42
  • Yes, for example the "http://127.0.0.1:8000/api/tags/2/" is the uri (hyperlink) to related resource, therefor Im using HyperlinkedModelSerializer. if you intent to use modelSerializer, you should probably use another unique identifier of related resource like primary key (i suppose). – Matúš Bartko Nov 03 '16 at 12:11