OhMad
OhMad

Reputation: 7289

Django - Generic Detail View must be called with either an object pk or slug when running tests

I have searched through other questions regarding the same error message, but it seems as if it should work for me (I am using Django 2 btw.)

class Book(models.Model):
    name = models.CharField(max_length=200)
    slug = models.SlugField(default='')

    def save(self, *args, **kwargs):
        self.slug = self.name.lower().replace(' ', '_')

my path:

path('<slug:slug>', views.BookDetailView.as_view(), name='book-detail'),

the view:

class BookDetailView(DetailView):
    model = Book
    template_name = 'books/bookdetail.html'
    context_object_name = 'book'

Still, it's throwing the error message when running the tests (it works when requesting the view normally through a browser).

Here is the appropriate test function (uses RequestFactory and mixer)

class TestBookDetailView:
    def test_access(self):
        mixer.blend('books.Book', name='The Book')
        req = RequestFactory().get('/books/the_book')
        req.user = mixer.blend(User)
        resp = BookDetailView.as_view()(req)
        assert resp.status_code == 200, 'Should display detail view'

Here is the traceback:

_______________________ TestBookDetailView.test_access ________________________

self = <books.tests.test_books_views.TestBookDetailView object at 0x000001C8E3577240>

    def test_access(self):
        mixer.blend('books.Book', name='The Book')
        req = RequestFactory().get('/books/the_book')
        req.user = mixer.blend(User)
>       resp = BookDetailView.as_view()(req)

books\tests\test_books_views.py:29:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
..\..\envs\booknote\lib\site-packages\django\views\generic\base.py:69: in view
    return self.dispatch(request, *args, **kwargs)
..\..\envs\booknote\lib\site-packages\django\utils\decorators.py:62: in _wrapper
    return bound_func(*args, **kwargs)
..\..\envs\booknote\lib\site-packages\django\contrib\auth\decorators.py:21: in _wrapped_view
    return view_func(request, *args, **kwargs)
..\..\envs\booknote\lib\site-packages\django\utils\decorators.py:58: in bound_func
    return func.__get__(self, type(self))(*args2, **kwargs2)
..\..\envs\booknote\lib\site-packages\django\views\generic\base.py:89: in dispatch
    return handler(request, *args, **kwargs)
..\..\envs\booknote\lib\site-packages\django\views\generic\detail.py:105: in get
    self.object = self.get_object()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <books.views.BookDetailView object at 0x000001C8E357CD68>
queryset = <QuerySet [<Book: Book object (1)>]>

    def get_object(self, queryset=None):
        """
            Return the object the view is displaying.

            Require `self.queryset` and a `pk` or `slug` argument in the URLconf.
            Subclasses can override this to return any object.
            """
        # Use a custom queryset if provided; this is required for subclasses
        # like DateDetailView
        if queryset is None:
            queryset = self.get_queryset()

        # Next, try looking up by primary key.
        pk = self.kwargs.get(self.pk_url_kwarg)
        slug = self.kwargs.get(self.slug_url_kwarg)
        if pk is not None:
            queryset = queryset.filter(pk=pk)

        # Next, try looking up by slug.
        if slug is not None and (pk is None or self.query_pk_and_slug):
            slug_field = self.get_slug_field()
            queryset = queryset.filter(**{slug_field: slug})

        # If none of those are defined, it's an error.
        if pk is None and slug is None:
            raise AttributeError("Generic detail view %s must be called with "
                                 "either an object pk or a slug."
>                                % self.__class__.__name__)
E           AttributeError: Generic detail view BookDetailView must be called with either an object pk or a slug.

..\..\envs\booknote\lib\site-packages\django\views\generic\detail.py:47: AttributeError

What have I done wrong?

Thanks in Advance

Upvotes: 1

Views: 3425

Answers (1)

Daniel Roseman
Daniel Roseman

Reputation: 599600

You need to pass the URL argument explicitly in the test.

resp = BookDetailView.as_view()(req, slug="the_book"))

Or instead of all that mucking around with request factories, use the built in test client:

self.client.force_login(user)
resp = self.client.get("/books/the_book")

Upvotes: 3

Related Questions