ModelSerializer with data as list - django-rest-framework

I have a ModelSerializer class as follows which I want to accept a list of items or a single item (dictionary) as data. The documentation states that passing "many" as True will support my requirement.
class PointSerializer(serializers.ModelSerializer):
class Meta:
model = Point
def __init__(self, *args, **kwargs):
if "data" in kwargs:
if isinstance(kwargs["data"]):
kwargs["many"] = True
super(PointSerializer, self).__init__(*args, **kwargs)
Now, providing data dictionary as follows works:
p = PointSerializer(data={'x':10, 'y': 12})
p.is_valid() # True
But this, with a list of dictionaries, fails:
p = PointSerializer(data=[{'x':10, 'y':12}, {'x':12, 'y':12}])
p.is_valid() # False
p.errors() # {'non_field_errors': ['Invalid data. Expected a dictionary, but got a list.']}
UPDATE:
Thanks to the chosen answer, I've changed my code to the following and it works fine:
class PointSerializer(serializers.ModelSerializer):
class Meta:
model = Point
>>> ps = PointSerializer(data={'x':10, 'y':12})
>>> ps.is_valid()
... True
>>> ps = PointSerializer(data=[{'x':10, 'y':12}, {'x':12, 'y':12}], many=True)
>>> ps.is_valid()
... True

many=True argument will only work when instantiating the serializer because it'll return a ListSerializer behind the scene.
Your option are either you set the many=True as serializer argument during creation call, either use explicitly the ListSerializer.

Related

How can you deal with a reserved word (from) in a serializer in DRF

Below is a serializer with a reserved word (from) used in a field:
class EdgeSerializer(serializers.Serializer):
field_name_map = {
'_from': 'from'
}
class Meta:
read_only_fields = ('_from', 'to', 'weight')
_from = serializers.IntegerField(required=True)
to = serializers.IntegerField(required=True)
weight = serializers.FloatField(required=True)
The problem is when I read serializer.data the field name in the "output" is _from, not from. I've tried using source=, I've tried reversing the field_name_map (key, value) => (value, key).
I can't use from as the name of the field, python blows up trying to parse the file
So, I did this which works, but which leaves a bad taste in my mouth and seems the "wrong" way to do it:
class FromIntegerField(serializers.IntegerField):
def bind(self, field_name, parent):
super().bind(field_name, parent)
self.field_name = 'from'
class EdgeSerializer(serializers.Serializer):
_from = FromIntegerField(required=True)
to = serializers.IntegerField(required=True)
weight = serializers.FloatField(required=True)
class Meta:
read_only_fields = ('_from', 'to', 'weight')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
#self.fields (a `BindingDict`) contains a field keyed by '_from'
# to the field where I changed the field_name to 'from'
While this works, the smell is a little bit too much for me and I'd like to know the right way to do this. I could generalize this approach to allow me to pass an output_name='from' but that still seems wrong. I would also like to be able to take a dict that has a key of from rather than _from as well but that is a secondary concern. I think I can do it by changing self.fields to be keyed off of 'from' instead of '_from' or setting the source to 'from' in the FromIntegerField class
In my opinion it is quite ugly but likely works, you can override the .get_fields(…) method
class EdgeSerializer(serializers.Serializer):
_from = FromIntegerField(required=True)
# …
def get_fields(self):
result = super().get_fields()
_form = result.pop('_from', None)
result['from'] = _from
return result
here we thus map the original key-value pairs to a slightly different dictionary where we add this to the from key.

How to implement Elasticsearch advanced search with DRF

I want to implement a search in Elastic Search with Django Rest Framework. I have a form for searching as follows.
I used a serializer to implement this form.
search.py:
class AdvancedSearch(mixins.ListModelMixin, viewsets.GenericViewSet):
serializer_class = AdvancedSearchSerializer
def query_builder(self, *args, **kwargs):
## building related query
return query
#get_db()
def get_queryset(self, db=None, *args, **kwargs):
serializer = self.get_serializer(data=self.request.data)
serializer.is_valid(raise_exception=True)
query = self.query_builder(search_input=serializer.validated_data)
response = db.search(query) # query the elastic with elasticsearch-dsl and return the results
if not response:
raise NoteFound()
return response
def list(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
self.serializer_class = AdvancedSearchSerializer
return super(AdvancedSearch, self).list(request, *args, **kwargs)
serializer.py:
class AdvancedSearchSerializer(serializers.Serializer):
metadata_choices = [('', ''), ...]
name = serializers.CharField(required=False, label='Name')
type = serializers.CharField(required=False, label='Type')
metadata = serializers.CharField(required=False, label='Metadata')
metadata_fields = serializers.MultipleChoiceField(allow_blank=True, choices=metadata_choices)
submit_date = serializers.DateTimeField(required=False)
def to_representation(self, instance):
output = {}
output['es_id'] = instance.meta.id
for attribute_name in instance:
attribute = getattr(instance, attribute_name)
if isinstance(attribute, (str, int, bool, float, type(None))):
# Primitive types can be passed through unmodified.
output[attribute_name] = attribute
elif isinstance(attribute, list):
# Recursively deal with items in lists.
output[attribute_name] = [
self.to_representation(item) for item in attribute
]
elif isinstance(attribute, (dict, AttrDict)):
temp = attribute.to_dict()
for key, value in temp.items():
print(key,value)
# Recursively deal with items in dictionaries.
output[attribute_name] = {
str(key): value
for key, value in temp.items()
}
else:
# Force anything else to its string representation.
output[attribute_name] = attribute
output['highlight'] = instance.meta.highlight.to_dict()
return [output]
With this code, I get the expected result, but I was wondering if this is a right approach.
And also in to_representation I have access to each result, but how can I add a total value like the number of results.
Thanks in advance.

Cloning multiple objects and returning them back in response with DRF

My goal is to clone mulitple objects and serialize them so that I can return the result back in my REST Api. Currently my code looks like this:
objects_to_clone = MyModel.objects.filter(...)
new_objects = []
for obj in objects_to_clone:
clone = copy.deepcopy(file)
clone.id = None
# Do something else with the cloned object
new_objects.append(clone)
serializer = MySerializer(data=new_objects, many=True) # YIELDS ERROR
if serializer.is_valid(raise_exception=True):
serializer.save()
return Response(serializer.data)
The problem with this code is that Django justfully yields
nonFieldErrors - Invalid data. Expected a dictionary, but got MyModel
How is it possible to overcome this without having to find dirty workarounds ?
You are getting that error because you are passing the models to the serializer instead of a dictionary.
You can convert the models to a dictionary by using .values() and specifying the fields you want returned.
The values() method returns a QuerySet that returns dictionaries, rather than model instances, when used as an iterable.
For example what you currently have evaluates to something like this
MyModel.objects.filter(...)
<QuerySet [<MyModel: Random Title>]>
but if you make use of values, the result of the same query will be
MyModel.objects.filter(...).values()
<QuerySet [{'id': 1, 'name': 'Model 1'}, {'id': 2, 'name': 'Model 2'}]>
at the end of the day, your code will be something like this
objects_to_clone = MyModel.objects.filter(...).values()
new_objects = []
for obj in objects_to_clone:
obj.id = None
# Do something else with the cloned object
new_objects.append(obj)
serializer = MySerializer(data=new_objects, many=True) # YIELDS ERROR
if serializer.is_valid(raise_exception=True):
serializer.save()
return Response(serializer.data)
What if you don't want to have to set the id of the object to None? The .values method allows you to specify the fields of the model you want
so a modified version of your code that is more straightforward will be
objects_to_clone = MyModel.objects.filter(...).values('name', 'description') # fake info of course
serializer = MySerializer(data=objects_to_clone, many=True) # YIELDS ERROR
if serializer.is_valid(raise_exception=True):
serializer.save()
return Response(serializer.data)

Overriding from_yaml to add custom YAML tag

Is overriding from_yaml enough to register a tag from a class or is it necessary to use yaml.add_constructor(Class.yaml_tag, Class.from_yaml)? If I don't use te add_constructor method, my YAML tags are not recognized. Example of what I have:
import yaml
class Something(yaml.YAMLObject):
yaml_tag = u'!Something'
#classmethod
def from_yaml(cls,loader,node):
# Set attributes to None if not in file
values = loader.construct_mapping(node, deep=True)
attr = ['attr1','attr2']
result = {}
for val in attr:
try:
result[val] = values[val]
except KeyError:
result[val] = None
return cls(**result)
Is this enough for it to work? I'm confused with the use of from_yaml vs any other constructor you would register using the method I mentioned above. I suppose there's something fundamental I'm missing, since they say:
Subclassing YAMLObject is an easy way to define tags, constructors,
and representers for your classes. You only need to override the
yaml_tag attribute. If you want to define your custom constructor and
representer, redefine the from_yaml and to_yaml method
correspondingly.
There is indeed no need to register explicitly:
import yaml
class Something(yaml.YAMLObject):
yaml_tag = u'!Something'
def __init__(self, *args, **kw):
print('some_init', args, kw)
#classmethod
def from_yaml(cls,loader,node):
# Set attributes to None if not in file
values = loader.construct_mapping(node, deep=True)
attr = ['attr1','attr2']
result = {}
for val in attr:
try:
result[val] = values[val]
except KeyError:
result[val] = None
return cls(**result)
yaml_str = """\
test: !Something
attr1: 1
attr2: 2
"""
d = yaml.load(yaml_str)
which gives:
some_init () {'attr1': 1, 'attr2': 2}
But there is no need at all to use PyYAML's load() which is
documented to be unsafe. You can just use safe_load if you set the yaml_loader class attribute:
import yaml
class Something(yaml.YAMLObject):
yaml_tag = u'!Something'
yaml_loader = yaml.SafeLoader
def __init__(self, *args, **kw):
print('some_init', args, kw)
#classmethod
def from_yaml(cls,loader,node):
# Set attributes to None if not in file
values = loader.construct_mapping(node, deep=True)
attr = ['attr1','attr2']
result = {}
for val in attr:
try:
result[val] = values[val]
except KeyError:
result[val] = None
return cls(**result)
yaml_str = """\
test: !Something
attr1: 1
attr2: 2
"""
d = yaml.safe_load(yaml_str)
as this gives the same:
some_init () {'attr1': 1, 'attr2': 2}
(done both with Python 3.6 and Python 2.7)
The registering is done in the __init__() of the metaclass of yaml.YAMLObject:
class YAMLObjectMetaclass(type):
"""
The metaclass for YAMLObject.
"""
def __init__(cls, name, bases, kwds):
super(YAMLObjectMetaclass, cls).__init__(name, bases, kwds)
if 'yaml_tag' in kwds and kwds['yaml_tag'] is not None:
cls.yaml_loader.add_constructor(cls.yaml_tag, cls.from_yaml)
cls.yaml_dumper.add_representer(cls, cls.to_yaml)
So maybe you are somehow interfering with that initialisation in your full class definition. Try to start with a minimal implementation as I did, and add the functionality on your class that you need until things break.

How to provide value validation at abstract class level?

I have an ABC BaseAbstract class with several getter/setter properties defined.
I want to require that the value to be set is an int and from 0 - 15.
#luminance.setter
#abstractproperty
#ValidateProperty(Exception, types=(int,), valid=lambda x: True if 0 <= x <= 15 else False)
def luminance(self, value):
"""
Set a value that indicate the level of light emitted from the block
:param value: (int): 0 (darkest) - 15 (brightest)
:return:
"""
pass
Can someone help me figure out what my ValidateProperty class/method should look like. I started with a class and called the accepts method but this is causing an error:
function object has no attribute 'func_code'
current source:
class ValidateProperty(object):
#staticmethod
def accepts(exception, *types, **kwargs):
def check_accepts(f, **kwargs):
assert len(types) == f.func_code.co_argcount
def new_f(*args, **kwds):
for i, v in enumerate(args):
if f.func_code.co_varnames[i] in types and\
not isinstance(v, types[f.func_code.co_varnames[i]]):
arg = f.func_code.co_varnames[i]
exp = types[f.func_code.co_varnames[i]]
raise exception("arg '{arg}'={r} does not match {exp}".format(arg=arg,
r=v,
exp=exp))
# del exp (unreachable)
for k,v in kwds.__iter__():
if k in types and not isinstance(v, types[k]):
raise exception("arg '{arg}'={r} does not match {exp}".format(arg=k,
r=v,
exp=types[k]))
return f(*args, **kwds)
new_f.func_name = f.func_name
return new_f
return check_accepts
One of us is confused about how decorators, descriptors (e.g. properties), and abstracts work -- I hope it's not me. ;)
Here is a rough working example:
from abc import ABCMeta, abstractproperty
class ValidateProperty:
def __init__(inst, exception, arg_type, valid):
# called on the #ValidateProperty(...) line
#
# save the exception to raise, the expected argument type, and
# the validator code for later use
inst.exception = exception
inst.arg_type = arg_type
inst.validator = valid
def __call__(inst, func):
# called after the def has finished, but before it is stored
#
# func is the def'd function, save it for later to be called
# after validating the argument
def check_accepts(self, value):
if not inst.validator(value):
raise inst.exception('value %s is not valid' % value)
func(self, value)
return check_accepts
class AbstractTestClass(metaclass=ABCMeta):
#abstractproperty
def luminance(self):
# abstract property
return
#luminance.setter
#ValidateProperty(Exception, int, lambda x: 0 <= x <= 15)
def luminance(self, value):
# abstract property with validator
return
class TestClass(AbstractTestClass):
# concrete class
val = 7
#property
def luminance(self):
# concrete property
return self.val
#luminance.setter
def luminance(self, value):
# concrete property setter
# call base class first to activate the validator
AbstractTestClass.__dict__['luminance'].__set__(self, value)
self.val = value
tc = TestClass()
print(tc.luminance)
tc.luminance = 10
print(tc.luminance)
tc.luminance = 25
print(tc.luminance)
Which results in:
7
10
Traceback (most recent call last):
File "abstract.py", line 47, in <module>
tc.luminance = 25
File "abstract.py", line 40, in luminance
AbstractTestClass.__dict__['luminance'].__set__(self, value)
File "abstract.py", line 14, in check_accepts
raise inst.exception('value %s is not valid' % value)
Exception: value 25 is not valid
A few points to think about:
The ValidateProperty is much simpler because a property setter only takes two parameters: self and the new_value
When using a class for a decorator, and the decorator takes arguments, then you will need __init__ to save the parameters, and __call__ to actually deal with the defd function
Calling a base class property setter is ugly, but you could hide that in a helper function
you might want to use a custom metaclass to ensure the validation code is run (which would also avoid the ugly base-class property call)
I suggested a metaclass above to eliminate the need for a direct call to the base class's abstractproperty, and here is an example of such:
from abc import ABCMeta, abstractproperty
class AbstractTestClassMeta(ABCMeta):
def __new__(metacls, cls, bases, clsdict):
# create new class
new_cls = super().__new__(metacls, cls, bases, clsdict)
# collect all base class dictionaries
base_dicts = [b.__dict__ for b in bases]
if not base_dicts:
return new_cls
# iterate through clsdict looking for properties
for name, obj in clsdict.items():
if not isinstance(obj, (property)):
continue
prop_set = getattr(obj, 'fset')
# found one, now look in bases for validation code
validators = []
for d in base_dicts:
b_obj = d.get(name)
if (
b_obj is not None and
isinstance(b_obj.fset, ValidateProperty)
):
validators.append(b_obj.fset)
if validators:
def check_validators(self, new_val):
for func in validators:
func(new_val)
prop_set(self, new_val)
new_prop = obj.setter(check_validators)
setattr(new_cls, name, new_prop)
return new_cls
This subclasses ABCMeta, and has ABCMeta do all of its work first, then does some additional processing. Namely:
go through the created class and look for properties
check the base classes to see if they have a matching abstractproperty
check the abstractproperty's fset code to see if it is an instance of ValidateProperty
if so, save it in a list of validators
if the list of validators is not empty
make a wrapper that will call each validator before calling the actual property's fset code
replace the found property with a new one that uses the wrapper as the setter code
ValidateProperty is a little different as well:
class ValidateProperty:
def __init__(self, exception, arg_type):
# called on the #ValidateProperty(...) line
#
# save the exception to raise and the expected argument type
self.exception = exception
self.arg_type = arg_type
self.validator = None
def __call__(self, func_or_value):
# on the first call, func_or_value is the function to use
# as the validator
if self.validator is None:
self.validator = func_or_value
return self
# every subsequent call will be to do the validation
if (
not isinstance(func_or_value, self.arg_type) or
not self.validator(None, func_or_value)
):
raise self.exception(
'%r is either not a type of %r or is outside '
'argument range' %
(func_or_value, type(func_or_value))
)
The base AbstractTestClass now uses the new AbstractTestClassMeta, and has the validator code directly in the abstractproperty:
class AbstractTestClass(metaclass=AbstractTestClassMeta):
#abstractproperty
def luminance(self):
# abstract property
pass
#luminance.setter
#ValidateProperty(Exception, int)
def luminance(self, value):
# abstract property validator
return 0 <= value <= 15
The final class is the same:
class TestClass(AbstractTestClass):
# concrete class
val = 7
#property
def luminance(self):
# concrete property
return self.val
#luminance.setter
def luminance(self, value):
# concrete property setter
# call base class first to activate the validator
# AbstractTestClass.__dict__['luminance'].__set__(self, value)
self.val = value

Resources