Django many-to-many model DRF

2019-03-05 11:31发布

问题:

I have the following model structure:

class Project(models.Model):
  author = models.ManyToManyField(Account)
  name = models.CharField(max_length=40, default='NewBook')

class Account(AbstractBaseUser):
  email = models.EmailField(unique=True)
  username = models.CharField(max_length=40, unique=True)
  first_name = models.CharField(max_length=40, blank=True)
  last_name = models.CharField(max_length=40, blank=True)
  tagline = models.CharField(max_length=140, blank=True)
  is_admin = models.BooleanField(default=False)
  created_at = models.DateTimeField(auto_now_add=True)
  updated_at = models.DateTimeField(auto_now=True)
  objects = AccountManager()
  USERNAME_FIELD = 'email'
  REQUIRED_FIELDS = ['username']

My view looks like this:

class ProjectViewSet(viewsets.ModelViewSet):
  queryset = Project.objects.order_by('-name')
  serializer_class = ProjectSerializer

  def perform_create(self, serializer):
    instance = serializer.save(author=self.request.user)
    return super(ProjectViewSet, self).perform_create(serializer)

After calling the view function, a classifier gets created in the database. But after that, I get the following error:

TypeError: 'Account' object is not iterable

The error gets thrown in this line:

instance = serializer.save(author=self.request.user)

Anyone how can help me with this?

回答1:

There are two problems here:

  1. Showing nested relationships for M2M field:

If the field is used to represent a to-many relationship, you should add the many=True flag to the serializer field.

So you need to add many=True to AccountSerializer:

author = AccountSerializer(read_only=True, required=False, many=True)
  1. A writable nested serializer:

By default nested serializers are read-only. If you want to support write-operations to a nested serializer field you'll need to create create() and/or update() methods in order to explicitly specify how the child relationships should be saved.

So if you look at the example and the documentation it seems that you need to implement create or update method.



回答2:

You need to set many=True when dealing with multiple relation - either a m2m or a reversed FK:

author = AccountSerializer(read_only=True, required=False, many=True)


回答3:

Since your Author field is many to many, you will need to override the create method on your serializer.

def create(self, validated_data):
     author = validated_data.pop(author, None)
     project = Project.objects.save(validated_data)
     if author:
         project.author.add(author)

You will also probably need to set the update method on the serializer, the behavior here can be tricky so make sure you test and make sure the behavior is what you expect.



回答4:

Ok, my previous answer, though could be an issue, isn't the root cause of the actual crash.

When calling the serializer, you set:

instance = serializer.save(author=self.request.user)

However, author is a ManyToManyField which means you should call the serializer as:

instance = serializer.save(author=[self.request.user])

NB: you still require the many=True on the serializer's author field.



回答5:

Please check...

your model.py

class Account(AbstractBaseUser):
  email = models.EmailField(unique=True)
  username = models.CharField(max_length=40, unique=True)
  first_name = models.CharField(max_length=40, blank=True)
  last_name = models.CharField(max_length=40, blank=True)
  tagline = models.CharField(max_length=140, blank=True)
  is_admin = models.BooleanField(default=False)
  created_at = models.DateTimeField(auto_now_add=True)
  updated_at = models.DateTimeField(auto_now=True)
  objects = AccountManager()
  USERNAME_FIELD = 'email'
  REQUIRED_FIELDS = ['username']


class Project(models.Model):
      author = models.ManyToManyField(Account)
      name = models.CharField(max_length=40, default='NewBook')

your serializer.py

class ProjectSerializer(serializers.ModelSerializer):
    author = AccountSerializer(read_only=True, required=False)

    class Meta:
        model = Project
        fields = ('id', 'author', 'name')
        read_only_fields = ('id')

    def get_validation_exclusions(self, *args, **kwargs):
        exclusions = super(ProjectSerializer, self).get_validation_exclusions()
        return exclusions + ['author']

and finally your view.py is

class ProjectViewSet(viewsets.ModelViewSet):
  queryset = Project.objects.order_by('-name')
  serializer_class = ProjectSerializer

  def perform_create(self, serializer):
    instance = serializer.save(author=self.request.user)
    return super(ProjectViewSet, self).perform_create(serializer)