Repository: encode/django-rest-framework

Issue:
LimitOffset pagination crashes Browseable API when limit=0
We are using `LimitOffsetPagination` on all our endpoints, and noticed that sending `limit=0` to the browsable api causes a crash.  Getting `json` output (either from an xmlhttprequest or from `format=json`) does not crash.  
## Steps to reproduce
1. Install `LimitOffsetPagination` as the default pagination
2. Open a `list` (ours are `ModelViewSet`) in the browser
3. Set the query string to `?limit=0&offset=20`
4. _boom_

_Notes_
We use a custom LimitOffset pagination, which just overrides the max and default limit
Using DRF 3.3.2
## Expected behavior

Should show the browsable api for the endpoint, with the following data:

```
{
    "count":148,
    "next":"https://api.co/page/?limit=0&offset=20",
    "previous":"https://api.co/page/?limit=0&offset=20",
    "results":[]
}
```
## Actual behavior

```
Exception Type: ZeroDivisionError
Exception Value:    
integer division or modulo by zero
Exception Location: .../venv/local/lib/python2.7/site-packages/rest_framework/pagination.py in _divide_with_ceil, line 41
Python Executable:  /usr/local/bin/uwsgi

.../venv/local/lib/python2.7/site-packages/rest_framework/pagination.py in get_html_context
    current = _divide_with_ceil(self.offset, self.limit) + 1 ...

    ▼ Local vars
    self      <mysite.LargePageination object at 0x7f614e677bd0>
    base_url  'https://api.co/page/?limit=0&offset=20'

.../venv/local/lib/python2.7/site-packages/rest_framework/pagination.py in _divide_with_ceil
    if a % b: ...

    ▼ Local vars
    a       20
    b       0
```



Discussion:
I am able to reproduce this behaviour. Also this is not really a crash, it just displays you an error message as you have Debug Mode set to True in your Django Project.

Though I guess there is no harm done in catching this and just returning an empty result.

Thanks for confirming @anx-ckreuzberger!
Agree - this ought to be resolved.

Thanks for confirming too.  It's nice to know I'm not crazy (though I did try it on a fresh project).  Not sure how it's not a crash, it's a `div by 0`.  The stack is well protected enough that this sends back a 500, so I guess it's not a core dump ;)  Well, no core dump, just an uncaught exception popping up in Sentry.


Resolving patch:
diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py
--- a/rest_framework/pagination.py
+++ b/rest_framework/pagination.py
@@ -36,10 +36,11 @@ def _positive_int(integer_string, strict=False, cutoff=None):
 
 def _divide_with_ceil(a, b):
     """
-    Returns 'a' divded by 'b', with any remainder rounded up.
+    Returns 'a' divided by 'b', with any remainder rounded up.
     """
     if a % b:
         return (a // b) + 1
+
     return a // b
 
 
@@ -358,17 +359,24 @@ def get_previous_link(self):
 
     def get_html_context(self):
         base_url = self.request.build_absolute_uri()
-        current = _divide_with_ceil(self.offset, self.limit) + 1
-        # The number of pages is a little bit fiddly.
-        # We need to sum both the number of pages from current offset to end
-        # plus the number of pages up to the current offset.
-        # When offset is not strictly divisible by the limit then we may
-        # end up introducing an extra page as an artifact.
-        final = (
-            _divide_with_ceil(self.count - self.offset, self.limit) +
-            _divide_with_ceil(self.offset, self.limit)
-        )
-        if final < 1:
+
+        if self.limit:
+            current = _divide_with_ceil(self.offset, self.limit) + 1
+
+            # The number of pages is a little bit fiddly.
+            # We need to sum both the number of pages from current offset to end
+            # plus the number of pages up to the current offset.
+            # When offset is not strictly divisible by the limit then we may
+            # end up introducing an extra page as an artifact.
+            final = (
+                _divide_with_ceil(self.count - self.offset, self.limit) +
+                _divide_with_ceil(self.offset, self.limit)
+            )
+
+            if final < 1:
+                final = 1
+        else:
+            current = 1
             final = 1
 
         if current > final:


Test patch:
diff --git a/tests/test_pagination.py b/tests/test_pagination.py
--- a/tests/test_pagination.py
+++ b/tests/test_pagination.py
@@ -505,6 +505,31 @@ def test_max_limit(self):
         assert content.get('next') == next_url
         assert content.get('previous') == prev_url
 
+    def test_limit_zero(self):
+        """
+        A limit of 0 should return empty results.
+        """
+        request = Request(factory.get('/', {'limit': 0, 'offset': 10}))
+        queryset = self.paginate_queryset(request)
+        context = self.get_html_context()
+        content = self.get_paginated_content(queryset)
+
+        assert context == {
+            'previous_url': 'http://testserver/?limit=0&offset=10',
+            'page_links': [
+                PageLink(
+                    url='http://testserver/?limit=0',
+                    number=1,
+                    is_active=True,
+                    is_break=False
+                )
+            ],
+            'next_url': 'http://testserver/?limit=0&offset=10'
+        }
+
+        assert queryset == []
+        assert content.get('results') == []
+
 
 class TestCursorPagination:
     """