Добавление поля, которое не относится к модели для сериализатора в структуре Django REST

У меня есть комментарий модели, который при создании может или не может создать нового пользователя. По этой причине для создания нового комментария для моего API требуется поле пароля. Вот моя модель комментариев:

class Comment(models.Model):
    commenter = models.ManyToManyField(Commenter)
    email = models.EmailField(max_length=100)
    author = models.CharField(max_length=100)
    url = models.URLField(max_length=200)
    content = models.TextField(blank=True, null=True)
    ip = models.IPAddressField(max_length=45)
    date = models.DateTimeField(default=datetime.now)
    post_title = models.CharField(max_length=200)
    post_url = models.URLField(max_length=200)
    rating = models.IntegerField(max_length=10, default=0)

Вот мой вид API:

class CommentNewView(CreateAPIView):
    model = Comment
    serializer_class = CommentCreateSerializer

Вот мой сериализатор:

class CommentCreateSerializer(serializers.ModelSerializer):
    commenter_pw = serializers.CharField(max_length=32, required=False)

    class Meta:
        model = Comment
        fields = ('email', 'author', 'url', 'content', 'ip', 'post_title', 'post_url', 'commenter_pw')

Вот ошибка, которую я получаю:

Environment:


Request Method: POST
Request URL: http://127.0.0.1:8000/api/comment/create/

Django Version: 1.5.2
Python Version: 2.7.2
Installed Applications:
('commentflow.apps.dashboard',
 'commentflow.apps.commenter',
 'commentflow.apps.comment',
 'rest_framework',
 'rest_framework.authtoken',
 'django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.sites',
 'django.contrib.messages',
 'django.contrib.staticfiles',
 'django.contrib.admin',
 'django.contrib.admindocs')
Installed Middleware:
('django.middleware.common.CommonMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'django.middleware.locale.LocaleMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware')


Traceback:
File "/Users/tlovett1/.virtualenvs/commentflow/lib/python2.7/site-packages/django/core/handlers/base.py" in get_response
  115.                         response = callback(request, *callback_args, **callback_kwargs)
File "/Users/tlovett1/.virtualenvs/commentflow/lib/python2.7/site-packages/django/views/generic/base.py" in view
  68.             return self.dispatch(request, *args, **kwargs)
File "/Users/tlovett1/.virtualenvs/commentflow/lib/python2.7/site-packages/django/views/decorators/csrf.py" in wrapped_view
  77.         return view_func(*args, **kwargs)
File "/Users/tlovett1/.virtualenvs/commentflow/lib/python2.7/site-packages/rest_framework/views.py" in dispatch
  327.             response = self.handle_exception(exc)
File "/Users/tlovett1/.virtualenvs/commentflow/lib/python2.7/site-packages/rest_framework/views.py" in dispatch
  324.             response = handler(request, *args, **kwargs)
File "/Users/tlovett1/.virtualenvs/commentflow/lib/python2.7/site-packages/rest_framework/generics.py" in post
  372.         return self.create(request, *args, **kwargs)
File "/Users/tlovett1/.virtualenvs/commentflow/lib/python2.7/site-packages/rest_framework/mixins.py" in create
  50.         if serializer.is_valid():
File "/Users/tlovett1/.virtualenvs/commentflow/lib/python2.7/site-packages/rest_framework/serializers.py" in is_valid
  479.         return not self.errors
File "/Users/tlovett1/.virtualenvs/commentflow/lib/python2.7/site-packages/rest_framework/serializers.py" in errors
  471.                 ret = self.from_native(data, files)
File "/Users/tlovett1/.virtualenvs/commentflow/lib/python2.7/site-packages/rest_framework/serializers.py" in from_native
  867.         instance = super(ModelSerializer, self).from_native(data, files)
File "/Users/tlovett1/.virtualenvs/commentflow/lib/python2.7/site-packages/rest_framework/serializers.py" in from_native
  324.             return self.restore_object(attrs, instance=getattr(self, 'object', None))
File "/Users/tlovett1/.virtualenvs/commentflow/lib/python2.7/site-packages/rest_framework/serializers.py" in restore_object
  852.             instance = self.opts.model(**attrs)
File "/Users/tlovett1/.virtualenvs/commentflow/lib/python2.7/site-packages/django/db/models/base.py" in __init__
  415.                 raise TypeError("'%s' is an invalid keyword argument for this function" % list(kwargs)[0])

Exception Type: TypeError at /api/comment/create/
Exception Value: 'commenter_pw' is an invalid keyword argument for this function

Ответ 1

Если кому-то интересно, решение должно переопределить метод restore_object и добавить дополнительную экземплярную переменную в объект комментария после ее создания:

def restore_object(self, attrs, instance=None):
        if instance is not None:
            instance.email = attrs.get('email', instance.email)
            instance.author = attrs.get('author', instance.author)
            instance.url = attrs.get('url', instance.url)
            instance.content = attrs.get('content', instance.content)
            instance.ip = attrs.get('ip', instance.ip)
            instance.post_title = attrs.get('post_title', instance.post_title)
            instance.post_url = attrs.get('post_url', instance.post_url)
            return instance

        commenter_pw = attrs.get('commenter_pw')
        del attrs['commenter_pw']

        comment = Comment(**attrs)
        comment.commenter_password = commenter_pw

        return comment

Ответ 2

Предыдущие ответы не работали над DRF3.0, метод restore_object() теперь устарел.

Решение, которое я использовал, ужасно, но я не нашел лучшего. Я поместил фиктивный getter/setter для этого поля в модель, это позволяет использовать это поле как любое другое на модели.

Не забудьте установить поле как write_only в определении сериализатора.

class Comment(models.Model):
    @property
    def commenter_pw():
        return None

    @commenter_pw.setter
    def commenter_pw(self, value):
        pass

class CommentCreateSerializer(serializers.ModelSerializer):
    commenter_pw = serializers.CharField(max_length=32, write_only=True, required=False)

    class Meta:
        model = Comment
        fields = ('email', 'author', 'url', 'content', 'ip', 'post_title', 'post_url', 'commenter_pw')

Ответ 3

Спасибо за ваш собственный ответ, это мне очень помогло:)

Но я думаю, что это немного более общий, так как я все еще хочу вызвать метод класса super serializer

def restore_object(self, attrs, instance=None):
    '''
    we have to ensure that the temporary_password is attached to the model
    even though it is no field
    '''
    commenter_pw = attrs.pop('comment_pw', None)
    obj = super(
        CommentCreateSerializer, self
    ).restore_object(attrs, instance=instance)
    if commenter_pw:
        obj.commenter_pw = commenter_pw
    return obj

Ответ 4

Что вы можете сделать, это перезаписать функцию pre_save или create и вынуть commenter_pw из полей данных, которые отправляются (не уверен, но вы, вероятно, можете вынуть форму request.POST или после вы его сериализовали), поэтому структура не должна вызывать ошибки.

Кроме того, если у вас есть дополнительная логика, вы можете реализовать его там, прежде чем сохранять его (например, тот, который проверяет, должен ли пользователь быть создан или что).