rsinger86 / drf-flex-fields Goto Github PK
View Code? Open in Web Editor NEWDynamically set fields and expand nested resources in Django REST Framework serializers.
License: MIT License
Dynamically set fields and expand nested resources in Django REST Framework serializers.
License: MIT License
doesn't consider "country" to be expanded if passed value is "country.state"
Hello and hope you're well!
I wanted to raise a discussion on how deferred fields are currently defined and whether a less verbose approach could be supported?
At the moment my understanding is that expandable fields explicitly need to have their serializer (or field type) defined. This is fine for "true" expands (that warrant a separate serializer) but becomes unnecessarily verbose for fields on the same model - those only defined in fields
and the ModelSerializer
infers the actual field types at runtime from the model.
Given this serializer:
class MySerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = ("id", "name", "description", "etc")
Let's say I wanted to have description
and etc
deferred - not rendered by default unless requested, currently I'd have to do this:
class MySerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = ("id", "name")
expandable_fields = {"description" serializers.CharField, "etc": serializers.CharField}
This requires explicitly listing out field classes for every field, a pretty tedious process.
In the codebase I'm currently working on we worked around this as follows:
class CustomFlexFieldsSerializerMixin(FlexFieldsSerializerMixin):
"""
Overriding the FlexFieldsSerializerMixin to enable declaring of "default_fields"
in the Serializer.Meta.
This is a list of fields to be shown if no "fields" parameter is present.
class Meta:
default_fields = ["id", "name"]
"""
def __init__(self, *args, **kwargs):
"""Set fields from Meta.default_fields if not provided in the parameters"""
if (
kwargs.get("context")
and not kwargs["context"]["request"].query_params.getlist(FIELDS_PARAM)
and not kwargs["context"]["request"].query_params.getlist(OMIT_PARAM)
):
super().__init__(*args, **kwargs, fields=self._default_fields)
else:
super().__init__(*args, **kwargs)
@property
def _default_fields(self) -> dict:
if hasattr(self, "Meta") and hasattr(self.Meta, "default_fields"):
return self.Meta.default_fields
return {}
Essentially the above approach sets the fields
argument to Meta.default_fields
(unless it's explicitly set within the context from the originating request) as if they were explicitly requested via the query string - this allows you to have deferrable fields with minimal changes to the serializer - just set default_fields
and you're good to go.
We had a TODO in there to upstream this so I wanted to raise this discussion to see if there's a way we can merge our approaches so our custom override above is no longer required.
Hello, I have a few large SerializerMethodFields
on my serializer, which return iterables that are not a FK to my model. Something like this:
Class FooSerializer(FlexFieldsModelSerializer):
things = SerializerMethodField()
def get_things(self, instance):
return ThingSerializer(get_things_related_to_this_instance(instance), many=True).data
As far as I know I cannot directly leverage drf-flex-fields
to make this field expandable, because expandable_fields
are a statically defined dict that takes a serializer class or tuple.
I have hacked around this by making these fields omitted by default, unless declared in the expand
argument:
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
expandable_method_fields = ('things', 'more_things')
if '~all' in self._flex_options['expand'] or '*' in self._flex_options['expand']:
return
for field in expandable_method_fields:
if (
field not in self._flex_options['expand']
and field not in self._flex_options['omit']
):
self._flex_options['omit'].append(field)
It works well enough for my purposes, but I thought I'd pitch this as a feature request, in case it is helpful or others have solved the problem differently. Cheers, and thanks for the library!
Thanks for this great library, I really love this. I have come across an issue however, when I wanted to use rest_flex_fields.filter_backends.FlexFieldsFilterBackend
. When I added it to the configuration of DRF, the following import error occured:
Traceback (most recent call last):
File "manage.py", line 22, in <module>
main()
File "manage.py", line 18, in main
execute_from_command_line(sys.argv)
File "d:\workspace\my_project\api\.env\lib\site-packages\django\core\management\__init__.py", line 401, in execute_from_command_line
utility.execute()
File "d:\workspace\my_project\api\.env\lib\site-packages\django\core\management\__init__.py", line 377, in execute
django.setup()
File "d:\workspace\my_project\api\.env\lib\site-packages\django\__init__.py", line 24, in setup
apps.populate(settings.INSTALLED_APPS)
File "d:\workspace\my_project\api\.env\lib\site-packages\django\apps\registry.py", line 114, in populate
app_config.import_models()
File "d:\workspace\my_project\api\.env\lib\site-packages\django\apps\config.py", line 211, in import_models
self.models_module = import_module(models_module_name)
File "d:\workspace\my_project\api\.env\lib\importlib\__init__.py", line 127, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
File "<frozen importlib._bootstrap>", line 1006, in _gcd_import
File "<frozen importlib._bootstrap>", line 983, in _find_and_load
File "<frozen importlib._bootstrap>", line 967, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 677, in _load_unlocked
File "<frozen importlib._bootstrap_external>", line 728, in exec_module
File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
File "d:\workspace\my_project\api\my_app\models.py", line 9, in <module>
from utils.mixins import ResultsModelMixin, TrackFieldChangesMixin
File "d:\workspace\my_project\api\utils\mixins\__init__.py", line 4, in <module>
from .results import ResultsModelMixin, ResultsListViewMixin
File "d:\workspace\my_project\api\utils\mixins\results\__init__.py", line 2, in <module>
from .views import ResultsListViewMixin
File "d:\workspace\my_project\api\utils\mixins\results\views.py", line 5, in <module>
from rest_framework.generics import get_object_or_404
File "d:\workspace\my_project\api\.env\lib\site-packages\rest_framework\generics.py", line 24, in <module>
class GenericAPIView(views.APIView):
File "d:\workspace\my_project\api\.env\lib\site-packages\rest_framework\generics.py", line 43, in GenericAPIView
filter_backends = api_settings.DEFAULT_FILTER_BACKENDS
File "d:\workspace\my_project\api\.env\lib\site-packages\rest_framework\settings.py", line 220, in __getattr__
val = perform_import(val, attr)
File "d:\workspace\my_project\api\.env\lib\site-packages\rest_framework\settings.py", line 168, in perform_import
return [import_from_string(item, setting_name) for item in val]
File "d:\workspace\my_project\api\.env\lib\site-packages\rest_framework\settings.py", line 168, in <listcomp>
return [import_from_string(item, setting_name) for item in val]
File "d:\workspace\my_project\api\.env\lib\site-packages\rest_framework\settings.py", line 177, in import_from_string
return import_string(val)
File "d:\workspace\my_project\api\.env\lib\site-packages\django\utils\module_loading.py", line 17, in import_string
module = import_module(module_path)
File "d:\workspace\my_project\api\.env\lib\importlib\__init__.py", line 127, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
File "d:\workspace\my_project\api\.env\lib\site-packages\rest_flex_fields\__init__.py", line 3, in <module>
from .views import FlexFieldsModelViewSet
File "d:\workspace\my_project\api\.env\lib\site-packages\rest_flex_fields\views.py", line 6, in <module>
from rest_framework import viewsets
File "d:\workspace\my_project\api\.env\lib\site-packages\rest_framework\viewsets.py", line 199, in <module>
class GenericViewSet(ViewSetMixin, generics.GenericAPIView):
AttributeError: module 'rest_framework.generics' has no attribute 'GenericAPIView'
Removing the imports from .views import FlexFieldsModelViewSet
from __init__.py
and from rest_framework.viewsets import GenericViewSet
from filter_backends.py
solved the issue for me. Probably the later could be placed behind the TYPE_CHECKING
flag from typing
, but I have no idea about the first one.
Env:
Django==3.0.7
django-filter==2.3.0
djangorestframework==3.11.0
drf-flex-fields==0.8.5
I have the following models:
class UploadTemplate(models.Model):
book = models.ForeignKey('Book', on_delete=models.SET_NULL, null=True)
coupon_type = models.CharField(max_length=100)
class Book(models.Model):
book_name = models.CharField(max_length=30)
#....random other fields
and serializers as follows:
class TemplateSerializer(FlexFieldsModelSerializer):
book = serializers.PrimaryKeyRelatedField(read_only=True)
class Meta:
model = UploadTemplate
fields = ["coupon_type", "id", "book"]
expandable_fields = {
'book': ('BookSerializer', {
'source': 'book'
})
}
class BookSerializer(FlexFieldsModelSerializer):
class Meta:
model = Book
fields = "__all__"
If i make a call to http://localhost:8000/v1/coupons/templates/6/?expand=book
I'd expect book to expand to the nested serializer. Unfortunately it doesn't and I've drawn a blank debugging.
My viewset:
from rest_flex_fields.views import FlexFieldsMixin
class UploadTemplateViewset(viewsets.ModelViewSet, FlexFieldsMixin):
queryset = models.UploadTemplate.objects.all()
serializer_class = serializers.UploadTemplateSerializer
filterset_fields = ('book', )
permit_list_expands = ['book']
def get_queryset(self):
print(is_expanded(self.request, 'book'))
return models.UploadTemplate.objects.all()
confirms that book is expanded. (i,e, it prints True in the console).
Am I doing something obviously dumb, and if not, where should I start with debugging the issue? I'm on django 2, python 3.6
I cannot tell but does this conform to jsonapi.org specification on how to render the JSON format?
Just wondering, I was trying to update to the latest release 0.9.9 but I cannot find it on Pypi
I would expect that when instantiating a serializer and explicitly setting the expand argument, that would be respected no matter what. Instead, the behavior is that if there is an expand query param, that is respected over an empty list argument because empty lists are falsy. Here is the logic in question:
"expand": (
self._get_permitted_expands_from_query_param(EXPAND_PARAM)
if not expand
else []
),
Maybe this is the intended behavior but it seems strange to me. Feature or bug?
Provide Django setting for customizing the field name that is used to expand resources. This will allow use of "include" instead of "expand".
The JSON API spec uses the word "include":
http://jsonapi.org/format/#fetching-includes
It seems that since upgrading to 0.7.0, drf-flex-fields no longer parses expansions requested via URL querystring the same way.
I have been doing this:
class EphemeronViewSet(FlexFieldsModelViewSet):
[...]
def get_serializer(self, *args, **kwargs):
if "expand" in self.request.query_params: # add from querystring in URL
kwargs["expand"] = self.request.query_params['expand']
return super(EphemeronViewSet, self).get_serializer(*args, **kwargs)
which allows expanding fields like this:
curl '0.0.0.0:8000/api/ephemera/?expand=user'
But since upgrading to 0.7.0, the field no longer gets expanded in the output.
I think these changed lines are what resulted in the change in behavior. If I add a print(passed)
just after that and compare previous behavior to 0.7.0:
{'expand': 'user', 'fields': [], 'omit': []}
{'expand': ['u', 's', 'e', 'r'], 'fields': [], 'omit': []}
Perhaps these lines should be something more like [kwargs.pop("expand")] if "expand" in kwargs else []
instead of list(kwargs.pop("fields", []))
?
(Or perhaps there's a better way to pass querystrings to drf-flex-fields than what I've been doing in my get_serializer()
override as shown above? I have been doing it that way in order to expand fields in response to a POST request, if I recall recorrectly.)
Hi, why does this repo has no releases in Github while there are a plenty of them in PyPi?
https://pypi.org/project/drf-flex-fields/#history
I wonder what exact commit corresponds to this or that release in PyPi.
Thanks!
post for create some resource, we also need to response include expand-information
Now, the query parameter expand
and fields
are fixed. Also, the magic value ~all
as expand value is fixed. It would be nicer if these could be configured in the settings.
Hello,
I may be missing something, so I apologize if I am, but I am unable to get DRF-Flex-Fields to work properly with a reverse relationships . Is this not something that drf-flex-fields can do or am I messing up somehow?
As a follow up to this comment #16 (comment)
I want to ask what if the /users
call also include groups and the groups are repeated several times, would drf-flex-fields philosophy by default provide groups as a direct expansion approach?
I would like to specify some fields to omit when I initialize the serializer and also allow the API request query_param to omit extrfa fields. Right now it's either or but merging the two would be nice. Also the same for fields, possibly.
For example:
This should return all the fields except password &email. Right now, it will only omit password.
FlexSerializer(data=request.data, omit=['password'])
GET /api/?omit=email
def __init__(self, *args, **kwargs):
expand = list(kwargs.pop("expand", []))
fields = list(kwargs.pop("fields", []))
omit = list(kwargs.pop("omit", []))
super(FlexFieldsSerializerMixin, self).__init__(*args, **kwargs)
self.expanded_fields = []
self._flex_fields_applied = False
self._flex_options = {
"expand": (
expand
if len(expand) > 0
else self._get_permitted_expands_from_query_param()
),
"fields": (
fields if len(fields) > 0 else self._get_query_param_value("fields")
),
"omit": omit if len(omit) > 0 else self._get_query_param_value("omit"),
}
Not sure what I'm doing wrong, but I'd really love to get this fixed after following the README pretty exact I think.
serializers.py
from rest_flex_fields import FlexFieldsModelSerializer
class CastleSerializer(FlexFieldsModelSerializer):
class Meta:
model = Castle
fields = '__all__'
class RoomSerializer(FlexFieldsModelSerializer):
castle = serializers.PrimaryKeyRelatedField(read_only=True)
class Meta:
model = Room
fields = ('id', 'name', 'castle')
expandable_fields = {
"castle": CastleSerializer
}
settings.py
# To make our nested serializers more like JSON API
REST_FLEX_FIELDS = {"EXPAND_PARAM": "include"}
API call: GET /api/v1/room/1/?include=castle
or
API call: GET /api/v1/rooms/?include=castle
both give:
{
"id": 1,
"name": "Throne Room",
"castle": 1
}
OR
[
{
"id": 1,
"name": "Throne Room",
"castle": 1
}
]
I expect it to be:
{
"id": 1,
"name": "Throne Room",
"castle": {
"id": 1,
"name": "Daventry Castle"
}
}
I also expect to be able to POST an update method without having to add the nested castle
id and have it just validate and work. That looks like it's fixed in the latest.
API call: POST /api/v1/rooms-update/
{
"id": 1,
"name": "Throne Room Changed Text",
}
TO RETURN
{
"id": 1,
"name": "Throne Room Changed Text",
"castle": {
"id": 1,
"name": "Daventry Castle"
}
}
It seems none of this is currently working. What am I missing out there (probably obvious to everyone else) to get this library working?
In the filter backend for optimizing the queries, the backend is doing a check to confirm that the serializer being used is a valid FlexFields serializer.
https://github.com/rsinger86/drf-flex-fields/blob/master/rest_flex_fields/filter_backends.py#L19
However I think rather than checking for the FlexFieldsModelSerializer, the proper check should be for a instance of FlexFieldsSerailizerMixin, as this mixin is what actually contains the functionality needed by the filter backend.
There are several cases in my project where we are having to build our own ModelSerializers where it doesn't make sense or could break things to introduce an additional ModelSerializer to the MRO, especially just to add the functionality provided by simply adding FlexFieldsSerializerMixin.
The FlexFieldsModelSerializer returns values for related fields by default:
GET /api/location/region/AKL/
{
"url": "http://127.0.0.1:8000/api/location/region/AKL/",
"code": "AKL",
"name": "Auckland",
"country": "NZL"
}
But by specifying country as a HyperlinkedRelatedField:
class RegionSerializer(FlexFieldsModelSerializer):
country = serializers.HyperlinkedRelatedField(
read_only=True,
view_name='country-detail'
)
We get:
{
"url": "http://127.0.0.1:8000/api/location/region/AKL/",
"code": "AKL",
"name": "Auckland",
"country": "http://127.0.0.1:8000/api/location/country/NZL/"
}
and ?expand=country works as expected. Nice.
is it possible to optimize query automatically for list request? I am not really sure how to implement this, but maybe we can override get_queryset to append the query with prefetch_related using fields that are is_expanded?
Hi, I created an expansion from your flex fields to allow the usage between APIs.
It is really usefull in a distributed system, where we usually have many weak foreign keys that points to a resource in a different service.
The usage remained the same, so its transparent to the API consumer. It even allows the nested expand/fields/omit through APIs
Do you have interest in a PR with this feature?
Ex:
class Meta:
model = FileImportInput
fields = "__all__"
expandable_fields = {
"data_input": (
APIResourceFlexDict,
{
"url": settings.DATA_INPUT_RESOURCE,
"included_headers": ["Authorization"],
},
),
}
delete
I've run the test suite for this package on Django 3.2, and it only required minor changes to work (and still be Django 3.1 compatible).
Would you accept a PR to upgrade this package's support to Django 3.2 and fix some deprecation warnings? Let me know if so.
Hey there! I was looking at graphql until I found you awesome library, thank you! I don't have to add another yet API ๐ก๏ธ
The question is:
We have two models with a lot of fields, e.g
class Order:
field0 = ....
field1 = ...
...
field35 = ...
restaurant=Restaurant
class Restaurant:
field0 = ...
field1 = ...
...
field60 = ....
And I want to make a request that will get All fields from Order, expand Restaurant, but only field0\field1
How can I achieve it with drf-flex?
I tried to solve it these ways:
GET /orders/?expand=restaurant
- but it gives too much fields, I really want to only two from Restaurant because we have about 100 orders in a list and all of them will contain 60 additional fieldsGET /orders/?fields=field0,field1,...,field35,restaurant.field0,restaurant.field1&expand=restaurant
- In this case I get that I want to, but I have to add all of fields to params, and I really don't want to do it on the frontend :)GET /orders/?expand=restaurant
- give me too much fields tooWhat I really want to can be achieved with GET /orders/?fields=~all,restaurant.field0, restaurant.field2&expand=restaurant
But looks like ~all
works only in expand
and not on fields
, right?
Hello,
I started getting this error after upgrading to Python 3 where fields
is a generator and not a list.
Traceback:
File "/app/.venv/lib/python3.7/site-packages/django/core/handlers/exception.py" in inner
41. response = get_response(request)
File "/app/.venv/lib/python3.7/site-packages/django/core/handlers/base.py" in _legacy_get_response
249. response = self._get_response(request)
File "/app/.venv/lib/python3.7/site-packages/django/core/handlers/base.py" in _get_response
187. response = self.process_exception_by_middleware(e, request)
File "/app/.venv/lib/python3.7/site-packages/django/core/handlers/base.py" in _get_response
185. response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/app/.venv/lib/python3.7/site-packages/django/views/decorators/csrf.py" in wrapped_view
58. return view_func(*args, **kwargs)
File "/app/.venv/lib/python3.7/site-packages/rest_framework/viewsets.py" in view
103. return self.dispatch(request, *args, **kwargs)
File "/app/.venv/lib/python3.7/site-packages/rest_framework/views.py" in dispatch
483. response = self.handle_exception(exc)
File "/app/.venv/lib/python3.7/site-packages/rest_framework/views.py" in handle_exception
443. self.raise_uncaught_exception(exc)
File "/app/.venv/lib/python3.7/site-packages/rest_framework/views.py" in dispatch
480. response = handler(request, *args, **kwargs)
File "/app/.venv/lib/python3.7/site-packages/rest_framework/mixins.py" in list
44. serializer = self.get_serializer(page, many=True)
File "/app/.venv/lib/python3.7/site-packages/rest_framework/generics.py" in get_serializer
112. return serializer_class(*args, **kwargs)
File "/app/.venv/lib/python3.7/site-packages/rest_framework/serializers.py" in __new__
124. return cls.many_init(*args, **kwargs)
File "/app/.venv/lib/python3.7/site-packages/rest_framework/serializers.py" in many_init
145. child_serializer = cls(*args, **kwargs)
File "/app/.venv/lib/python3.7/site-packages/rest_flex_fields/serializers.py" in __init__
34. sparse_fields, next_sparse_fields = split_levels(fields)
File "/app/.venv/lib/python3.7/site-packages/rest_flex_fields/utils.py" in split_levels
27. fields = [a.strip() for a in fields.split(",") if a.strip()]
AttributeError at /api/v2/users/
'map' object has no attribute 'split'
Running on Python 3.7
Django==1.11.25
djangorestframework==3.8.2
drf-flex-fields==0.6.1
I can submit a PR to fix this issue if you don't mind
When passing omitted fields to the serializer in a views' get_serializer
method, it still seems to be possible to save/update said fields, even though they're not returned in the Response.
For example:
def get_serializer(self, *args, **kwargs):
serializer_class = self.get_serializer_class()
kwargs.setdefault('context', self.get_serializer_context())
kwargs.update({'omit': 'example_field'})
return serializer_class(*args, **kwargs)
Will not return example_field
in the response, however any value passed into a PUT
or PATCH
request for that field will still be saved on the model.
Fields don't seem to be omitted until to_representation
is called, but the lateness of that call means that the serializer still contains these fields up until the response is prepared. Is this the expected behavior? It seems contrary to what I would expect.
Hi!
Basically this is what I'm talking about: dbrgn/drf-dynamic-fields#25
Will it even work with that TPA's RecursiveField implementation? If not, can you point me in the right direction with regards to what to change in drf-flex-fields
' code?
Thanks.
I am using drf-spectacular for schema generation; using the @extend_schema
decorator to add docs to my views.
Say I have a serializer named PersonSerializer
that inherits from FlexFieldsModelSerializer
. If I pass it the fields
argument, I expect it to show only those fields in the schema. Example code:
@extend_schema(
responses={201: PersonSerializer(fields=["id", "username"])},
)
@api_view(["POST"])
def my_view(request):
But the schema picks up all the default fields from the PersonSerializer
class.
I do not know if this is something that needs to be solved in this project or drf-spectacular but any help is greatly appreciated.
Documentation talks about deferring fields but I can't get that to work. Just so we're on the same page, I have a couple of statistic fields that I want to make available but I don't want included in every query. These are annotated and pretty expensive (hence the deferral).
So here, I want to be able to annotate and display the last visit (the end of the last booking a Visitor had). The annotation itself โdespite looking pretty gnarlyโ works.... But throwing expand=last_visit
or expand=last_visit&fields=last_visit
through on the querystring doesn't make the serialiser render this field.
class VisitorSerializer(FlexFieldsModelSerializer):
class Meta:
model = Visitor
fields = ('first_name', 'second_name')
expandable_fields = {
'last_visit': (serializers.DateTimeField, {})
}
class VisitorViewSet(viewsets.ModelViewSet):
permission_classes = (IsAuthenticated,)
queryset = Visitor.objects.all()
serializer_class = VisitorSerializer
permit_list_expands = ['last_visit']
def get_queryset(self):
queryset = super().get_queryset()
if is_expanded(self.request, 'last_visit'):
subq = Booking.objects.filter(visitor_bookings__visitor=OuterRef('pk')).values('timeframe')
queryset = queryset.annotate(
last_visit=RangeEndsWith(Subquery(subq))
)
return queryset
This works perfectly fine in 0.7.5, but breaks from 0.8 ๐
from rest_flex_fields import FlexFieldsModelSerializer
from django.db import models
class MentorMentee(models.Model):
mentee = models.ForeignKey('CustomUser', related_name="mentors", on_delete=models.CASCADE)
skill = models.ForeignKey('Skill', on_delete=models.CASCADE)
mentor = models.ForeignKey('CustomUser', related_name="mentees", on_delete=models.CASCADE)
class MentorReqSerializer(FlexFieldsModelSerializer):
class Meta:
model = MentorMentee
fields = '__all__'
data = MentorReqSerializer(fields=('mentor', 'skill'), data={'mentor': 1, 'skill': 51518})
data.is_valid(raise_exception=True)
Raises
rest_framework.exceptions.ValidationError: {'mentee': [ErrorDetail(string='This field is required.', code='required')]}
Is there any way to expand fields from the view or set some fields as expanded by default?
I am utilizing drf-flex-fields heavily and everything works fine until the moment i add:
REST_FRAMEWORK = {
...
'DEFAULT_FILTER_BACKENDS': (
'rest_flex_fields.filter_backends.FlexFieldsFilterBackend',
),
...
}
It crashes the app with the following traceback:
INFO 2020-03-03 21:38:35,864 autoreload 90410 4456287680 Watching for file changes with StatReloader
Performing system checks...
Exception in thread django-main-thread:
Traceback (most recent call last):
File "/usr/local/Cellar/[email protected]/3.8.1/Frameworks/Python.framework/Versions/3.8/lib/python3.8/threading.py", line 932, in _bootstrap_inner
self.run()
File "/usr/local/Cellar/[email protected]/3.8.1/Frameworks/Python.framework/Versions/3.8/lib/python3.8/threading.py", line 870, in run
self._target(*self._args, **self._kwargs)
File "/Users/alex.zagoro/venv/noya-TS4FQGPC/lib/python3.8/site-packages/django/utils/autoreload.py", line 53, in wrapper
fn(*args, **kwargs)
File "/Users/alex.zagoro/venv/noya-TS4FQGPC/lib/python3.8/site-packages/django/core/management/commands/runserver.py", line 117, in inner_run
self.check(display_num_errors=True)
File "/Users/alex.zagoro/venv/noya-TS4FQGPC/lib/python3.8/site-packages/django/core/management/base.py", line 392, in check
all_issues = self._run_checks(
File "/Users/alex.zagoro/venv/noya-TS4FQGPC/lib/python3.8/site-packages/django/core/management/base.py", line 382, in _run_checks
return checks.run_checks(**kwargs)
File "/Users/alex.zagoro/venv/noya-TS4FQGPC/lib/python3.8/site-packages/django/core/checks/registry.py", line 72, in run_checks
new_errors = check(app_configs=app_configs)
File "/Users/alex.zagoro/venv/noya-TS4FQGPC/lib/python3.8/site-packages/django/core/checks/urls.py", line 13, in check_url_config
return check_resolver(resolver)
File "/Users/alex.zagoro/venv/noya-TS4FQGPC/lib/python3.8/site-packages/django/core/checks/urls.py", line 23, in check_resolver
return check_method()
File "/Users/alex.zagoro/venv/noya-TS4FQGPC/lib/python3.8/site-packages/django/urls/resolvers.py", line 407, in check
for pattern in self.url_patterns:
File "/Users/alex.zagoro/venv/noya-TS4FQGPC/lib/python3.8/site-packages/django/utils/functional.py", line 48, in __get__
res = instance.__dict__[self.name] = self.func(instance)
File "/Users/alex.zagoro/venv/noya-TS4FQGPC/lib/python3.8/site-packages/django/urls/resolvers.py", line 588, in url_patterns
patterns = getattr(self.urlconf_module, "urlpatterns", self.urlconf_module)
File "/Users/alex.zagoro/venv/noya-TS4FQGPC/lib/python3.8/site-packages/django/utils/functional.py", line 48, in __get__
res = instance.__dict__[self.name] = self.func(instance)
File "/Users/alex.zagoro/venv/noya-TS4FQGPC/lib/python3.8/site-packages/django/urls/resolvers.py", line 581, in urlconf_module
return import_module(self.urlconf_name)
File "/Users/alex.zagoro/venv/noya-TS4FQGPC/lib/python3.8/importlib/__init__.py", line 127, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
File "<frozen importlib._bootstrap>", line 1014, in _gcd_import
File "<frozen importlib._bootstrap>", line 991, in _find_and_load
File "<frozen importlib._bootstrap>", line 975, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 671, in _load_unlocked
File "<frozen importlib._bootstrap_external>", line 783, in exec_module
File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
File "/Users/alex.zagoro/projects/gagosian/noya/gagosian/urls.py", line 6, in <module>
path('api/v1/', include('gagosian.api.v1.urls', namespace='api'))
File "/Users/alex.zagoro/venv/noya-TS4FQGPC/lib/python3.8/site-packages/django/urls/conf.py", line 34, in include
urlconf_module = import_module(urlconf_module)
File "/Users/alex.zagoro/venv/noya-TS4FQGPC/lib/python3.8/importlib/__init__.py", line 127, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
File "<frozen importlib._bootstrap>", line 1014, in _gcd_import
File "<frozen importlib._bootstrap>", line 991, in _find_and_load
File "<frozen importlib._bootstrap>", line 975, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 671, in _load_unlocked
File "<frozen importlib._bootstrap_external>", line 783, in exec_module
File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
File "/Users/alex.zagoro/projects/gagosian/noya/gagosian/api/v1/urls.py", line 4, in <module>
from .contacts import urls as contacts_urls
File "/Users/alex.zagoro/projects/gagosian/noya/gagosian/api/v1/contacts/urls.py", line 3, in <module>
from .viewsets import (
File "/Users/alex.zagoro/projects/gagosian/noya/gagosian/api/v1/contacts/viewsets.py", line 1, in <module>
from rest_framework.viewsets import ModelViewSet
File "/Users/alex.zagoro/venv/noya-TS4FQGPC/lib/python3.8/site-packages/rest_framework/viewsets.py", line 27, in <module>
from rest_framework import generics, mixins, views
File "/Users/alex.zagoro/venv/noya-TS4FQGPC/lib/python3.8/site-packages/rest_framework/generics.py", line 24, in <module>
class GenericAPIView(views.APIView):
File "/Users/alex.zagoro/venv/noya-TS4FQGPC/lib/python3.8/site-packages/rest_framework/generics.py", line 43, in GenericAPIView
filter_backends = api_settings.DEFAULT_FILTER_BACKENDS
File "/Users/alex.zagoro/venv/noya-TS4FQGPC/lib/python3.8/site-packages/rest_framework/settings.py", line 220, in __getattr__
val = perform_import(val, attr)
File "/Users/alex.zagoro/venv/noya-TS4FQGPC/lib/python3.8/site-packages/rest_framework/settings.py", line 168, in perform_import
return [import_from_string(item, setting_name) for item in val]
File "/Users/alex.zagoro/venv/noya-TS4FQGPC/lib/python3.8/site-packages/rest_framework/settings.py", line 168, in <listcomp>
return [import_from_string(item, setting_name) for item in val]
File "/Users/alex.zagoro/venv/noya-TS4FQGPC/lib/python3.8/site-packages/rest_framework/settings.py", line 177, in import_from_string
return import_string(val)
File "/Users/alex.zagoro/venv/noya-TS4FQGPC/lib/python3.8/site-packages/django/utils/module_loading.py", line 17, in import_string
module = import_module(module_path)
File "/Users/alex.zagoro/venv/noya-TS4FQGPC/lib/python3.8/importlib/__init__.py", line 127, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
File "/Users/alex.zagoro/venv/noya-TS4FQGPC/lib/python3.8/site-packages/rest_flex_fields/__init__.py", line 3, in <module>
from .views import FlexFieldsModelViewSet
File "/Users/alex.zagoro/venv/noya-TS4FQGPC/lib/python3.8/site-packages/rest_flex_fields/views.py", line 21, in <module>
class FlexFieldsModelViewSet(FlexFieldsMixin, viewsets.ModelViewSet):
AttributeError: partially initialized module 'rest_framework.viewsets' has no attribute 'ModelViewSet' (most likely due to a circular import)
Adding the backend directly to the view works fine.
Based on https://github.com/rsinger86/drf-flex-fields#dynamically-setting-fields,
Here are my serializers:
class AddressBookSerializer(FlexFieldsModelSerializer):
class Meta:
model = SellerAddressBook
fields = ('id', 'address', 'city', 'state', )
class OrderSerializer(FlexFieldsModelSerializer):
address = AddressBookSerializer(many=False)
class Meta:
model = Order
fields = ('id', 'order_name', 'address',)
On Get,
/orders/123/?fields=id,order_name,address.city
ACTUAL RESULT
{
"id" : 123
"order_name" : "Order Name",
"address" : {
"id" : "1",
"address": "my add",
"city": "my_city",
"state": " my state"
}
EXPECTED RESULT
{
"id" : 13322
"order_name" : "Order Name",
"address" : {
"city": "my_city"
}
Hi!
Thanks for the great library! I faced an issue with multiple nested fields expansion and was able to track it down to the following lines:
drf-flex-fields/rest_flex_fields/serializers.py
Lines 93 to 107 in 25be9c1
TypeError: __init__() got an unexpected keyword argument 'expand'
Since I am using custom REST_FLEX_FIELDS
params, this is throwing an error because the serializer expected parameters with different names since you only pop off the expected custom params
drf-flex-fields/rest_flex_fields/serializers.py
Lines 26 to 31 in 25be9c1
I believe all thats needed to fix this is to edit this section to utilize the custom params instead when passing
Hi Robert;
First of all, thank you so much for such an amazing library! ๐
I'm testing the "FlexFieldsFilterBackend" and I think you overlooked something. Please let me know if I'm wrong.
When dealing with M2M fields, it's always desiderable to include them in prefetch_related clause, even if you're working only with pks. Otherwise, django would need to perform an extra query for every single record.
I noticed that you check if the field is expanded before including it in the prefetch_related clause. I think it should be included anyways.
Thanks in advance.
HI,
I have to use drf-flex-field in multi database architecture.
Currently I am looping through all the databases and fetching the result. but when I pass expand it breaks and raise error that Model doesn't exists. because it is selecting default database.
Is there any way to pass the database name to be using when expanding and serialising?
The README.md mentions:
The
include
field takes precedence overexpand
.
I find this confusing. Shouldn't it be fields
instead of include
?
v0.9.4 is published on PyPi but there isn't a corresponding tag (or source) in this repo.
Currently flex-fields support passing lists of fields to expand/omit like this:
?expand=field_1,field_2,field_3
For our internal development there is a request from frontend team to support passing lists like this:
?expand=field_1,field_2
?expand=field_1&expand=field_2
?expand[]=field_1&expand[]=field_2
The reason is - different libs/frameworks has different internal implementation for handling query string arrays, and looks like all this 3 options used.
Currently we do this with overriding _parse_request_list_value
method from FlexFieldsModelSerializer
.
This is naive and simple implementation:
def _parse_request_list_value(self, field):
if not self._can_access_request:
return []
values = self.context["request"].query_params.getlist(field)
if not values:
values = self.context["request"].query_params.getlist('{}[]'.format(field))
if values and len(values) == 1:
return values[0].split(",")
return values or []
@rsinger86 Do you interested in adding this to the drf-flex-fields
itself? If so, I can create PR then.
a.b
and b
are both legal expands, is_epxanded
will not distinguish between b
at the top level or nestedis_expanded
will return true for all keys if ~all
is in expanded, but ~all
only expands top level expandsProposed fix:
def is_expanded(expand, key):
"""Determine if the given key is expanded"""
expand_fields = []
# first split on commas to get each expand
for full in expand.split(","):
# than split on dots to get each component that is expanded
parts = full.split(".")
for i in range(len(parts)):
# add each prefix, as each prefix is epxanded, ie
# a.b.c will add a, a.b and a.b.c to the expand_fields list
# we do this to differentiate a.b from b
expand_fields.append(".".join(parts[: i + 1]))
# ~all only expands top level fields
if "." not in key and "~all" in expand_fields:
return True
return key in expand_fields
Test:
import pytest
from .utils import is_expanded
data = [
("a", "a", True),
("a", "b", False),
("a,b,c", "a", True),
("a,b,c", "b", True),
("a,b,c", "c", True),
("a,b,c", "d", False),
("a.b.c", "a", True),
("a.b.c", "a.b", True),
("a.b.c", "a.b.c", True),
("a.b.c", "b", False),
("a.b.c", "c", False),
("a.b.c", "d", False),
("a.b.c,d", "a", True),
("a.b.c,d", "d", True),
("~all", "a", True),
("~all", "a.b", False),
]
class TestIsExpanded:
@pytest.mark.parametrize("expand,key,ret", data)
def test_expanded(self, expand, key, ret):
assert is_expanded(expand, key) is ret
I'm trying to rename a field that is also a nested serializer. Normally, I'd use the source
kwarg during serializer instantiation for this, but if I define the serializer in expandable_fields
, it doesn't work. In the code below, if I set the field name (in the FooBarSerializer
) to foo_bar_details
, everything works. But if I set it to details
and then include the source
field, I get this error (this approach works fine if I instantiate the serializer normally, outside of expandable_fields
):
File "/Users/david/src/proj/venv/lib/python3.7/site-packages/rest_framework/serializers.py", line 1325, in build_unknown_field
(field_name, model_class.__name__)
django.core.exceptions.ImproperlyConfigured: Field name `details` is not valid for model `FooBar`.
class FooBarDetails(django.db.models.Model):
id = models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True)
properties = models.CharField(max_length=256, null=True, blank=True)
class Meta:
db_table = 'foo_bar_details'
class FooBar(django.db.models.Model):
id = models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True)
name = models.CharField(max_length=256, null=True, blank=True)
foo_bar_details = models.ForeignKey(FooBarDetails, on_delete=models.SET_NULL, null=True)
class Meta:
db_table = 'foo_bar'
class FooBarDetailsSerializer(FlexFieldsModelSerializer):
class Meta:
model = FooBarDetails
fields = ('properties')
class FooBarSerializer(FlexFieldsModelSerializer):
expandable_fields = {
'details': (
FooBarDetailsSerializer,
{'required': False, 'many': False, 'read_only': True, 'source': 'foo_bar_details'},
),
}
class Meta:
model = FooBar
fields = (
'id',
'name',
'details',
)
I think I've traced this issue to here: https://github.com/rsinger86/drf-flex-fields/blob/master/rest_flex_fields/serializers.py#L102
Is there a reason that the source
keyword is purposefully being excluded from the serializer instantiation?
Would it be possible to have OpenAPI support for query parameters?
At the moment this is provided by the FlexFieldsFilterBackend
however the filter backend also implements the (not thoroughly tested) query optimization features, which may not be desirable.
An easy fix would be to just separate the docs into its own (dummy) filter backend that doesn't actually do anything query-wise, so people who only want docs can include that one. It does feel a bit hacky to make an essentially fake filtering backend just for documentation so I'd like to get others' thoughts on this if there's a better way.
All fields that are expandable will get the same serializer names. It's not directly an issue with this library but this causes issues with (for example) schema generation where names -can- be based on serializer class names.
Perhaps include the original serializer name in the generated type name.
In views.py:50
change this:
- return type('DynamicFieldsModelSerializer', (self.serializer_class,), {
+ return type('{}FlexFieldsSerializer'.format(self.serializer_class.__name__), (self.serializer_class,), {
'expand': expand,
'include_fields': fields,
})
When calling a ViewSet that has both the FlexFieldsFilterBackend
and a serializer containing a field pointing to a GenericForeignKey
, we get the error : AttributeError: 'GenericForeignKey' object has no attribute 'attname'
It can easily be reproduced with the following classes :
models
class TaggedItem(models.Model):
tag = models.SlugField()
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
link = ForeignKey(SomeTaggableModel, null=True, on_delete=models.CASCADE)
serializers
class TaggedItemSerializer(FlexFieldsSerializerMixin, ModelSerializer):
content_object = PrimaryKeyRelatedField(read_only=True)
class Meta:
model = TaggedItem
fields = (
"id",
"content_type",
"object_id",
"content_object"
)
Please notice that the PrimaryKeyRelatedField
is only here for simplicity, in real life it could be a GenericRelatedField
from
rest-framework-generic-relations.
views
class TaggedItemViewSet(ModelViewSet):
serializer_class = TaggedItemSerializer
queryset = TaggedItem.objects.all()
filter_backends = [FlexFieldsFilterBackend]
After digging a bit, I found that the field gets included in the queryset.only(...)
and crashes probably because it is not a concrete field. A GenericForeignKey should, instead, be included in the queryset.prefetch_related(...)
fields.
The easy fix is to not import everything into __init__
, but that changes the API. The bad solution is to tell people to not list the app in INSTALLED_APPS. The ugly solution is to change views.py
to import DRF internals inside the methods where they are needed, not importing at the top.
python3 manage.py migrate
...
File "/usr/lib/python3.8/site-packages/rest_flex_fields/__init__.py", line 3, in <module>
from .views import FlexFieldsModelViewSet
File "/usr/lib/python3.8/site-packages/rest_flex_fields/views.py", line 6, in <module>
from rest_framework import viewsets
File "/usr/lib/python3.8/site-packages/rest_framework/viewsets.py", line 27, in <module>
from rest_framework import generics, mixins, views
File "/usr/lib/python3.8/site-packages/rest_framework/generics.py", line 9, in <module>
from rest_framework import mixins, views
File "/usr/lib/python3.8/site-packages/rest_framework/views.py", line 17, in <module>
from rest_framework.schemas import DefaultSchema
File "/usr/lib/python3.8/site-packages/rest_framework/schemas/__init__.py", line 33, in <module>
authentication_classes=api_settings.DEFAULT_AUTHENTICATION_CLASSES,
File "/usr/lib/python3.8/site-packages/rest_framework/settings.py", line 220, in __getattr__
val = perform_import(val, attr)
File "/usr/lib/python3.8/site-packages/rest_framework/settings.py", line 168, in perform_import
return [import_from_string(item, setting_name) for item in val]
File "/usr/lib/python3.8/site-packages/rest_framework/settings.py", line 168, in <listcomp>
return [import_from_string(item, setting_name) for item in val]
File "/usr/lib/python3.8/site-packages/rest_framework/settings.py", line 177, in import_from_string
return import_string(val)
File "/usr/lib/python3.8/site-packages/django/utils/module_loading.py", line 17, in import_string
module = import_module(module_path)
File "/usr/lib64/python3.8/importlib/__init__.py", line 127, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
File "/usr/lib/python3.8/site-packages/rest_framework_jwt/authentication.py", line 17, in <module>
from rest_framework_jwt.blacklist.exceptions import (
File "/usr/lib/python3.8/site-packages/rest_framework_jwt/blacklist/exceptions.py", line 8, in <module>
class MissingToken(AuthenticationFailed):
File "/usr/lib/python3.8/site-packages/rest_framework_jwt/blacklist/exceptions.py", line 10, in MissingToken
msg = _('The token is missing.')
File "/usr/lib/python3.8/site-packages/django/utils/translation/__init__.py", line 105, in ugettext
return gettext(message)
File "/usr/lib/python3.8/site-packages/django/utils/translation/__init__.py", line 92, in gettext
return _trans.gettext(message)
File "/usr/lib/python3.8/site-packages/django/utils/translation/trans_real.py", line 354, in gettext
_default = _default or translation(settings.LANGUAGE_CODE)
File "/usr/lib/python3.8/site-packages/django/utils/translation/trans_real.py", line 267, in translation
_translations[language] = DjangoTranslation(language)
File "/usr/lib/python3.8/site-packages/django/utils/translation/trans_real.py", line 154, in __init__
self._add_installed_apps_translations()
File "/usr/lib/python3.8/site-packages/django/utils/translation/trans_real.py", line 195, in _add_installed_apps_translations
raise AppRegistryNotReady(
django.core.exceptions.AppRegistryNotReady: The translation infrastructure cannot be initialized before the apps registry is ready. Check that you don't make non-lazy gettext calls at import time.
Also occurs with drf_extended_viewset and beda-software/drf-writable-nested#93
I'm facing a RecursionError when querying a subset of fields that should not make recursion at all.
Here are some simplified models and serializers:
# disclaimer: I did not directly test this code, it's just an extract of mine.
# If you don't manage to reproduce the issue with this snippet, please let me know
class Client(models.Model):
name = models.CharField(max_length=250)
class Project(models.Model):
name = models.CharField(max_length=250)
client = models.ForeignKey(Client, on_delete=models.PROTECT, related_name='projects')
class ProjectSerializer(FlexFieldsModelSerializer):
expandable_fields = {
'client_details': ('api.ClientSerializer', {'source': 'client', 'read_only': True}),
}
class Meta:
model = Project
fields = [
'id',
'name',
]
class ClientSerializer(FlexFieldsModelSerializer):
expandable_fields = {
'projects_details': (ProjectSerializer, {'source': 'projects', 'many': True, 'read_only': True}),
}
class Meta:
model = Client
fields = [
'id',
'name',
]
I queried my endpoint like this:
/api/client/5906?expand=projects_details&fields=id,name,projects_details.id
The expected result would be:
{
"id": 5906,
"name": "client name",
"projects_details": [
{
"id": 2056
},
{
"id": 3323
}
]
}
Instead, I'm getting a RecursionError (see below). Did I miss something ? I understand that since I'm requesting to expand the projects and the projects themselves have a reference to the clients, but given the fields
input, I feel like this should not fall in recursion.
RecursionError at /api/client/5906
maximum recursion depth exceeded
Request Method: GET
Request URL: http://localhost:81/api/client/5906?expand=projects_details&fields=id,name,projects_details.id
Django Version: 2.1.9
Python Executable: C:\Users\augendre\.virtualenvs\bizdev_kb_api-TgTXDkAC\Scripts\python.exe
Python Version: 3.7.3
Python Path: ****
Server time: Mon, 24 Jun 2019 17:47:09 +0200
Installed Applications:
['django.contrib.admindocs',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'django_filters',
'corsheaders',
'api',
'custom_auth']
Installed Middleware:
['corsheaders.middleware.CorsMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.RemoteUserMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware']
Traceback:
File "C:\Users\augendre\.virtualenvs\bizdev_kb_api-TgTXDkAC\lib\site-packages\django\core\handlers\exception.py" in inner
34. response = get_response(request)
File "C:\Users\augendre\.virtualenvs\bizdev_kb_api-TgTXDkAC\lib\site-packages\django\core\handlers\base.py" in _get_response
126. response = self.process_exception_by_middleware(e, request)
File "C:\Users\augendre\.virtualenvs\bizdev_kb_api-TgTXDkAC\lib\site-packages\django\core\handlers\base.py" in _get_response
124. response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "C:\Users\augendre\.virtualenvs\bizdev_kb_api-TgTXDkAC\lib\site-packages\django\views\decorators\csrf.py" in wrapped_view
54. return view_func(*args, **kwargs)
File "C:\Users\augendre\.virtualenvs\bizdev_kb_api-TgTXDkAC\lib\site-packages\django\views\generic\base.py" in view
68. return self.dispatch(request, *args, **kwargs)
File "C:\Users\augendre\.virtualenvs\bizdev_kb_api-TgTXDkAC\lib\site-packages\rest_framework\views.py" in dispatch
495. response = self.handle_exception(exc)
File "C:\Users\augendre\.virtualenvs\bizdev_kb_api-TgTXDkAC\lib\site-packages\rest_framework\views.py" in handle_exception
455. self.raise_uncaught_exception(exc)
File "C:\Users\augendre\.virtualenvs\bizdev_kb_api-TgTXDkAC\lib\site-packages\rest_framework\views.py" in dispatch
492. response = handler(request, *args, **kwargs)
File "C:\Users\augendre\.virtualenvs\bizdev_kb_api-TgTXDkAC\lib\site-packages\rest_framework\generics.py" in get
284. return self.retrieve(request, *args, **kwargs)
File "C:\Users\augendre\.virtualenvs\bizdev_kb_api-TgTXDkAC\lib\site-packages\rest_framework\mixins.py" in retrieve
57. serializer = self.get_serializer(instance)
File "C:\Users\augendre\.virtualenvs\bizdev_kb_api-TgTXDkAC\lib\site-packages\rest_framework\generics.py" in get_serializer
112. return serializer_class(*args, **kwargs)
File ".\api\serializers.py" in __init__
230. super().__init__(*args, **kwargs)
File "C:\Users\augendre\.virtualenvs\bizdev_kb_api-TgTXDkAC\lib\site-packages\rest_flex_fields\serializers.py" in __init__
47. name, next_expand_fields, next_sparse_fields, next_omit_fields
File "C:\Users\augendre\.virtualenvs\bizdev_kb_api-TgTXDkAC\lib\site-packages\rest_flex_fields\serializers.py" in _make_expanded_field_serializer
58. serializer_settings = copy.deepcopy(field_options[1])
File "C:\Users\augendre\.virtualenvs\bizdev_kb_api-TgTXDkAC\lib\copy.py" in deepcopy
150. y = copier(x, memo)
File "C:\Users\augendre\.virtualenvs\bizdev_kb_api-TgTXDkAC\lib\copy.py" in _deepcopy_dict
240. y[deepcopy(key, memo)] = deepcopy(value, memo)
File "C:\Users\augendre\.virtualenvs\bizdev_kb_api-TgTXDkAC\lib\copy.py" in deepcopy
150. y = copier(x, memo)
File "C:\Users\augendre\.virtualenvs\bizdev_kb_api-TgTXDkAC\lib\copy.py" in _deepcopy_dict
240. y[deepcopy(key, memo)] = deepcopy(value, memo)
File "C:\Users\augendre\.virtualenvs\bizdev_kb_api-TgTXDkAC\lib\copy.py" in deepcopy
180. y = _reconstruct(x, memo, *rv)
File "C:\Users\augendre\.virtualenvs\bizdev_kb_api-TgTXDkAC\lib\copy.py" in _reconstruct
281. if hasattr(y, '__setstate__'):
File "C:\Users\augendre\.virtualenvs\bizdev_kb_api-TgTXDkAC\lib\site-packages\rest_framework\request.py" in __getattr__
412. return getattr(self._request, attr)
File "C:\Users\augendre\.virtualenvs\bizdev_kb_api-TgTXDkAC\lib\site-packages\rest_framework\request.py" in __getattr__
412. return getattr(self._request, attr)
File "C:\Users\augendre\.virtualenvs\bizdev_kb_api-TgTXDkAC\lib\site-packages\rest_framework\request.py" in __getattr__
412. return getattr(self._request, attr)
[...]
Exception Type: RecursionError at /api/client/5906
Exception Value: maximum recursion depth exceeded
Request information:
USER: ****
GET:
expand = 'projects_details'
fields = 'id,name,projects_details.id'
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.