chibisov / drf-extensions Goto Github PK
View Code? Open in Web Editor NEWDRF-extensions is a collection of custom extensions for Django REST Framework
Home Page: http://chibisov.github.io/drf-extensions/docs
License: MIT License
DRF-extensions is a collection of custom extensions for Django REST Framework
Home Page: http://chibisov.github.io/drf-extensions/docs
License: MIT License
When using the ExtendedSimpleRouter, list_route won't route the traffic correctly. Instead of using /viewSet/methods
it uses /viewSet/pk/methods
.
The list_route and detail_route decorators where added in DRF 2.4 to replace the @action and @link decorators. There much more flexible and allow routing requests to list views or detailed views. There are extremely helpful when requesting the current user or a list of all users.
If anyone knows a workaround until this issue is fixed, I'd highly appreciate it.
What do you think about adding something along the lines of encode/django-rest-framework#1964 to drf-extensions?
@thedrow offers a pretty good write-up on a workable approach: encode/django-rest-framework#1964 (comment)
class InspectionItemViewSet(ListETAGMixin, viewsets.ModelViewSet):
model = InspectionItem
filter_fields = ('property', )
If you access the API with e.g. /api/v1/inspectionitems/?property=(null) ... this throws an EmptyQuerySet exception here:
class ListSqlQueryKeyBit(KeyBitBase):
def get_data(self, params, view_instance, view_method, request, args, kwargs):
return force_text(
view_instance.filter_queryset(view_instance.get_queryset()).query.__str__()
Should be a regression, refer #3
Привет, перепробовал кучу вещей в инете для nested drf routing, остановился на твоем пакете.
У тебя отсутствует генератор полей-ссылок на nested-urls (у базового не хватает kwargs для reverse).
class HyperlinkedNestedIdentityField(HyperlinkedIdentityField):
def __init__(self, view_name=None, additional_reverse_kwargs={}, **kwargs):
self.additional_reverse_kwargs = additional_reverse_kwargs
super(HyperlinkedNestedIdentityField, self).__init__(view_name, **kwargs)
def get_url(self, obj, view_name, request, format):
"""
Given an object, return the URL that hyperlinks to the object.
May raise a `NoReverseMatch` if the `view_name` and `lookup_field`
attributes are not configured to correctly match the URL conf.
"""
# Unsaved objects will not yet have a valid URL.
if obj.pk is None:
return None
kwargs = {}
for k, v in self.additional_reverse_kwargs.iteritems():
kwargs[k] = getattr(obj, v, v)
kwargs.update({self.lookup_url_kwarg: getattr(obj, self.lookup_field)})
return self.reverse(view_name, kwargs=kwargs, request=request, format=format)
Пример использования:
class MealRecordSerializer(serializers.HyperlinkedModelSerializer):
url = HyperlinkedNestedIdentityField(view_name='mealrecord-detail', read_only=True,
additional_reverse_kwargs={"parent_lookup_user_id": "user_id"})
class Meta:
model = MealRecord
fields = ('id', 'url', 'user', 'date', 'time', 'title', 'calories_count')
My use case is as followed, I have a Comment model that can attach to any resource
class Comment(models.Model):
"""
"""
content_type = models.ForeignKey(ContentType,
verbose_name=_('content type'),
related_name="content_type_set_for_%(class)s")
object_pk = models.TextField('object ID')
content_object = generic.GenericForeignKey(ct_field="content_type", fk_field="object_pk")
comment = models.TextField('comment')
My router then looks like:
router = ExtendedSimpleRouter()
tasks_router = router.register(r'tasks', views.TaskViewSet)
tasks_router.register(r'comments', views.CommentModelViewSet, base_name='tasks-comment', parents_query_lookups=['content_object'])
This of course fails because it expects one of content_type, object_pk, comment...
I made an attempt to fix it, but getting the parent's model is not straight forward, any thoughts or ideas?
from rest_framework_extensions.mixins import NestedViewSetMixin as DRFNestedViewSetMixin
from rest_framework_extensions.settings import extensions_api_settings
class NestedViewSetMixin(DRFNestedViewSetMixin):
"""
Small extension of DRFNestedViewSetMixin to support GenericForeignKey
"""
def get_parents_query_contenttype(self):
#TODO: there must be a better way to get the parent viewset????
path = '/'.join(self.request._request.path.split('/')[:-3])+'/'
parent_viewset = resolve(path).func.cls
ct = ContentType.objects.get_for_model(parent_viewset.queryset.model)
return ct
def get_parents_query_dict(self):
qs = super(DRFNestedViewSetMixin, self).get_queryset()
result1 = super(NestedViewSetMixin, self).get_parents_query_dict()
result= {}
for query_lookup, query_value in result1.items():
field = getattr(qs.model, query_lookup)
if field.__class__.__name__ == 'GenericForeignKey':
result[field.fk_field] = query_value
result[field.ct_field] = self.get_parents_query_contenttype()
else:
result[query_lookup] = query_value
return result
I wanted to update Travis/tox configs, but saw that drf-extensions
supports a bunch of old versions. Based on discussion here and here and also here, proposal is to change supported versions to:
This would allow to greatly streamline the build matrix, and make the supported versions more clear and concise.
How is this done?
Reported by @acastellana.
In DRF 2.4.4 neither @link(is_for_list=True) nor @action(is_for_list=True) work properly. Any advance on this issue?
Thanks!
Update. Seems that DRF 2.4.4 already has a @list_route() decorator
Is looks like using the NestedViewSetMixin
auto generates the filter code for nested routers, but does nothing for saving.
I would recommend something like the following for a URL like companies/company_pk/locations/location_pk
:
def pre_save(self, obj):
obj.location = Location.objects.get(pk=self.kwargs['location_slug'],
company__pk=self.kwargs['company_pk']
)
to be auto generated so that a user posting to the above URL can omit those fields which are determined by the URL itself.
for filter_expr, value in filters.items():
# example: author__id__in=1,2,3
filter_bits = filter_expr.split(LOOKUP_SEP) # => ['author', 'id', 'in']
field_name = filter_bits.pop(0) # => 'author'
filter_type = 'exact'
if not field_name in serializer_class.Meta.fields: # should use get_fields
# It's not a field we know about. Move along citizen.
continue
if filter_bits and filter_bits[-1] in query_terms:
filter_type = filter_bits.pop() # => 'in'
I am using ExtendedDefaultRouter
with nested routes. Right now my root view is being polluted by the sub items:
GET /api/
{
"people": "http://127.0.0.1:8000/api/people/",
"people/(?P<parent_lookup_person__uuid>[^/.]+)/tests": "http://127.0.0.1:8000/api/tests/",
"tests": "http://127.0.0.1:8000/api/tests/"
}
Let we have the following models
class Book(models.Model):
id = models.CharField()
class Page(models.Model):
book = models.ForeignKey('Book')
and there is no books and no pages in the db. The URL to list pages related to certain book is /books/<BookID>/pages/
.
How to reproduce:
perform GET request to /books/999/pages/
Expected result
404 code is returned because there is no book with id 999
Actual result
Empty list is returned.
As far as I understand the default functionality allows to use ResourceUriField
to point only to a detail
view. In my use-case I want something like this:
class PostDetailSerializer(ModelSerializer):
url = ResourceUriField(view_name='post-detail')
comments_url = ResourceUriField(view_name='post-comments-list')
Of course, I could just implement my own field, but wouldn't this be useful functionality in drf-extensions
?
ExtendedDefaultRouter. get_dynamic_routes
should call getattr()
with default values, so the original @action
decorator still works with the ExtendedDefaultRouter
.
https://github.com/chibisov/drf-extensions/blob/master/rest_framework_extensions/routers.py#L107
Currently DRF does the following:
def register(self, prefix, viewset, base_name=None):
if base_name is None:
base_name = self.get_default_base_name(viewset)
Why not do the following? This should work.
I'm having a trouble to get etag decorator working. Generated etags don't change after updating the corresponding contents (responses from drf views are updated too).
By looking at the code in drf-extensions, the default behaviour of the default_etag_func seems to be calculating etags based on requests. Wouldn't it supposed to be based on responses?
Before attempting to write a custom etag_func, I would like to make sure that I'm not missing anything.
I'm testing with following versions.
Hi - great package, thanks!
I am wondering why the KeyConstructor bits are class variables not instance? It means that it is hard to reuse KeyContructors across different viewsets when only only perhaps the GET param needs changing.
It would ease reuse to be able to specify params when the instance is constructed, as something like
class CityView(views.APIView):
@cache_response(key_func=CityGetKeyConstructor(params={'request_meta': ['GEOIP_CITY']}))
def get(self, request, *args, **kwargs):
class CountryView(views.APIView):
@cache_response(key_func=CityGetKeyConstructor(params={'request_meta': ['GEOIP_COUNTRY']}))
def get(self, request, *args, **kwargs):
Along the lines of DetailSerializerMixin
:
class SerializerAPI(object):
"""
Mixin for using different Serializer for browsable API.
"""
def get_serializer_class(self, *args, **kwargs):
parent = super(SerializerAPI, self).get_serializer_class(*args, **kwargs)
# We use the API serializer when not HEAD/OPTIONS and the render is set to API
if (self.request.method not in ['HEAD', 'OPTIONS']
and hasattr(self.request, 'accepted_renderer')
and self.request.accepted_renderer.format == 'api'):
return self.serializer_class_api
else:
return parent
I'm getting following exception while using DRF 2.4 and Django 1.7:
Traceback (most recent call last):
File "/usr/lib/python3.3/wsgiref/handlers.py", line 137, in run
self.result = application(self.environ, self.start_response)
File "/home/vagrant/.virtualenvs/project/lib/python3.3/site-packages/django/core/handlers/wsgi.py", line 187, in __call__
response = self.get_response(request)
File "/home/vagrant/.virtualenvs/project/lib/python3.3/site-packages/django/core/handlers/base.py", line 210, in get_response
response._closable_objects.append(request)
AttributeError: 'Response' object has no attribute '_closable_objects'
It works alright if I downgrade to DRF 2.3 or remove @cache_response
from given view.
Hi,
There's a small typo (easily overlooked) in the docs, which causes the cache invalidation sample code not to work.
class UpdatedAtKeyBit(KeyBitBase): def get_data(self, **kwargs): key = 'api_update_at_timestamp' value = cache.get('api_updated_at_timestamp', None) if not value: value = datetime.datetime.utcnow() cache.set(key, value=value) return force_text(value)
Should be:
class UpdatedAtKeyBit(KeyBitBase): def get_data(self, **kwargs): key = 'api_updated_at_timestamp' value = cache.get(key, None) if not value: value = datetime.datetime.utcnow() cache.set(key, value=value) return force_text(value)
Caching is working nicely now. Thanks for the package.
Create a migration guide/ "compare" section for those coming over from: https://github.com/alanjds/drf-nested-routers.
so i want to do
item/1/collections/
item/1/colors/
when i do this
router.register(
r'institutes', InstituteViewSet
).register(
r'collections', CollectionViewSet, 'institute-collections', parents_query_lookups='owner_id'
).register(
r'items', ItemViewSet, 'collection-items', parents_query_lookups='owner_id'
)
it gives me this urls
/item/1/collection/3/colors/
which is not what i want .. how do i get it consume two endpoints at the same level
New in DRF 2.4 http://www.django-rest-framework.org/api-guide/viewsets#marking-extra-actions-for-routing
Maybe we need functional tests for compatibility of ExtendedRouter with these new decorators.
Hi!
I noticed that when using a ExtendedSimpleRouter and registering routes that might have a dot "." in their lookup regex will cause the lookup to fail. I presume that the prefix creation here here was made with pk lookups in mind. However, there might be (as in my case) times where dots are a part of the API endpoint. As an example an email address.
I noticed that there is a function here that indicates if dots are to be used in the regex or not. And after DRF 2.4 it seems that dots are not to be matched in URLs (according to that file). However, the way the code is set up right now there is no way of changing the default behavior or the regex lookup. I propose that a optional parameter, parents_query_lookups_regex, be implemented in the register() function, which would give the caller more flexibility when it comes to matching routes.
I'm quite new with the code base so please excuse me if I have overlooked some part of the router setup or if I'm making a complete ass out of myself asking for such implementations :)
If the lookup_field
of the ViewSet pointed to by a nested route is not set to pk
then the parents_query_lookups
should end with __<lookup_field>
:
router.register('people', views.PersonViewSet)
.register(r'tests', views.TestUnderPersonViewSet, base_name="test",
parents_query_lookups=["person__uuid"])
where TestUnderPersonViewSet
has its lookup_field = 'uuid'
.
It would be nice if you could include the license text of the BSD license variant that you prefer. This would make it possible to distribute your package as a Debian package.
As suggested in another issue comment I tried:
from rest_framework.routers import DefaultRouter as _DefaultRouter
from rest_framework_extensions.routers import ExtendedActionLinkRouterMixin
from rest_framework_nested import nested_routers
class DefaultRouter(ExtendedActionLinkRouterMixin, _DefaultRouter):
pass
class NestedSimpleRouter(ExtendedActionLinkRouterMixin, nested_routers.NestedSimpleRouter):
pass
apirouter = DefaultRouter(trailing_slash=False)
apirouter.register(r'parentresource', ParentResourceViewSet, 'parentresource')
parentresource_nested_router = NestedSimpleRouter(
apirouter, r'parentresource', lookup='parentresource',
trailing_slash=False)
parentresource_nested_router.register(r'nestedresource', NestedResourceViewSet, 'nestedresource')
urlpatterns = patterns('',
url(r'^api/', include(apirouter.urls)),
url(r'^api/', include(parentresource_nested_router.urls)),
# ...
And the NestedResourceViewSet
has a custom_collection_operation
method decorated with rest_framework_extensions.decorators.action
, but the nested routes are still registered at the root. This is what I need:
^api/v1/ ^parentresource$ [name='parentresource-list']
^api/v1/ ^parentresource\.(?P<format>[a-z0-9]+)$ [name='parentresource-list']
^api/v1/ ^parentresource/(?P<id>[^/.]+)$ [name='parentresource-detail']
...
^api/v1/ ^parentresource/(?P<parentresource_id>[^/.]+)/nestedresource$ [name='nestedresource-list']
^api/v1/ ^parentresource/(?P<parentresource_id>[^/.]+)/nestedresource/(?P<id>[^/]+)$ [name='nestedresource-detail']
^api/v1/ ^parentresource/(?P<parentresource_id>[^/.]+)/nestedresource/custom_collection_operation$ [name='nestedresource-custom-collection-operation']
But they are generated at the root:
^api/ ^parentresource$ [name='parentresource-list']
^api/ ^parentresource\.(?P<format>[a-z0-9]+)$ [name='parentresource-list']
^api/ ^parentresource/(?P<id>[^/.]+)$ [name='parentresource-detail']
...
^api/ ^nestedresource/custom_collection_operation$ [name='nestedresource-custom-operation-list']
^api/ ^nestedresource$ [name='nestedresource-list']
^api/ ^nestedresource/(?P<id>[^/]+)$ [name='nestedresource-detail']
Right now I feel that in order to use HyperlinkedIdentityField
I end up repeating myself.
employees = serializers.HyperlinkedIdentityField(view_name='employee-list', lookup_field='slug',
slug_url_kwarg='parent_lookup_institution__slug')
My urls.py looks like:
institutions_router = router.register('institutions', views.InstitutionViewSet)
institutions_router.register('employees', views.EmployeeViewSetUnderInstitution,
base_name="employee", parents_query_lookups=["institution__slug"])
Is there a smarter / cleaner way of doing this?
drf-nested-routers provides (incomplete) nested versions of hyperlinked fields (https://github.com/alanjds/drf-nested-routers/blob/master/rest_framework_nested/relations.py).
It be awesome to have those in drf-extensions.
DefaultRouter tries to reverse nested routes and breaks with error:
Reverse for 'action-categories-action-list' with arguments '()' and keyword arguments '{}' not found.
Something like bare django cache decorator, in drf-extensions:
@cache_response(cache='other_than_default')
Seems reasonable?
I think it's useful to generate cache keys based on the positional and/or keyword method arguments for the decorated method. In particular, if I have a view for a URL that's like GET /cities/(?P<user_id>)/
, then Django will pass a kwarg called user_id
to my view.
I see that there are key bits for params and request. Why not args or kwargs? Please see my pull request for a simple implementation.
I'm coming across two problems:
Can you think of a strategy to deal with either of these issues?
Consider adding the following to viewsets:
class ListModelViewSet(mixins.ListModelMixin,
GenericViewSet):
"""
A viewset that provides default `list()` actions.
"""
pass
This would complement ReadOnlyModelViewSet
.
Hi,
I'm getting this error using PartialUpdateSerializerMixin with DRF 3.0:
AssertionError: Serializer tasks.serializers.task.TaskSerializer
has old-style version 2 .save_object()
that is no longer compatible with REST framework 3. Use the new-style .create()
and .update()
methods instead.
Is this a bug?
Thanks in advance
Just wondering whether it makes sense, or there's scope, to add a mixin that includes the ETag for each result in a list response, say as a key in the JSON– I essentially want to get a bunch of a results, and use the ETag from each to If-Match PATCH them.
Is that a sensible approach? Is it easy to add? Should there be a mixin in this repo to add that?
There is a mixin for etags, which is really useful, but in many cases it would be better to deal with caching based on a date. In a lot of our data, we store the last time something was modified which means we would be able to use the last-modified
header for caching purposes.
Other release have dates: http://chibisov.github.io/drf-extensions/docs/#release-notes
We ran into a minor snag with using DetailSerializerMixin
. When POST
'ing to a list URL, you are returned the detail serializer representation, and therefore you see a weird thing like this:
So as you see, we are still on the /posts/
URL, but we see a post instance and the form on the bottom is also for the post instance. Even more, pressing the POST button would not work, because some fields would obviously be missing from the detail form that are required for list form.
If you also consider this a bug, then I can probably provide a PR for a more robust solution, because we used an in-house implementation of the same thing, and didn't have this problem, but decided to switch to a packaged solution, but are now kind of holding back because of this.
Support for GitHub style nested routing:
/<userId>/<reposId>/...
this instead of:
/<userId>/repositories/<reposId>/...
HI @chibisov thanks for the great work here, I'm looking to move my nested routers over from DRF Nested routers to this package as it offers some other cool enhancements, but I see that functionality is not available on the latest pypi version.. any ideas on when it will be ? (so i don't have to install this package from github)...
Thanks in advance..
$ tar -tf drf-extensions-0.2.4.tar.gz | grep '\.pyc$'
drf-extensions-0.2.4/rest_framework_extensions/__pycache__/__init__.cpython-33 (2).pyc
drf-extensions-0.2.4/rest_framework_extensions/__pycache__/__init__.cpython-33.pyc
drf-extensions-0.2.4/rest_framework_extensions/__pycache__/__init__.cpython-34.pyc
drf-extensions-0.2.4/rest_framework_extensions/__pycache__/compat.cpython-33.pyc
drf-extensions-0.2.4/rest_framework_extensions/__pycache__/compat.cpython-34.pyc
... etc.
This breaks bdist_egg
:
$ python setup.py bdist_egg
... snip ...
zip_safe flag not set; analyzing archive contents...
Traceback (most recent call last):
File "setup.py", line 77, in <module>
'Topic :: Internet :: WWW/HTTP',
File "/usr/lib/python2.7/distutils/core.py", line 151, in setup
dist.run_commands()
File "/usr/lib/python2.7/distutils/dist.py", line 953, in run_commands
self.run_command(cmd)
File "/usr/lib/python2.7/distutils/dist.py", line 972, in run_command
cmd_obj.run()
File "/usr/lib/python2.7/dist-packages/setuptools/command/bdist_egg.py", line 203, in run
os.path.join(archive_root,'EGG-INFO'), self.zip_safe()
File "/usr/lib/python2.7/dist-packages/setuptools/command/bdist_egg.py", line 239, in zip_safe
return analyze_egg(self.bdist_dir, self.stubs)
File "/usr/lib/python2.7/dist-packages/setuptools/command/bdist_egg.py", line 347, in analyze_egg
safe = scan_module(egg_dir, base, name, stubs) and safe
File "/usr/lib/python2.7/dist-packages/setuptools/command/bdist_egg.py", line 381, in scan_module
code = marshal.load(f)
ValueError: bad marshal data (unknown type code)
Please publish a new release without the .pyc
files.
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.