Monday, June 5, 2017

Re: Authentication backend - serialization

On Monday 05 June 2017 04:14:01 Alison P wrote:

 

> If I use the default session serializer, I get the following error:

> TypeError at /login/

>

> <class 'OneTimePasswordBackend'> is not JSON serializable

 

I'm using this is a general solution. Feel free to strike what you don't need.

The basics is that a JSONEncoder knows primitives and how to recurse containers. The rest is passed to a method "default", which generates the above error message. Subclasses should override it to implement their own knowledge about objects to serialize.

 

class JSONModelEncoder(DjangoJSONEncoder):
exclude_callback = None
recurse_foreign_keys = True

def
get_excluded_fields(self, instance):
if self.exclude_callback is not None:
return self.exclude_callback(instance)
else:
return []

def recurse_foreign(self, instance, field) -> dict:
retval = {}
if isinstance(field, models.OneToOneField):
# OneToOneField is a subclass of ForeignKey, thus must be placed
# before ForeignKey.
# If parent_link is true, then we need to pull in the fields
# as if they were part of the current model.
if not field.parent_link:
parent_obj = getattr(instance, field.name)
if parent_obj is not None:
value = self.model_to_dict(
parent_obj,
exclude=self.get_excluded_fields(parent_obj)
)
retval.update(value)
elif isinstance(field, models.ForeignKey):
# Resolve the model pointed to.
foreign = getattr(instance, field.name)
if foreign is not None:
value = self.model_to_dict(foreign)
retval[field.name] = value
else:
retval[field.name] = None
elif
isinstance(field, models.ManyToManyField):
# Create a list of model dicts.
modlist = []
related = getattr(instance, field.name)
for rel in related:
modlist.append(self.model_to_dict(rel))

retval[field.name] = modlist
else:
raise TypeError('recurse_foreign called on {}'.format(type(field)))

return retval

def link_foreign(self, instance, field) -> dict:
if isinstance(field, models.ManyToManyField):
return self.recurse_foreign(instance, field)
elif isinstance(field, models.OneToOneField):
if not field.parent_link:
return self.recurse_foreign(instance, field)
elif isinstance(field, models.ForeignKey):
foreign = getattr(instance, field.name)
# raise ValueError(repr(foreign))
if foreign is not None:
if getattr(foreign, 'absolute_url', False):
return {
field.name: {
'text': force_text(foreign),
'link': foreign.absolute_url,
}
}
else:
return {
field.name: {
'text': force_text(foreign),
'pk': foreign.pk
}
}

return {}

def model_to_dict(self, instance, exclude=None, **kwargs) -> dict:
"""Convert a model instance to a dictionary of field names and values.

If the model has a method of the same name, that method is called for
three reasons:
#. Centralization. This method can be used in other parts of django
or an application to provide a consistent dictionary of the
model.
#. The default implementation only maps fields. If the model has
important attributes that are implemented as properties this
mixin will not find them.
#. Hiding of sensitive fields. The model is better equipped to
evaluate if a field contains sensitive information.

:param instance: the model instance to convert
:type instance: models.Model
:param exclude: list of fields to exclude from being sent
:type exclude: list
"""
exclude = exclude or self.get_excluded_fields(instance)
if hasattr(instance, 'model_to_dict') and \
callable(getattr(instance, 'model_to_dict')):
return instance.model_to_dict(exclude, **kwargs)
retval = {}
for field in instance._meta.fields:
if field.name in exclude:
continue
if
isinstance(field, (models.ForeignKey, models.ManyToManyField)):
if self.recurse_foreign_keys:
retval.update(self.recurse_foreign(instance, field))
else:
merge = self.link_foreign(instance, field)
if merge:
retval.update(merge)
else:
retval[field.name] = getattr(instance, field.name)

return retval

def default(self, obj) -> object:
"""Prepares a value for json representation.

Complex types that are not containers should be handled by this method.
:param obj:
:type obj: object
"""
if isinstance(obj, models.query.QuerySet):
return list(obj)
elif hasattr(obj, 'model_to_dict') and \
callable(getattr(obj, 'model_to_dict')):
return obj.model_to_dict(exclude=self.get_excluded_fields(obj))
elif isinstance(obj, models.Model):
return self.model_to_dict(obj)
else:
return super().default(obj)

--

Melvyn Sopacua

No comments:

Post a Comment