Edited at
DjangoDay 8

REST Framework で unique_together の エラーメッセージを変えたい


  • この記事は Django Advent Calendar 2018 の 8日目(代打) と BeProud Advent Calendar 2018 と17日目の記事です。

  • REST Framework の ModelSerializer を使った時に unique_together のエラーメッセージを変えたかった。

  • 通常 Modelunique_together が設定されていたら、ModelSerializerはその設定従い validation してくれる

  • この時のエラーメッセージは Model のデフォルトのエラーメッセージとなる

  • But そのエラメッセージをもう少しわかりやすいものに変えたかったが割とめんどうだったという話


前提


  • Python 3.7

  • Django 2.1.4

  • djangorestframework 3.9.0


確認


  • まずデフォルトのエラーがどうなるか確認する。

  • 適当に unique_together を持つ Model と、それを使う ModelSerializer を用意する


# model ---

class Spam(models.Model):

name = models.CharField('name', max_length=255)
company_id = models.IntegerField('company id')

class Meta:
unique_together = ('name', 'company_id')

# serializer ---

class SpamSerializer(serializers.ModelSerializer):

class Meta:
model = Spam
fields = '__all__'



  • unique_together のエラーを確認

>>> from spam.serializers import SpamSerializer

>>> s = SpamSerializer(data={'name': 'name1', 'company_id': 1})
>>> s.is_valid()
True
>>> s.save()
<Spam: Spam object>
>>> s = SpamSerializer(data={'name': 'name1', 'company_id': 1})
>>> s.is_valid()
False
>>> s.errors
{u'non_field_errors': [ErrorDetail(string=u'The fields name, company_id must make a unique set.', code=u'unique')]}



  • "The fields name, company_id must make a unique set." の部分を適当にカスタマイズしたい


どうするか?

1. Serializer 側に別途 UniqueTogetherValidator を設定する

UniqueTogetherValidator(message='Your custom message', fields=(field1, field2,))

-> エラーメッセージを変えたいだけなのに別途validation設定するのなんか微妙

2. Modelunique_error_messageメソッドoverride する

def unique_error_message(self, model_class, unique_check):

error = super().unique_error_message(model_class, unique_check)
# Intercept the unique_together error
if len(unique_check) != 1:
error.message = 'Your custom message'
return error

-> これもなんか微妙にめんどい、カスタムなエラーメッセージは Serializer側 に寄せたいキムチがある


メッセージだけ書き換えるやつを作る


  • 結局 ModelSerializerunique_together があったら、どっかで UniqueTogetherValidator を使ってるんじゃろう?

  • そこのメッセージだけ変えるようなものを作ればええんやんかという都合の良い推測を立てる。

  • 調べて見た結果 get_unique_together_validators とかいうメソッドを持ってるやんかということがわかったので適当に override してメッセージを差し替えるようにする

  • https://github.com/encode/django-rest-framework/blob/master/rest_framework/serializers.py#L1488

class MyModelSerializer(ModelSerializer):

def get_unique_together_validators(self):
""" override ModelSerializer.get_unique_together_validators """

validators = super().get_unique_together_validators()
kwargs = self.get_extra_kwargs()

my_errors = kwargs.get('my_errors', {})
errors = my_errors.get('unique_together')
if not my_errors or not errors:
return validators

for v in validators:
if v.fields in errors:
v.message = errors.get(v.fields)
return validators

# overrideした Serializerをbaseにする
class SpamSerializer(MyModelSerializer):

class Meta:
model = Spam
fields = '__all__'
extra_kwargs = {
'my_errors': { # <- 適当に自分で決めたエラメッセージを設定する
'unique_together': {
('name', 'company_id'): "その名前は既に使われてはりますなぁ(にちゃぁぁ"
}
}
}

実行結果

>>> from spam.serializers import SpamSerializer

>>> s = SpamSerializer(data={'name': 'name1', 'company_id': 1})
>>> s.is_valid()
False
>>> s.errors
{'non_field_errors': [ErrorDetail(string='その名前は既に使われてはりますなぁ(にちゃぁぁ', code='unique')]}

これでやりたいことはとりあえず出来た。


まとめ



  • unique_together の エラーメッセージ変えるだけが結構めんどい。

  • メッセージ部分だけをoverride するような Serializer を用意して対処した。

  • But このやり方は Undocumented で将来的な restframework の変更に対して何も保証がないので注意が必要。 -> restframework の バージョンアップとともに壊れる可能性がある

  • 多分一番無難で安全なのは UniqueTogetherValidator をひとつづ設定していくのが良い。

以上おわり