Code Monkey home page Code Monkey logo

drf-dynamic-fields's Introduction

Dynamic Serializer Fields for Django REST Framework

Build status PyPI Version PyPI Downloads License is MIT

This package provides a mixin that allows the user to dynamically select only a subset of fields per resource.

Official version support:

  • Django 2.2 LTS, 3.2 LTS, 4.0
  • Supported REST Framework versions: 3.8, 3.9
  • Python 3.7+

Scope

This library is about filtering fields based on individual requests. It is deliberately kept simple and we do not plan to add new features (including support for nested fields). Feel free to contribute improvements, code simplifications and bugfixes though! (See also: #18)

If you need more advanced filtering features, maybe drf-flex-fields could be something for you.

Installing

pip install drf-dynamic-fields

What It Does

Example serializer:

class IdentitySerializer(DynamicFieldsMixin, serializers.HyperlinkedModelSerializer):
    class Meta:
        model = models.Identity
        fields = ('id', 'url', 'type', 'data')

A regular request returns all fields:

GET /identities

[
  {
    "id": 1,
    "url": "http://localhost:8000/api/identities/1/",
    "type": 5,
    "data": "John Doe"
  },
  ...
]

A query with the fields parameter on the other hand returns only a subset of the fields:

GET /identities/?fields=id,data

[
  {
    "id": 1,
    "data": "John Doe"
  },
  ...
]

And a query with the omit parameter excludes specified fields.

GET /identities/?omit=data

[
  {
    "id": 1,
    "url": "http://localhost:8000/api/identities/1/",
    "type": 5
  },
  ...
]

You can use both fields and omit in the same request!

GET /identities/?omit=data,fields=data,id

[
  {
    "id": 1
  },
  ...
]

Though why you would want to do something like that is beyond this author.

It also works on single objects!

GET /identities/1/?fields=id,data

{
  "id": 1,
  "data": "John Doe"
}

Usage

When defining a serializer, use the DynamicFieldsMixin:

from drf_dynamic_fields import DynamicFieldsMixin

class IdentitySerializer(DynamicFieldsMixin, serializers.ModelSerializer):
    class Meta:
        model = models.Identity
        fields = ('id', 'url', 'type', 'data')

The mixin needs access to the request object. Some DRF classes like the ModelViewSet set that by default, but if you handle serializers yourself, pass in the request through the context:

events = Event.objects.all()
serializer = EventSerializer(events, many=True, context={'request': request})

Warnings

If the request context does not have access to the request, a warning is emitted:

UserWarning: Context does not have access to request.

First, make sure that you are passing the request to the serializer context (see "Usage" section).

There are some cases (e.g. nested serializers) where you cannot get rid of the warning that way (see issue 27). In that case, you can silence the warning through settings.py:

DRF_DYNAMIC_FIELDS = {
   'SUPPRESS_CONTEXT_WARNING': True,
}

Testing

To run tests, install Django and DRF and then run runtests.py:

$ python runtests.py

Credits

  • The implementation is based on this StackOverflow answer. Thanks YAtOff!
  • The GitHub users X17 and rawbeans provided improvements on my gist that were incorporated into this library. Thanks!
  • For other contributors, please see Github contributor stats.

License

MIT license, see LICENSE file.

drf-dynamic-fields's People

Contributors

dbrgn avatar jtrain avatar michael-k avatar racum avatar sbchisholm avatar woakas avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

drf-dynamic-fields's Issues

Subclassable get_* filter and omit params

Thanks for writing and maintaining this incredibly useful library. I recently ran into a situation where I need to be able to control my serialization dynamically outside of just using URL params. One suggestion that I had for improving this library would be to encapsulate the acquisition of your include and omit lists into it's own method (or set of methods) so that it can be overridden by subclassing (if, perhaps, you wanted to use a different url parameter or even a whole different source, such as the serializer context). That's what I did in my current project.

Cheers!

Add tests

It's a bit tricky because Django is required.

Add support for concrete nested serializers

This is a proposal to add support for editing the fields of nested serializers not on a per-request basis, but at instantiation time.

Here is an example. There is a serializer for user called UserSerializer it has rather a lot of fields

class UserSerializer(...):
 class Meta:
  fields = ('id', 'url', 'name', 'email', 'accounts', 'friends', 'most_recent_activity')

The UserSerializer is nested inside another serializer

class MessageSerializer(...):
 from_users = UserSerializer(many=True, read_only=True)
 class Meta:
  fields = ('id', 'url', 'from_users', 'to_users', 'account', 'created_at', 'modified_at')

But we only want a few bits of info for each user in the MessageSerializer, just name, email, id and url. Just enough context to help the front end render without relying on a user lookup.

This proposal is made to solve that situation:

class MessageSerializer(...):
 from_users = UserSerializer(many=True, read_only=True, fields=('id', 'url', 'name', 'email'))

I have already coded something like this up, and can I can see how there is some overlap with this project. Enough to justify putting it in, I think. Unfortunately it doesn't directly support the purpose of this project, which is dynamic per-request fields. This is more like dynamic fields at runtime.

PyPI v 0.2.0 not up to date

Hey folks. Thanks for the mixin. Definitely made my life easier.

Unfortunately v 0.2.0 is not up to date on PyPI. Specifically the merge preventing the filter from being applied to nested serializers is not present.

Thanks again and cheers.

adding a field with the omitted fields

hi
It could be awesome If I could get a new field on the serialzier listing all the fields that were omitted.

The usecase of that, is when my client gets a response he stores the response is a cache,

When he uses the cache he needs to know if the object didn't have the field or if it is omitted,
if it is omitted they will try to get the omitted fields from my service

WDYT?

Thanks, Guy

Access to context fails inside nested serializer

As discussed in #5 a DynamicFieldMixin-ed serializer that accesses context will fail when used as a nested serializer.

I found this issue when I was re-using a serializer as a nested serializer, and that serializer relies on context['request'] to build a url for one of its fields.

Without fixing drf-dynamic-fields to allow nested serializers, there are a number of work arounds:

  1. You could just have two separate serializers, one for nesting and one for normal views.
  2. The nested serializer can manually find the correct context by chasing the parent fields up the tree until it finds the one with context set.

I used 2) for a while before deciding it was better to fix the underlying issue.

Fails for recursive fields.

This is my serializer:

class CategoryRecursiveSerializer(DynamicFieldsMixin, serializers.ModelSerializer):
    children = serializers.ListField(read_only=True, source='get_children', child=RecursiveField())
    value = serializers.CharField(source='name', read_only=True)  # hack requested by frontend
    parent = fields.CategoryField(queryset=models.Category.objects.all(),
                                             required=False, allow_null=True)

This is my request:

http://api.mlocal.com/api/v1/categories/?fields=id,value,children

This is my response:

{
  "count": 20,
  "next": null,
  "previous": null,
  "results": [
    {
      "id": "5914220d-c455-45ff-a135-e9ac7f307dae",
      "value": "Fruits & Vegetables",
      "children": [
        {
          "id": "7426afb3-b2b6-4f74-8a25-6e5e5865fb4f",
          "name": "Fresh Vegetables",
          "value": "Fresh Vegetables",
          "image": null,
          "is_featured": false,
          "children": [],
          "parent": {
            "id": "5914220d-c455-45ff-a135-e9ac7f307dae",
            "name": "Fruits & Vegetables",
            "image": "http://api.mlocal.com/categories/35629/fruits-vegetables-41698-1523944087.png",
            "is_featured": true,
            "parent": null,
            "created": "2018-07-17T09:27:42.920241Z"
          },
          "created": "2018-07-17T09:28:32.731214Z"
        }
      ],
      "image": "http://api.mlocal.com/categories/35629/fruits-vegetables-41698-1523944087.png"
    }
  ]
}

The parent field comes in nested children, except for the root objects in which only the fields that I mentioned come.

RFC: Library maintenance

I'm currently lacking the time to properly maintain this library. There are currently two open pull requests that I would like to review, but for which I can't really find time and motivation to do so.

This library has started out as a very simple mixin but has grown in scope and is currently doing more than I personally need. More features also mean more complexity in maintenance and PR review.

There is currently another crate doing similar things, https://github.com/rsinger86/drf-flex-fields. The scope seems similar but drf-flex-fields seems to be more powerful regarding the filters. I'm not totally sure how the feature level compares.

Having two libraries doing simliar things is a waste of time, so I could imagine three options:

  • Adding more maintainers to this crate
  • Removing all advanced filtering to simplify the crate ("back to the roots") and send people to drf-flex-fields for the advanced stuff
  • Deprecating the crate in favor of drf-flex-fields

I'm not sure whether drf-flex-fields would cover all use cases. What do users of drf-dynamic-fields think?

CCing all people somehow involved:

@jtrain
@cb109
@ryanrich
@rsinger86

Filter fields in embedded document

I think this is a feature request but is there a way to filter fields from embedded documents? Think of a base document (in mongodb) that looks like this in JSON:

[{
  "_id":  12,
  "name": "test 1",
  "embdded_doc": {
     "_id": 1237652,
     "other_info": "this and that",
     "color": "red"
  } 
}]

and you only want to output: _id, name, embedded_doc.color

We are trying to avoid having to serialize the entire embedded doc since it could a be large memory footprint, especially when we are returning a list of the parent objects.

Thanks

Get rid of warning

I have a use case where I need to dynamically determine the nested serializer like this:

    def get_config(self, tracker: Tracker):
        config = tracker.get_config()
        if config is not None:
            serializer = TRACKER_CONFIG_SERIALIZERS.get(tracker.model)
            if serializer is None:
                logger.warning('Could not find serializer for model %s', tracker.model)
                return None
            return serializer(config).data
        return None

This works but results in a warning, because the nested serializer does not have access to the request in the context.

If we pass in the context...

            return serializer(config, context=self.context).data

...then the warning is gone, but the filtering is applied to those fields too.

The problem is that drf-dynamic-fields cannot know that this serializer is a nested serializer because it's used like a root serializer. And I'm afraid of playing some tricks with parent/child attributes, since that can go wrong easily.

I see two approaches to solve this:

  • Remove the warning (no filtering if there's no request in the context)
  • Add a special field to the context (e.g. dynamic_fields_ignore) that is queried in order to ignore the filtering on this serializer.

I'm undecided. What do you think @jtrain?

field selection not working with SerializerMethodField

class ServiceSerializer(DynamicFieldsMixin, serializers.ModelSerializer):

    stretch = serializers.SerializerMethodField()

    class Meta:
        model = Service
        fields = ['id', 'stretch', 'name']

    def get_stretch(self, obj):
         ...

I can't skip the stretch field or select the others, it always shows me all the fields, is there any way to do it?

"Fields" param in settings

Hello,

An idea that would be nice: the ability to set the GET param names in settings. Something like the default:

DRF_DYNAMIC_FIELDS_GET_NAMES = ['fields', 'omit]

Which could be overriden in the local settings with something like:

DRF_DYNAMIC_FIELDS_GET_NAMES = ['__fields', '__omit]

So that the risk of the GET param colliding with model fields named "fields" and "omit" is minimised when using drf_dynamic_fields alongside https://github.com/carltongibson/django-filter, for example.

Thanks!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.