Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using ViewSet.as_view() in test results in AttributeError #2171

Closed
tdavis opened this issue Dec 1, 2014 · 8 comments
Closed

Using ViewSet.as_view() in test results in AttributeError #2171

tdavis opened this issue Dec 1, 2014 · 8 comments

Comments

@tdavis
Copy link
Contributor

tdavis commented Dec 1, 2014

I feel like I must be doing something stupid since this is all over the documentation, but it has never worked for me:

view = MyViewSet.as_view()
uri = reverse(...)
request = my_APIFactory.get(uri)
response = view(request)

This test (with zero-argument call to as_view()) always fails with the TB:

   def view(request, *args, **kwargs):
       self = cls(**initkwargs)
       # We also store the mapping of request methods to actions,
       # so that we can later set the action attribute.
       # eg. `self.action = 'list'` on an incoming GET request.
       self.action_map = actions

       # Bind methods to actions
       # This is the bit that's different to a standard view
>     for method, action in actions.items():
           handler = getattr(self, action)
E     AttributeError: 'NoneType' object has no attribute 'items'

eggs/djangorestframework-3.0.0-py3.4.egg/rest_framework/viewsets.py:70: AttributeError

I mean, this makes sense looking at the code: the actions argument defaults to None but gets no other value, and items() is eventually called on it. What am I missing? How can actions be optional?

@LilyFoote
Copy link
Contributor

Have you tried something like:

view = MyViewSet.as_view({'get': 'retrieve'})
uri = reverse(...)
request = my_APIFactory.get(uri)
response = view(request)

The viewset docs might be helpful too.

@tomchristie
Copy link
Member

Surprised it actually worked at all without following @Ian-Foote's suggestion... May be that we should ensure .as_view on a ViewSet raises an error if it's not passed the method-> action mapping?

@BrickXu
Copy link
Contributor

BrickXu commented Dec 2, 2014

@tomchristie agree with you.

@tdavis
Copy link
Contributor Author

tdavis commented Dec 2, 2014

Sorry to confuse: it doesn't work, at all, without passing the action mapping directly. However, doing so makes it easy for the test to diverge from the reality of the routed view. So I've taken to using get_routes() and constructing the dictionary from the result of that, essentially how get_urls() does.

Is it just me, or does the view-calling testing strategy seem rather clunky? I end up passing args/kwargs to reverse and then directly to the view; the viewset is unable to dispatch requests before it has been modified by the router. Using as_view() feels like the worst of both worlds; either I should just call MyViewset().retrieve() or whatever, or I must accept the extra indirection of the http-style Client requests to keep things DRY and consistent with the eventual reality.

@tomchristie
Copy link
Member

it doesn't work, at all, without passing the action mapping directly.

What do you get then, in this case?

@tdavis
Copy link
Contributor Author

tdavis commented Dec 2, 2014

The AttributeError I posted in the original comment.

@BrickXu
Copy link
Contributor

BrickXu commented Dec 3, 2014

@tdavis ,

Actually, the examples that you posted was told in the document of ViewSet, you can look at link[0], here is refs:

If we need to, we can bind this viewset into two separate views, like so:

user_list = UserViewSet.as_view({'get': 'list'})
user_detail = UserViewSet.as_view({'get': 'retrieve'})

Typically we wouldn't do this, but would instead register the viewset with a router, and allow the urlconf to be automatically generated.

from myapp.views import UserViewSet
from rest_framework.routers import DefaultRouter

router = DefaultRouter()
router.register(r'users', UserViewSet)
urlpatterns = router.urls

you can use as_view directly(but it is not recommended) and do not forget to specify the actions dict, it's very important! ViewSet need it to mapping the HTTP METHOD and process method. but if you do not do that, ViewSet will not know how to mapping it.

Here is a list of mapping relationship by default:

GET => list(resource collection) or retrieve(single resource)
POST => create
DELETE => destroy
PUT => update

You can custom the mapping by overwriting SimpleRouter.routes attribute.

The AttributeError you posted, I think it is a bug for as_view, it does not validate the actions is None or empty, should be raised a ``TypeError` with helpful tips here.

The `actions` argument must be provided when calling `.as_view()` on a ViewSet. For example `.as_view({'get': 'list'})`

Is it clear for you now?

link:
[0] http://www.django-rest-framework.org/api-guide/viewsets/

@tomchristie
Copy link
Member

Thanks for the report, now resolved as #2175.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants