From 2eec430836179114becab94a2c94b3f1ea701d8b Mon Sep 17 00:00:00 2001 From: Drew Shafer Date: Mon, 8 Jun 2020 19:27:55 -0500 Subject: [PATCH] add inst_field_name to expandable --- .bumpversion.cfg | 2 +- CHANGELOG.md | 4 +- .../machinery/filtering.py | 26 ++++++++---- setup.py | 2 +- tests/filters.py | 28 +++++++++++++ tests/test_filters.py | 42 +++++++++++++++++++ 6 files changed, 92 insertions(+), 12 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 4897581..d1d8e6e 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.20.0 +current_version = 0.21.0 [bumpversion:file:setup.py] diff --git a/CHANGELOG.md b/CHANGELOG.md index 442e55b..457a89d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -# [Unrelased] +# [0.21.0] +### Added +- [PR 42](/~https://github.com/salesforce/django-declarative-apis/pull/42) Add inst_field_name to expandable fields ### Changed - [PR 39](/~https://github.com/salesforce/django-declarative-apis/pull/39) Add long_description to setup - [PR 40](/~https://github.com/salesforce/django-declarative-apis/pull/40) Remove use of root logger diff --git a/django_declarative_apis/machinery/filtering.py b/django_declarative_apis/machinery/filtering.py index 707a7ac..06b8075 100644 --- a/django_declarative_apis/machinery/filtering.py +++ b/django_declarative_apis/machinery/filtering.py @@ -21,12 +21,13 @@ class _ExpandableForeignKey: - def __init__(self, display_key, model_class): + def __init__(self, display_key, model_class, inst_field_name): self.display_key = display_key self.model_class = model_class + self.inst_field_name = inst_field_name -def expandable(model_class=None, display_key=None): +def expandable(model_class=None, display_key=None, inst_field_name=None): if model_class and not issubclass(model_class, (models.Model,)): raise ValueError("model_class must be an instance of a Django Model") if model_class and display_key: @@ -34,7 +35,7 @@ def expandable(model_class=None, display_key=None): model_class._meta.get_field(display_key) except models.FieldDoesNotExist as e: # noqa raise ValueError(f"{display_key} is not a field on {model_class.__name__}") - return _ExpandableForeignKey(display_key, model_class) + return _ExpandableForeignKey(display_key, model_class, inst_field_name) def _get_unexpanded_field_value(inst, field_name, field_type): @@ -42,19 +43,22 @@ def _get_unexpanded_field_value(inst, field_name, field_type): return DEFAULT_UNEXPANDED_VALUE display_key = field_type.display_key or field_type.model_class._meta.pk.name - is_multiple = isinstance(inst.__class__._meta.get_field(field_name), ManyToOneRel) + inst_field_name = field_type.inst_field_name or field_name + is_multiple = isinstance( + inst.__class__._meta.get_field(inst_field_name), ManyToOneRel + ) if is_multiple: # special case for keys that have multiple values (for instance, inverse fk relations) - obj = getattr(inst, field_name) + obj = getattr(inst, inst_field_name) return [{display_key: getattr(v, display_key)} for v in obj.all()] if display_key == field_type.model_class._meta.pk.name: # special case - we know this is a primary key, so we can get it without retrieving the object - return {display_key: getattr(inst, field_name + "_id")} + return {display_key: getattr(inst, inst_field_name + "_id")} else: # we're not returning the PK - have to actually retrieve the model - obj = getattr(inst, field_name) + obj = getattr(inst, inst_field_name) return {display_key: getattr(obj, display_key)} @@ -67,8 +71,12 @@ def _get_filtered_field_value( if isinstance(field_type, types.FunctionType): val = field_type(inst) - elif isinstance(field_type, _ExpandableForeignKey) and not expand_this: - val = _get_unexpanded_field_value(inst, field_name, field_type) + elif isinstance(field_type, _ExpandableForeignKey): + if expand_this: + inst_field_name = field_type.inst_field_name or field_name + val = getattr(inst, inst_field_name) + else: + val = _get_unexpanded_field_value(inst, field_name, field_type) else: try: val = getattr(inst, field_name) diff --git a/setup.py b/setup.py index 5686181..5c4632c 100644 --- a/setup.py +++ b/setup.py @@ -20,7 +20,7 @@ setuptools.setup( name="django-declarative-apis", - version="0.20.0", # set by bumpversion + version="0.21.0", # set by bumpversion author="Drew Shafer", url="https://salesforce.com", description="Simple, readable, declarative APIs for Django", diff --git a/tests/filters.py b/tests/filters.py index 380ec6d..3024ec2 100644 --- a/tests/filters.py +++ b/tests/filters.py @@ -38,6 +38,34 @@ }, } +RENAMED_EXPANDABLE_MODEL_FIELDS = { + str: ALWAYS, + int: ALWAYS, + dict: ALWAYS, + models.TestModel: { + "pk": ALWAYS, + "int_field": ALWAYS, + "renamed_expandable_dict": expandable(inst_field_name="expandable_dict"), + "renamed_expandable_string": expandable(inst_field_name="expandable_string"), + }, + models.ParentModel: { + "nonstandard_id": ALWAYS, + "name": ALWAYS, + "favorite": expandable(model_class=models.ChildModel, display_key="name"), + "children": expandable(model_class=models.ChildModel, display_key="name"), + }, + models.ChildModel: { + "pk": ALWAYS, + "renamed_test": expandable( + model_class=models.TestModel, inst_field_name="test" + ), + "name": ALWAYS, + "renamed_parent": expandable( + model_class=models.ParentModel, inst_field_name="parent" + ), + }, +} + DEFAULT_FILTERS_NO_EXPANDABLE = { str: ALWAYS, int: ALWAYS, diff --git a/tests/test_filters.py b/tests/test_filters.py index 508a714..2472840 100644 --- a/tests/test_filters.py +++ b/tests/test_filters.py @@ -180,3 +180,45 @@ def test_expandable_absent_if_no_expandable_fields(self): self.assertEqual( filtered["expandable_string"], models.TestModel.EXPANDABLE_STRING_RETURN ) + + def test_rename_expandable_foreign_key(self): + filtered = filtering.apply_filters_to_object( + self.p1c1, filters.RENAMED_EXPANDABLE_MODEL_FIELDS + ) + + self.assertEqual(filtered["renamed_test"], {"id": self.p1c1.test.pk}) + self.assertEqual( + filtered["renamed_parent"], {"nonstandard_id": self.p1c1.parent.pk} + ) + + filtered = filtering.apply_filters_to_object( + self.p1c1, + filters.RENAMED_EXPANDABLE_MODEL_FIELDS, + expand_header="renamed_test,renamed_parent", + ) + self.assertTrue(2, len(filtered["renamed_test"])) + self.assertIn("int_field", filtered["renamed_test"]) + self.assertNotIn("renamed_expandable_dict", filtered["renamed_test"]) + self.assertTrue(4, len(filtered["renamed_parent"])) + + filtered = filtering.apply_filters_to_object( + self.p1c1, + filters.RENAMED_EXPANDABLE_MODEL_FIELDS, + expand_header="renamed_test,renamed_test.renamed_expandable_dict", + ) + self.assertTrue(3, len(filtered["renamed_test"])) + self.assertIn("renamed_expandable_dict", filtered["renamed_test"]) + self.assertEqual( + self.test_model.expandable_dict, + filtered["renamed_test"]["renamed_expandable_dict"], + ) + self.assertNotIn("renamed_expandable_string", filtered["renamed_test"]) + + filtered = filtering.apply_filters_to_object( + self.p1c1, + filters.RENAMED_EXPANDABLE_MODEL_FIELDS, + expand_header="renamed_test,renamed_test.renamed_expandable_dict,renamed_test.renamed_expandable_string", + ) + self.assertTrue(4, len(filtered["renamed_test"])) + self.assertIn("renamed_expandable_dict", filtered["renamed_test"]) + self.assertIn("renamed_expandable_string", filtered["renamed_test"])