- この記事は Django Advent Calendar 2018 の 8日目(代打) と BeProud Advent Calendar 2018 と17日目の記事です。
- REST Framework の
ModelSerializer
を使った時にunique_together
のエラーメッセージを変えたかった。 - 通常
Model
にunique_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." の部分を適当にカスタマイズしたい
どうするか?
- とりあえず適当にググってみた結果、以下の方法が出てきた
- https://stackoverflow.com/a/49751507
1. Serializer
側に別途 UniqueTogetherValidator
を設定する
UniqueTogetherValidator(message='Your custom message', fields=(field1, field2,))
-> エラーメッセージを変えたいだけなのに別途validation設定するのなんか微妙
2. Model
の unique_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側
に寄せたいキムチがある
メッセージだけ書き換えるやつを作る
- 結局
ModelSerializer
はunique_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
をひとつづ設定していくのが良い。
以上おわり