-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
(hotfix): resolve incorrect and inconsistent module naming
- see #2 - in Python-land, packages are _not_ imported by their package name, but their _module_ name - see also https://stackoverflow.com/a/54599368/3431180 - and the module name of this package was `serializable`, _not_ `django-serializable-model` - also in Python-land, one cannot use the standard `import` syntax for hyphenated modules, it will actually cause a SyntaxError - but for some reason it's not illegal to do so, and is allowed both in `setuptools` naming and PyPI naming (it is discouraged by PEP8) - rename serializable.py to django_serializable_model.py - leave serializable.py and just make it a wrapper / alias of django_serialiazable_model so as not to cause a breaking change (pkg): publish `django_serializable_model` module as well - don't remove `serializable` though so as to not cause a breaking change; should remove it in v1.0.0 (docs): use `django_serializable_model` in Usage docs
- Loading branch information
Showing
4 changed files
with
149 additions
and
134 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
import django | ||
from django.db import models | ||
from django.core.exceptions import ObjectDoesNotExist | ||
|
||
|
||
class _SerializableQuerySet(models.query.QuerySet): | ||
"""Implements the serialize method on a QuerySet""" | ||
def serialize(self, *args): | ||
serialized = [] | ||
for elem in self: | ||
serialized.append(elem.serialize(*args)) | ||
return serialized | ||
|
||
|
||
class SerializableManager(models.Manager): | ||
"""Implements table-level serialization via SerializableQuerySet""" | ||
# backward compatibility for Django < 1.10 | ||
if django.VERSION < (1, 10): | ||
# when queried from a related Model, use this Manager | ||
use_for_related_fields = True | ||
|
||
def get_queryset(self): | ||
return _SerializableQuerySet(self.model) | ||
|
||
# backward compatibility for Django < 1.6 | ||
if django.VERSION < (1, 6): | ||
get_query_set = get_queryset | ||
|
||
def get_queryset_compat(self): | ||
get_queryset = (self.get_query_set | ||
if hasattr(self, 'get_query_set') | ||
else self.get_queryset) | ||
return get_queryset() | ||
|
||
# implement serialize on the Manager itself (on .objects, before .all()) | ||
def serialize(self, *args): | ||
return self.get_queryset_compat().serialize(*args) | ||
|
||
|
||
class SerializableModel(models.Model): | ||
""" | ||
Abstract Model that implements recursive serializability of models to | ||
dictionaries, both at the row and table level, with some overriding allowed | ||
""" | ||
objects = SerializableManager() | ||
|
||
class Meta: | ||
abstract = True | ||
# when queried from a related Model, use this Manager | ||
base_manager_name = 'SerializableManager' | ||
|
||
def serialize(self, *args, **kwargs): | ||
""" | ||
Serializes the Model object with model_to_dict_custom and kwargs, and | ||
proceeds to recursively serialize related objects as requested in args | ||
""" | ||
serialized = model_to_dict_custom(self, **kwargs) | ||
args = list(args) # convert tuple to list | ||
|
||
# iterate and recurse through all arguments | ||
index = 0 | ||
length = len(args) | ||
while index < length: | ||
# split the current element | ||
field_with_joins = args[index] | ||
field, join = _split_joins(field_with_joins) | ||
all_joins = [join] if join else [] # empty string to empty array | ||
|
||
# delete it from the list | ||
del args[index] | ||
length -= 1 | ||
|
||
# get all joins for this field from the arguments | ||
arg_joins = [_split_joins(arg, only_join=True) | ||
for arg in args if arg.startswith(field)] | ||
all_joins += arg_joins # combine all joins on this field | ||
|
||
# recurse if related object actually exists | ||
try: | ||
serialized[field] = getattr(self, field).serialize(*all_joins) | ||
except (AttributeError, ObjectDoesNotExist): | ||
pass | ||
|
||
# shrink length and remove all args that were recursed over | ||
length -= len(arg_joins) | ||
args = [arg for arg in args if not arg.startswith(field)] | ||
|
||
return serialized | ||
|
||
|
||
def model_to_dict_custom(instance, fields=None, exclude=None, editable=True): | ||
""" | ||
Custom model_to_dict function that differs by including all uneditable | ||
fields and excluding all M2M fields by default | ||
Also sets all ForeignKey fields to name + _id, similar to .values() | ||
""" | ||
# avoid circular import | ||
from django.db.models.fields.related import ForeignKey | ||
opts = instance._meta | ||
data = {} | ||
for f in opts.fields: | ||
# skip uneditable fields if editable kwarg is False | ||
if not editable and not f.ediable: | ||
continue | ||
# whitelisted fields only if fields kwarg is passed | ||
if fields and f.name not in fields: | ||
continue | ||
# blacklist fields from exclude kwarg | ||
if exclude and f.name in exclude: | ||
continue | ||
else: | ||
if isinstance(f, ForeignKey): | ||
data[f.name + '_id'] = f.value_from_object(instance) | ||
else: | ||
data[f.name] = f.value_from_object(instance) | ||
return data | ||
|
||
|
||
def _split_joins(join_string, only_join=False): | ||
""" | ||
Split a string into the field and it's joins, separated by __ as per | ||
Django convention | ||
""" | ||
split = join_string.split('__') | ||
field = split.pop(0) # the first field | ||
join = '__'.join(split) # the rest of the fields | ||
|
||
# return single join or tuple based on kwarg | ||
if only_join: | ||
return join | ||
return field, join |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,131 +1,10 @@ | ||
import django | ||
from django.db import models | ||
from django.core.exceptions import ObjectDoesNotExist | ||
""" | ||
This is just a pure wrapper / alias module around django_serializable_model to | ||
still be able to import it with the original, unintended name of serializable. | ||
See https://github.com/agilgur5/django-serializable-model/issues/2 | ||
In the first major/breaking release, v1.0.0, this file should be deleted and | ||
the module removed from `setup.py`. | ||
""" | ||
|
||
class _SerializableQuerySet(models.query.QuerySet): | ||
"""Implements the serialize method on a QuerySet""" | ||
def serialize(self, *args): | ||
serialized = [] | ||
for elem in self: | ||
serialized.append(elem.serialize(*args)) | ||
return serialized | ||
|
||
|
||
class SerializableManager(models.Manager): | ||
"""Implements table-level serialization via SerializableQuerySet""" | ||
# backward compatibility for Django < 1.10 | ||
if django.VERSION < (1, 10): | ||
# when queried from a related Model, use this Manager | ||
use_for_related_fields = True | ||
|
||
def get_queryset(self): | ||
return _SerializableQuerySet(self.model) | ||
|
||
# backward compatibility for Django < 1.6 | ||
if django.VERSION < (1, 6): | ||
get_query_set = get_queryset | ||
|
||
def get_queryset_compat(self): | ||
get_queryset = (self.get_query_set | ||
if hasattr(self, 'get_query_set') | ||
else self.get_queryset) | ||
return get_queryset() | ||
|
||
# implement serialize on the Manager itself (on .objects, before .all()) | ||
def serialize(self, *args): | ||
return self.get_queryset_compat().serialize(*args) | ||
|
||
|
||
class SerializableModel(models.Model): | ||
""" | ||
Abstract Model that implements recursive serializability of models to | ||
dictionaries, both at the row and table level, with some overriding allowed | ||
""" | ||
objects = SerializableManager() | ||
|
||
class Meta: | ||
abstract = True | ||
# when queried from a related Model, use this Manager | ||
base_manager_name = 'SerializableManager' | ||
|
||
def serialize(self, *args, **kwargs): | ||
""" | ||
Serializes the Model object with model_to_dict_custom and kwargs, and | ||
proceeds to recursively serialize related objects as requested in args | ||
""" | ||
serialized = model_to_dict_custom(self, **kwargs) | ||
args = list(args) # convert tuple to list | ||
|
||
# iterate and recurse through all arguments | ||
index = 0 | ||
length = len(args) | ||
while index < length: | ||
# split the current element | ||
field_with_joins = args[index] | ||
field, join = _split_joins(field_with_joins) | ||
all_joins = [join] if join else [] # empty string to empty array | ||
|
||
# delete it from the list | ||
del args[index] | ||
length -= 1 | ||
|
||
# get all joins for this field from the arguments | ||
arg_joins = [_split_joins(arg, only_join=True) | ||
for arg in args if arg.startswith(field)] | ||
all_joins += arg_joins # combine all joins on this field | ||
|
||
# recurse if related object actually exists | ||
try: | ||
serialized[field] = getattr(self, field).serialize(*all_joins) | ||
except (AttributeError, ObjectDoesNotExist): | ||
pass | ||
|
||
# shrink length and remove all args that were recursed over | ||
length -= len(arg_joins) | ||
args = [arg for arg in args if not arg.startswith(field)] | ||
|
||
return serialized | ||
|
||
|
||
def model_to_dict_custom(instance, fields=None, exclude=None, editable=True): | ||
""" | ||
Custom model_to_dict function that differs by including all uneditable | ||
fields and excluding all M2M fields by default | ||
Also sets all ForeignKey fields to name + _id, similar to .values() | ||
""" | ||
# avoid circular import | ||
from django.db.models.fields.related import ForeignKey | ||
opts = instance._meta | ||
data = {} | ||
for f in opts.fields: | ||
# skip uneditable fields if editable kwarg is False | ||
if not editable and not f.ediable: | ||
continue | ||
# whitelisted fields only if fields kwarg is passed | ||
if fields and f.name not in fields: | ||
continue | ||
# blacklist fields from exclude kwarg | ||
if exclude and f.name in exclude: | ||
continue | ||
else: | ||
if isinstance(f, ForeignKey): | ||
data[f.name + '_id'] = f.value_from_object(instance) | ||
else: | ||
data[f.name] = f.value_from_object(instance) | ||
return data | ||
|
||
|
||
def _split_joins(join_string, only_join=False): | ||
""" | ||
Split a string into the field and it's joins, separated by __ as per | ||
Django convention | ||
""" | ||
split = join_string.split('__') | ||
field = split.pop(0) # the first field | ||
join = '__'.join(split) # the rest of the fields | ||
|
||
# return single join or tuple based on kwarg | ||
if only_join: | ||
return join | ||
return field, join | ||
from django_serializable_model import * # noqa F403, F401 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters