mirror of
https://github.com/encode/django-rest-framework.git
synced 2024-11-22 17:47:04 +03:00
Final bits of docs for ModelSerializer fields API
This commit is contained in:
parent
48d15f6ff8
commit
2a1485e009
|
@ -457,7 +457,7 @@ To do so, open the Django shell, using `python manage.py shell`, then import the
|
||||||
name = CharField(allow_blank=True, max_length=100, required=False)
|
name = CharField(allow_blank=True, max_length=100, required=False)
|
||||||
owner = PrimaryKeyRelatedField(queryset=User.objects.all())
|
owner = PrimaryKeyRelatedField(queryset=User.objects.all())
|
||||||
|
|
||||||
## Specifying which fields should be included
|
## Specifying which fields to include
|
||||||
|
|
||||||
If you only want a subset of the default fields to be used in a model serializer, you can do so using `fields` or `exclude` options, just as you would with a `ModelForm`.
|
If you only want a subset of the default fields to be used in a model serializer, you can do so using `fields` or `exclude` options, just as you would with a `ModelForm`.
|
||||||
|
|
||||||
|
@ -499,7 +499,7 @@ You can add extra fields to a `ModelSerializer` or override the default fields b
|
||||||
|
|
||||||
Extra fields can correspond to any property or callable on the model.
|
Extra fields can correspond to any property or callable on the model.
|
||||||
|
|
||||||
## Specifying which fields should be read-only
|
## Specifying read only fields
|
||||||
|
|
||||||
You may wish to specify multiple fields as read-only. Instead of adding each field explicitly with the `read_only=True` attribute, you may use the shortcut Meta option, `read_only_fields`.
|
You may wish to specify multiple fields as read-only. Instead of adding each field explicitly with the `read_only=True` attribute, you may use the shortcut Meta option, `read_only_fields`.
|
||||||
|
|
||||||
|
@ -528,7 +528,7 @@ Please review the [Validators Documentation](/api-guide/validators/) for details
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
## Specifying additional keyword arguments for fields.
|
## Additional keyword arguments
|
||||||
|
|
||||||
There is also a shortcut allowing you to specify arbitrary additional keyword arguments on fields, using the `extra_kwargs` option. Similarly to `read_only_fields` this means you do not need to explicitly declare the field on the serializer.
|
There is also a shortcut allowing you to specify arbitrary additional keyword arguments on fields, using the `extra_kwargs` option. Similarly to `read_only_fields` this means you do not need to explicitly declare the field on the serializer.
|
||||||
|
|
||||||
|
@ -567,31 +567,62 @@ The inner `Meta` class on serializers is not inherited from parent classes by de
|
||||||
|
|
||||||
Typically we would recommend *not* using inheritance on inner Meta classes, but instead declaring all options explicitly.
|
Typically we would recommend *not* using inheritance on inner Meta classes, but instead declaring all options explicitly.
|
||||||
|
|
||||||
## Advanced `ModelSerializer` usage
|
## Customizing field mappings
|
||||||
|
|
||||||
The ModelSerializer class also exposes an API that you can override in order to alter how serializer fields are automatically determined when instantiating the serializer.
|
The ModelSerializer class also exposes an API that you can override in order to alter how serializer fields are automatically determined when instantiating the serializer.
|
||||||
|
|
||||||
#### `.serializer_field_mapping`
|
Normally if a `ModelSerializer` does not generate the fields you need by default the you should either add them to the class explicitly, or simply use a regular `Serializer` class instead. However in some cases you may want to create a new base class that defines how the serializer fields are created for any given model.
|
||||||
|
|
||||||
|
### `.serializer_field_mapping`
|
||||||
|
|
||||||
A mapping of Django model classes to REST framework serializer classes. You can override this mapping to alter the default serializer classes that should be used for each model class.
|
A mapping of Django model classes to REST framework serializer classes. You can override this mapping to alter the default serializer classes that should be used for each model class.
|
||||||
|
|
||||||
#### `.serializer_relational_field`
|
### `.serializer_relational_field`
|
||||||
|
|
||||||
This property should be the serializer field class, that is used for relational fields by default. For `ModelSerializer` this defaults to `PrimaryKeyRelatedField`. For `HyperlinkedModelSerializer` this defaults to `HyperlinkedRelatedField`.
|
This property should be the serializer field class, that is used for relational fields by default. For `ModelSerializer` this defaults to `PrimaryKeyRelatedField`. For `HyperlinkedModelSerializer` this defaults to `HyperlinkedRelatedField`.
|
||||||
|
|
||||||
#### The build field methods
|
### The field_class and field_kwargs API
|
||||||
|
|
||||||
#### `build_standard_field(**kwargs)`
|
The following methods are called to determine the class and keyword arguments for each field that should be automatically included on the serializer. Each of these methods should return a two tuple of `(field_class, field_kwargs)`.
|
||||||
|
|
||||||
#### `build_relational_field(**kwargs)`
|
### `.build_standard_field(self, field_name, model_field)`
|
||||||
|
|
||||||
#### `build_nested_field(**kwargs)`
|
Called to generate a serializer field that maps to a standard model field.
|
||||||
|
|
||||||
#### `build_property_field(**kwargs)`
|
The default implementation returns a serializer class based on the `serializer_field_mapping` attribute.
|
||||||
|
|
||||||
#### `build_url_field(**kwargs)`
|
### `.build_relational_field(self, field_name, relation_info)`
|
||||||
|
|
||||||
#### `build_unknown_field(**kwargs)`
|
Called to generate a serializer field that maps to a relational model field.
|
||||||
|
|
||||||
|
The default implementation returns a serializer class based on the `serializer_relational_field` attribute.
|
||||||
|
|
||||||
|
The `relation_info` argument is a named tuple, that contains `model_field`, `related_model`, `to_many` and `has_through_model` properties.
|
||||||
|
|
||||||
|
### `.build_nested_field(self, field_name, relation_info, nested_depth)`
|
||||||
|
|
||||||
|
Called to generate a serializer field that maps to a relational model field, when the `depth` option has been set.
|
||||||
|
|
||||||
|
The default implementation dynamically creates a nested serializer class based on either `ModelSerializer` or `HyperlinkedModelSerializer`.
|
||||||
|
|
||||||
|
The `nested_depth` will be the value of the `depth` option, minus one.
|
||||||
|
|
||||||
|
The `relation_info` argument is a named tuple, that contains `model_field`, `related_model`, `to_many` and `has_through_model` properties.
|
||||||
|
|
||||||
|
### `.build_property_field(self, field_name, model_class)`
|
||||||
|
|
||||||
|
Called to generate a serializer field that maps to a property or zero-argument method on the model class.
|
||||||
|
|
||||||
|
The default implementation returns a `ReadOnlyField` class.
|
||||||
|
|
||||||
|
### `.build_url_field(self, field_name, model_class)`
|
||||||
|
|
||||||
|
Called to generate a serializer field for the serializer's own `url` field. The default implementation returns a `HyperlinkedIdentityField` class.
|
||||||
|
|
||||||
|
### `.build_unknown_field(self, field_name, model_class)`
|
||||||
|
|
||||||
|
Called when the field name did not map to any model field or model property.
|
||||||
|
The default implementation raises an error, although subclasses may customize this behavior.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
@ -239,6 +239,10 @@ body a:hover{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h1 code, h2 code, h3 code, h4 code, h5 code {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
/* sticky footer and footer */
|
/* sticky footer and footer */
|
||||||
html, body {
|
html, body {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
|
@ -802,10 +802,25 @@ class ModelSerializer(Serializer):
|
||||||
Return the dict of field names -> field instances that should be
|
Return the dict of field names -> field instances that should be
|
||||||
used for `self.fields` when instantiating the serializer.
|
used for `self.fields` when instantiating the serializer.
|
||||||
"""
|
"""
|
||||||
|
assert hasattr(self, 'Meta'), (
|
||||||
|
'Class {serializer_class} missing "Meta" attribute'.format(
|
||||||
|
serializer_class=self.__class__.__name__
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assert hasattr(self.Meta, 'model'), (
|
||||||
|
'Class {serializer_class} missing "Meta.model" attribute'.format(
|
||||||
|
serializer_class=self.__class__.__name__
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
declared_fields = copy.deepcopy(self._declared_fields)
|
declared_fields = copy.deepcopy(self._declared_fields)
|
||||||
model = getattr(self.Meta, 'model')
|
model = getattr(self.Meta, 'model')
|
||||||
depth = getattr(self.Meta, 'depth', 0)
|
depth = getattr(self.Meta, 'depth', 0)
|
||||||
|
|
||||||
|
if depth is not None:
|
||||||
|
assert depth >= 0, "'depth' may not be negative."
|
||||||
|
assert depth <= 10, "'depth' may not be greater than 10."
|
||||||
|
|
||||||
# Retrieve metadata about fields & relationships on the model class.
|
# Retrieve metadata about fields & relationships on the model class.
|
||||||
info = model_meta.get_field_info(model)
|
info = model_meta.get_field_info(model)
|
||||||
field_names = self.get_field_names(declared_fields, info)
|
field_names = self.get_field_names(declared_fields, info)
|
||||||
|
@ -817,27 +832,32 @@ class ModelSerializer(Serializer):
|
||||||
field_names, declared_fields, extra_kwargs
|
field_names, declared_fields, extra_kwargs
|
||||||
)
|
)
|
||||||
|
|
||||||
# Now determine the fields that should be included on the serializer.
|
# Determine the fields that should be included on the serializer.
|
||||||
ret = OrderedDict()
|
fields = OrderedDict()
|
||||||
|
|
||||||
for field_name in field_names:
|
for field_name in field_names:
|
||||||
|
# If the field is explicitly declared on the class then use that.
|
||||||
if field_name in declared_fields:
|
if field_name in declared_fields:
|
||||||
# Field is explicitly declared on the class, use that.
|
fields[field_name] = declared_fields[field_name]
|
||||||
ret[field_name] = declared_fields[field_name]
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Determine the serializer field class and keyword arguments.
|
# Determine the serializer field class and keyword arguments.
|
||||||
field_cls, kwargs = self.build_field(field_name, info, model, depth)
|
field_class, field_kwargs = self.build_field(
|
||||||
|
field_name, info, model, depth
|
||||||
|
)
|
||||||
|
|
||||||
# Populate any kwargs defined in `Meta.extra_kwargs`
|
# Include any kwargs defined in `Meta.extra_kwargs`
|
||||||
kwargs = self.build_field_kwargs(kwargs, extra_kwargs, field_name)
|
field_kwargs = self.build_field_kwargs(
|
||||||
|
field_kwargs, extra_kwargs, field_name
|
||||||
|
)
|
||||||
|
|
||||||
# Create the serializer field.
|
# Create the serializer field.
|
||||||
ret[field_name] = field_cls(**kwargs)
|
fields[field_name] = field_class(**field_kwargs)
|
||||||
|
|
||||||
# Add in any hidden fields.
|
# Add in any hidden fields.
|
||||||
ret.update(hidden_fields)
|
fields.update(hidden_fields)
|
||||||
|
|
||||||
return ret
|
return fields
|
||||||
|
|
||||||
# Methods for determining the set of field names to include...
|
# Methods for determining the set of field names to include...
|
||||||
|
|
||||||
|
@ -916,108 +936,105 @@ class ModelSerializer(Serializer):
|
||||||
|
|
||||||
# Methods for constructing serializer fields...
|
# Methods for constructing serializer fields...
|
||||||
|
|
||||||
def build_field(self, field_name, info, model, nested_depth):
|
def build_field(self, field_name, info, model_class, nested_depth):
|
||||||
"""
|
"""
|
||||||
Return a two tuple of (cls, kwargs) to build a serializer field with.
|
Return a two tuple of (cls, kwargs) to build a serializer field with.
|
||||||
"""
|
"""
|
||||||
if field_name in info.fields_and_pk:
|
if field_name in info.fields_and_pk:
|
||||||
return self.build_standard_field(field_name, info, model)
|
model_field = info.fields_and_pk[field_name]
|
||||||
|
return self.build_standard_field(field_name, model_field)
|
||||||
|
|
||||||
elif field_name in info.relations:
|
elif field_name in info.relations:
|
||||||
|
relation_info = info.relations[field_name]
|
||||||
if not nested_depth:
|
if not nested_depth:
|
||||||
return self.build_relational_field(field_name, info, model)
|
return self.build_relational_field(field_name, relation_info)
|
||||||
else:
|
else:
|
||||||
return self.build_nested_field(field_name, info, model, nested_depth)
|
return self.build_nested_field(field_name, relation_info, nested_depth)
|
||||||
|
|
||||||
elif hasattr(model, field_name):
|
elif hasattr(model_class, field_name):
|
||||||
return self.build_property_field(field_name, info, model)
|
return self.build_property_field(field_name, model_class)
|
||||||
|
|
||||||
elif field_name == api_settings.URL_FIELD_NAME:
|
elif field_name == api_settings.URL_FIELD_NAME:
|
||||||
return self.build_url_field(field_name, info, model)
|
return self.build_url_field(field_name, model_class)
|
||||||
|
|
||||||
return self.build_unknown_field(field_name, info, model)
|
return self.build_unknown_field(field_name, model_class)
|
||||||
|
|
||||||
def build_standard_field(self, field_name, info, model):
|
def build_standard_field(self, field_name, model_field):
|
||||||
"""
|
"""
|
||||||
Create regular model fields.
|
Create regular model fields.
|
||||||
"""
|
"""
|
||||||
field_mapping = ClassLookupDict(self.serializer_field_mapping)
|
field_mapping = ClassLookupDict(self.serializer_field_mapping)
|
||||||
model_field = info.fields_and_pk[field_name]
|
|
||||||
|
|
||||||
field_cls = field_mapping[model_field]
|
field_class = field_mapping[model_field]
|
||||||
kwargs = get_field_kwargs(field_name, model_field)
|
field_kwargs = get_field_kwargs(field_name, model_field)
|
||||||
|
|
||||||
if 'choices' in kwargs:
|
if 'choices' in field_kwargs:
|
||||||
# Fields with choices get coerced into `ChoiceField`
|
# Fields with choices get coerced into `ChoiceField`
|
||||||
# instead of using their regular typed field.
|
# instead of using their regular typed field.
|
||||||
field_cls = ChoiceField
|
field_class = ChoiceField
|
||||||
if not issubclass(field_cls, ModelField):
|
if not issubclass(field_class, ModelField):
|
||||||
# `model_field` is only valid for the fallback case of
|
# `model_field` is only valid for the fallback case of
|
||||||
# `ModelField`, which is used when no other typed field
|
# `ModelField`, which is used when no other typed field
|
||||||
# matched to the model field.
|
# matched to the model field.
|
||||||
kwargs.pop('model_field', None)
|
field_kwargs.pop('model_field', None)
|
||||||
if not issubclass(field_cls, CharField) and not issubclass(field_cls, ChoiceField):
|
if not issubclass(field_class, CharField) and not issubclass(field_class, ChoiceField):
|
||||||
# `allow_blank` is only valid for textual fields.
|
# `allow_blank` is only valid for textual fields.
|
||||||
kwargs.pop('allow_blank', None)
|
field_kwargs.pop('allow_blank', None)
|
||||||
|
|
||||||
return field_cls, kwargs
|
return field_class, field_kwargs
|
||||||
|
|
||||||
def build_relational_field(self, field_name, info, model):
|
def build_relational_field(self, field_name, relation_info):
|
||||||
"""
|
"""
|
||||||
Create fields for forward and reverse relationships.
|
Create fields for forward and reverse relationships.
|
||||||
"""
|
"""
|
||||||
relation_info = info.relations[field_name]
|
field_class = self.serializer_related_class
|
||||||
|
field_kwargs = get_relation_kwargs(field_name, relation_info)
|
||||||
field_cls = self.serializer_related_class
|
|
||||||
kwargs = get_relation_kwargs(field_name, relation_info)
|
|
||||||
|
|
||||||
# `view_name` is only valid for hyperlinked relationships.
|
# `view_name` is only valid for hyperlinked relationships.
|
||||||
if not issubclass(field_cls, HyperlinkedRelatedField):
|
if not issubclass(field_class, HyperlinkedRelatedField):
|
||||||
kwargs.pop('view_name', None)
|
field_kwargs.pop('view_name', None)
|
||||||
|
|
||||||
return field_cls, kwargs
|
return field_class, field_kwargs
|
||||||
|
|
||||||
def build_nested_field(self, field_name, info, model, nested_depth):
|
def build_nested_field(self, field_name, relation_info, nested_depth):
|
||||||
"""
|
"""
|
||||||
Create nested fields for forward and reverse relationships.
|
Create nested fields for forward and reverse relationships.
|
||||||
"""
|
"""
|
||||||
relation_info = info.relations[field_name]
|
|
||||||
|
|
||||||
class NestedSerializer(ModelSerializer):
|
class NestedSerializer(ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = relation_info.related
|
model = relation_info.related_model
|
||||||
depth = nested_depth - 1
|
depth = nested_depth
|
||||||
|
|
||||||
field_cls = NestedSerializer
|
field_class = NestedSerializer
|
||||||
kwargs = get_nested_relation_kwargs(relation_info)
|
field_kwargs = get_nested_relation_kwargs(relation_info)
|
||||||
|
|
||||||
return field_cls, kwargs
|
return field_class, field_kwargs
|
||||||
|
|
||||||
def build_property_field(self, field_name, info, model):
|
def build_property_field(self, field_name, model_class):
|
||||||
"""
|
"""
|
||||||
Create a read only field for model methods and properties.
|
Create a read only field for model methods and properties.
|
||||||
"""
|
"""
|
||||||
field_cls = ReadOnlyField
|
field_class = ReadOnlyField
|
||||||
kwargs = {}
|
field_kwargs = {}
|
||||||
|
|
||||||
return field_cls, kwargs
|
return field_class, field_kwargs
|
||||||
|
|
||||||
def build_url_field(self, field_name, info, model):
|
def build_url_field(self, field_name, model_class):
|
||||||
"""
|
"""
|
||||||
Create a field representing the object's own URL.
|
Create a field representing the object's own URL.
|
||||||
"""
|
"""
|
||||||
field_cls = HyperlinkedIdentityField
|
field_class = HyperlinkedIdentityField
|
||||||
kwargs = get_url_kwargs(model)
|
field_kwargs = get_url_kwargs(model_class)
|
||||||
|
|
||||||
return field_cls, kwargs
|
return field_class, field_kwargs
|
||||||
|
|
||||||
def build_unknown_field(self, field_name, info, model):
|
def build_unknown_field(self, field_name, model_class):
|
||||||
"""
|
"""
|
||||||
Raise an error on any unknown fields.
|
Raise an error on any unknown fields.
|
||||||
"""
|
"""
|
||||||
raise ImproperlyConfigured(
|
raise ImproperlyConfigured(
|
||||||
'Field name `%s` is not valid for model `%s`.' %
|
'Field name `%s` is not valid for model `%s`.' %
|
||||||
(field_name, model.__class__.__name__)
|
(field_name, model_class.__name__)
|
||||||
)
|
)
|
||||||
|
|
||||||
def build_field_kwargs(self, kwargs, extra_kwargs, field_name):
|
def build_field_kwargs(self, kwargs, extra_kwargs, field_name):
|
||||||
|
@ -1318,17 +1335,16 @@ class HyperlinkedModelSerializer(ModelSerializer):
|
||||||
list(model_info.forward_relations.keys())
|
list(model_info.forward_relations.keys())
|
||||||
)
|
)
|
||||||
|
|
||||||
def build_nested_field(self, field_name, info, model, nested_depth):
|
def build_nested_field(self, field_name, relation_info, nested_depth):
|
||||||
"""
|
"""
|
||||||
Create nested fields for forward and reverse relationships.
|
Create nested fields for forward and reverse relationships.
|
||||||
"""
|
"""
|
||||||
relation_info = info.relations[field_name]
|
|
||||||
|
|
||||||
class NestedSerializer(HyperlinkedModelSerializer):
|
class NestedSerializer(HyperlinkedModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = relation_info.related
|
model = relation_info.related_model
|
||||||
depth = nested_depth - 1
|
depth = nested_depth - 1
|
||||||
|
|
||||||
field_cls = NestedSerializer
|
field_class = NestedSerializer
|
||||||
kwargs = get_nested_relation_kwargs(relation_info)
|
field_kwargs = get_nested_relation_kwargs(relation_info)
|
||||||
return field_cls, kwargs
|
|
||||||
|
return field_class, field_kwargs
|
||||||
|
|
|
@ -24,7 +24,7 @@ FieldInfo = namedtuple('FieldResult', [
|
||||||
|
|
||||||
RelationInfo = namedtuple('RelationInfo', [
|
RelationInfo = namedtuple('RelationInfo', [
|
||||||
'model_field',
|
'model_field',
|
||||||
'related',
|
'related_model',
|
||||||
'to_many',
|
'to_many',
|
||||||
'has_through_model'
|
'has_through_model'
|
||||||
])
|
])
|
||||||
|
@ -77,7 +77,7 @@ def get_field_info(model):
|
||||||
for field in [field for field in opts.fields if field.serialize and field.rel]:
|
for field in [field for field in opts.fields if field.serialize and field.rel]:
|
||||||
forward_relations[field.name] = RelationInfo(
|
forward_relations[field.name] = RelationInfo(
|
||||||
model_field=field,
|
model_field=field,
|
||||||
related=_resolve_model(field.rel.to),
|
related_model=_resolve_model(field.rel.to),
|
||||||
to_many=False,
|
to_many=False,
|
||||||
has_through_model=False
|
has_through_model=False
|
||||||
)
|
)
|
||||||
|
@ -86,7 +86,7 @@ def get_field_info(model):
|
||||||
for field in [field for field in opts.many_to_many if field.serialize]:
|
for field in [field for field in opts.many_to_many if field.serialize]:
|
||||||
forward_relations[field.name] = RelationInfo(
|
forward_relations[field.name] = RelationInfo(
|
||||||
model_field=field,
|
model_field=field,
|
||||||
related=_resolve_model(field.rel.to),
|
related_model=_resolve_model(field.rel.to),
|
||||||
to_many=True,
|
to_many=True,
|
||||||
has_through_model=(
|
has_through_model=(
|
||||||
not field.rel.through._meta.auto_created
|
not field.rel.through._meta.auto_created
|
||||||
|
@ -99,7 +99,7 @@ def get_field_info(model):
|
||||||
accessor_name = relation.get_accessor_name()
|
accessor_name = relation.get_accessor_name()
|
||||||
reverse_relations[accessor_name] = RelationInfo(
|
reverse_relations[accessor_name] = RelationInfo(
|
||||||
model_field=None,
|
model_field=None,
|
||||||
related=relation.model,
|
related_model=relation.model,
|
||||||
to_many=relation.field.rel.multiple,
|
to_many=relation.field.rel.multiple,
|
||||||
has_through_model=False
|
has_through_model=False
|
||||||
)
|
)
|
||||||
|
@ -109,7 +109,7 @@ def get_field_info(model):
|
||||||
accessor_name = relation.get_accessor_name()
|
accessor_name = relation.get_accessor_name()
|
||||||
reverse_relations[accessor_name] = RelationInfo(
|
reverse_relations[accessor_name] = RelationInfo(
|
||||||
model_field=None,
|
model_field=None,
|
||||||
related=relation.model,
|
related_model=relation.model,
|
||||||
to_many=True,
|
to_many=True,
|
||||||
has_through_model=(
|
has_through_model=(
|
||||||
(getattr(relation.field.rel, 'through', None) is not None)
|
(getattr(relation.field.rel, 'through', None) is not None)
|
||||||
|
|
|
@ -206,7 +206,7 @@ class TestRegularFieldMappings(TestCase):
|
||||||
|
|
||||||
with self.assertRaises(ImproperlyConfigured) as excinfo:
|
with self.assertRaises(ImproperlyConfigured) as excinfo:
|
||||||
TestSerializer().fields
|
TestSerializer().fields
|
||||||
expected = 'Field name `invalid` is not valid for model `ModelBase`.'
|
expected = 'Field name `invalid` is not valid for model `RegularFieldsModel`.'
|
||||||
assert str(excinfo.exception) == expected
|
assert str(excinfo.exception) == expected
|
||||||
|
|
||||||
def test_missing_field(self):
|
def test_missing_field(self):
|
||||||
|
|
Loading…
Reference in New Issue
Block a user