[prev in list] [next in list] [prev in thread] [next in thread] 

List:       mailman-cvs
Subject:    [Mailman-checkins] [Git][mailman/mailman][release-3.0] Rework pagination to fix the 'start' and 'tot
From:       Barry Warsaw <gitlab () mg ! gitlab ! com>
Date:       2015-11-06 20:28:08
Message-ID: 563d0d58504c9_7c3270699c99685 () worker80 ! cluster ! gitlab ! com ! mail
[Download RAW message or body]

[Attachment #2 (multipart/alternative)]


Barry Warsaw pushed to branch release-3.0 at mailman / Mailman


Commits:
2b2814bb by Aurélien Bompard at 2015-11-06T14:23:39Z
Rework pagination to fix the 'start' and 'total_size' values

- - - - -


11 changed files:

- src/mailman/docs/NEWS.rst
- src/mailman/rest/docs/lists.rst
- src/mailman/rest/docs/membership.rst
- src/mailman/rest/docs/users.rst
- src/mailman/rest/helpers.py
- src/mailman/rest/lists.py
- src/mailman/rest/members.py
- src/mailman/rest/queues.py
- src/mailman/rest/tests/test_lists.py
- src/mailman/rest/tests/test_paginate.py
- src/mailman/rest/users.py


Changes:

=====================================
src/mailman/docs/NEWS.rst
=====================================
--- a/src/mailman/docs/NEWS.rst
+++ b/src/mailman/docs/NEWS.rst
@@ -49,6 +49,8 @@ Bugs
  * Added Trove classifiers to setup.py.  (Closes: #152)
  * Fix the processing of subscription confirmation messages when the mailing
    list is set to confirm-then-moderate.  (Closes #114)
+ * Fix pagination values `start` and `total_size` in the REST API.  Given by
+   Aurélien Bompard.  (Closes: #154)
 
 
 3.0.0 -- "Show Don't Tell"


=====================================
src/mailman/rest/docs/lists.rst
=====================================
--- a/src/mailman/rest/docs/lists.rst
+++ b/src/mailman/rest/docs/lists.rst
@@ -76,7 +76,7 @@ page.
         volume: 1
     http_etag: "..."
     start: 0
-    total_size: 1
+    total_size: 2
 
     >>> dump_json('http://localhost:9001/3.0/domains/example.com/lists'
     ...           '?count=1&page=2')
@@ -91,8 +91,8 @@ page.
         self_link: http://localhost:9001/3.0/lists/bird.example.com
         volume: 1
     http_etag: "..."
-    start: 0
-    total_size: 1
+    start: 1
+    total_size: 2
 
 
 Creating lists via the API


=====================================
src/mailman/rest/docs/membership.rst
=====================================
--- a/src/mailman/rest/docs/membership.rst
+++ b/src/mailman/rest/docs/membership.rst
@@ -267,7 +267,7 @@ page.
         user: http://localhost:9001/3.0/users/3
     http_etag: ...
     start: 0
-    total_size: 1
+    total_size: 2
 
 This works with members of a single list as well as with all members.
 
@@ -285,7 +285,7 @@ This works with members of a single list as well as with all members.
         user: http://localhost:9001/3.0/users/3
     http_etag: ...
     start: 0
-    total_size: 1
+    total_size: 5
 
 
 Owners and moderators


=====================================
src/mailman/rest/docs/users.rst
=====================================
--- a/src/mailman/rest/docs/users.rst
+++ b/src/mailman/rest/docs/users.rst
@@ -84,7 +84,7 @@ page.
         user_id: 1
     http_etag: "..."
     start: 0
-    total_size: 1
+    total_size: 2
 
     >>> dump_json('http://localhost:9001/3.0/users?count=1&page=2')
     entry 0:
@@ -94,8 +94,8 @@ page.
         self_link: http://localhost:9001/3.0/users/2
         user_id: 2
     http_etag: "..."
-    start: 0
-    total_size: 1
+    start: 1
+    total_size: 2
 
 
 Creating users


=====================================
src/mailman/rest/helpers.py
=====================================
--- a/src/mailman/rest/helpers.py
+++ b/src/mailman/rest/helpers.py
@@ -20,6 +20,7 @@
 __all__ = [
     'BadRequest',
     'ChildError',
+    'CollectionMixin',
     'GetterSetter',
     'NotFound',
     'bad_request',
@@ -112,31 +113,6 @@ def etag(resource):
     return json.dumps(resource, cls=ExtendedEncoder)
 
 
-def paginate(method):
-    """Method decorator to paginate through collection result lists.
-
-    Use this to return only a slice of a collection, specified in the request
-    itself.  The request should use query parameters `count` and `page` to
-    specify the slice they want.  The slice will start at index
-    ``(page - 1) * count`` and end (exclusive) at ``(page * count)``.
-
-    Decorated methods must take ``self`` and ``request`` as the first two
-    arguments.
-    """
-    def wrapper(self, request, *args, **kwargs):
-        # Allow falcon's HTTPBadRequest exceptions to percolate up.  They'll
-        # get turned into HTTP 400 errors.
-        count = request.get_param_as_int('count', min=0)
-        page = request.get_param_as_int('page', min=1)
-        result = method(self, request, *args, **kwargs)
-        if count is None and page is None:
-            return result
-        list_start = (page - 1) * count
-        list_end = page * count
-        return result[list_start:list_end]
-    return wrapper
-
-
 
 class CollectionMixin:
     """Mixin class for common collection-ish things."""
@@ -168,22 +144,39 @@ class CollectionMixin:
         """
         raise NotImplementedError
 
+    def _paginate(self, request, collection):
+        """Method to paginate through collection result lists.
+
+        Use this to return only a slice of a collection, specified in
+        the request itself.  The request should use query parameters
+        `count` and `page` to specify the slice they want.  The slice
+        will start at index ``(page - 1) * count`` and end (exclusive)
+        at ``(page * count)``.
+        """
+        # Allow falcon's HTTPBadRequest exceptions to percolate up.  They'll
+        # get turned into HTTP 400 errors.
+        count = request.get_param_as_int('count', min=0)
+        page = request.get_param_as_int('page', min=1)
+        total_size = len(collection)
+        if count is None and page is None:
+            return 0, total_size, collection
+        list_start = (page - 1) * count
+        list_end = page * count
+        return list_start, total_size, collection[list_start:list_end]
+
     def _make_collection(self, request):
         """Provide the collection to the REST layer."""
-        collection = self._get_collection(request)
-        if len(collection) == 0:
-            return dict(start=0, total_size=0)
-        else:
+        start, total_size, collection = self._paginate(
+            request, self._get_collection(request))
+        result = dict(start=start, total_size=total_size)
+        if len(collection) != 0:
             entries = [self._resource_as_dict(resource)
                        for resource in collection]
             # Tag the resources but use the dictionaries.
             [etag(resource) for resource in entries]
             # Create the collection resource
-            return dict(
-                start=0,
-                total_size=len(collection),
-                entries=entries,
-                )
+            result['entries'] = entries
+        return result
 
 
 


=====================================
src/mailman/rest/lists.py
=====================================
--- a/src/mailman/rest/lists.py
+++ b/src/mailman/rest/lists.py
@@ -40,7 +40,7 @@ from mailman.interfaces.subscriptions import ISubscriptionService
 from mailman.rest.listconf import ListConfiguration
 from mailman.rest.helpers import (
     CollectionMixin, GetterSetter, NotFound, bad_request, child, created,
-    etag, no_content, not_found, okay, paginate, path_to)
+    etag, no_content, not_found, okay, path_to)
 from mailman.rest.members import AMember, MemberCollection
 from mailman.rest.post_moderation import HeldMessages
 from mailman.rest.sub_moderation import SubscriptionRequests
@@ -112,7 +112,6 @@ class _ListBase(CollectionMixin):
             self_link=path_to('lists/{0}'.format(mlist.list_id)),
             )
 
-    @paginate
     def _get_collection(self, request):
         """See `CollectionMixin`."""
         return list(getUtility(IListManager))
@@ -230,7 +229,6 @@ class MembersOfList(MemberCollection):
         self._mlist = mailing_list
         self._role = role
 
-    @paginate
     def _get_collection(self, request):
         """See `CollectionMixin`."""
         # Overrides _MemberBase._get_collection() because we only want to
@@ -251,7 +249,6 @@ class ListsForDomain(_ListBase):
         resource = self._make_collection(request)
         okay(response, etag(resource))
 
-    @paginate
     def _get_collection(self, request):
         """See `CollectionMixin`."""
         return list(self._domain.mailing_lists)


=====================================
src/mailman/rest/members.py
=====================================
--- a/src/mailman/rest/members.py
+++ b/src/mailman/rest/members.py
@@ -38,7 +38,7 @@ from mailman.interfaces.user import IUser, UnverifiedAddressError
 from mailman.interfaces.usermanager import IUserManager
 from mailman.rest.helpers import (
     CollectionMixin, NotFound, accepted, bad_request, child, conflict,
-    created, etag, no_content, not_found, okay, paginate, path_to)
+    created, etag, no_content, not_found, okay, path_to)
 from mailman.rest.preferences import Preferences, ReadOnlyPreferences
 from mailman.rest.validator import (
     Validator, enum_validator, subscriber_validator)
@@ -76,7 +76,6 @@ class _MemberBase(CollectionMixin):
             response['user'] = path_to('users/{}'.format(user.user_id.int))
         return response
 
-    @paginate
     def _get_collection(self, request):
         """See `CollectionMixin`."""
         return list(getUtility(ISubscriptionService))


=====================================
src/mailman/rest/queues.py
=====================================
--- a/src/mailman/rest/queues.py
+++ b/src/mailman/rest/queues.py
@@ -29,7 +29,7 @@ from mailman.app.inject import inject_text
 from mailman.interfaces.listmanager import IListManager
 from mailman.rest.helpers import (
     CollectionMixin, bad_request, created, etag, no_content, not_found, okay,
-    paginate, path_to)
+    path_to)
 from mailman.rest.validator import Validator
 from zope.component import getUtility
 
@@ -50,7 +50,6 @@ class _QueuesBase(CollectionMixin):
             self_link=path_to('queues/{}'.format(name)),
             )
 
-    @paginate
     def _get_collection(self, request):
         """See `CollectionMixin`."""
         return sorted(config.switchboards)


=====================================
src/mailman/rest/tests/test_lists.py
=====================================
--- a/src/mailman/rest/tests/test_lists.py
+++ b/src/mailman/rest/tests/test_lists.py
@@ -287,7 +287,7 @@ class TestListPagination(unittest.TestCase):
             'http://localhost:9001/3.0/domains/example.com/lists'
             '?count=1&page=1')
         # There are 6 total lists, but only the first one in the page.
-        self.assertEqual(resource['total_size'], 1)
+        self.assertEqual(resource['total_size'], 6)
         self.assertEqual(resource['start'], 0)
         self.assertEqual(len(resource['entries']), 1)
         entry = resource['entries'][0]
@@ -298,8 +298,8 @@ class TestListPagination(unittest.TestCase):
             'http://localhost:9001/3.0/domains/example.com/lists'
             '?count=1&page=2')
         # There are 6 total lists, but only the first one in the page.
-        self.assertEqual(resource['total_size'], 1)
-        self.assertEqual(resource['start'], 0)
+        self.assertEqual(resource['total_size'], 6)
+        self.assertEqual(resource['start'], 1)
         self.assertEqual(len(resource['entries']), 1)
         entry = resource['entries'][0]
         self.assertEqual(entry['fqdn_listname'], 'bee@example.com')
@@ -309,8 +309,8 @@ class TestListPagination(unittest.TestCase):
             'http://localhost:9001/3.0/domains/example.com/lists'
             '?count=1&page=6')
         # There are 6 total lists, but only the first one in the page.
-        self.assertEqual(resource['total_size'], 1)
-        self.assertEqual(resource['start'], 0)
+        self.assertEqual(resource['total_size'], 6)
+        self.assertEqual(resource['start'], 5)
         self.assertEqual(len(resource['entries']), 1)
         entry = resource['entries'][0]
         self.assertEqual(entry['fqdn_listname'], 'fly@example.com')
@@ -337,6 +337,6 @@ class TestListPagination(unittest.TestCase):
             'http://localhost:9001/3.0/domains/example.com/lists'
             '?count=1&page=7')
         # There are 6 total lists, but only the first one in the page.
-        self.assertEqual(resource['total_size'], 0)
-        self.assertEqual(resource['start'], 0)
+        self.assertEqual(resource['total_size'], 6)
+        self.assertEqual(resource['start'], 6)
         self.assertNotIn('entries', resource)


=====================================
src/mailman/rest/tests/test_paginate.py
=====================================
--- a/src/mailman/rest/tests/test_paginate.py
+++ b/src/mailman/rest/tests/test_paginate.py
@@ -27,7 +27,7 @@ import unittest
 from falcon import HTTPInvalidParam, Request
 from mailman.app.lifecycle import create_list
 from mailman.database.transaction import transaction
-from mailman.rest.helpers import paginate
+from mailman.rest.helpers import CollectionMixin
 from mailman.testing.layers import RESTLayer
 
 
@@ -51,79 +51,84 @@ class TestPaginateHelper(unittest.TestCase):
         with transaction():
             self._mlist = create_list('test@example.com')
 
+    def _get_resource(self):
+        class Resource(CollectionMixin):
+            def _get_collection(self, request):
+                return ['one', 'two', 'three', 'four', 'five']
+            def _resource_as_dict(self, res):
+                return {'value': res}
+        return Resource()
+
     def test_no_pagination(self):
         # When there is no pagination params in the request, all 5 items in
         # the collection are returned.
-        @paginate
-        def get_collection(self, request):
-            return ['one', 'two', 'three', 'four', 'five']
+        resource = self._get_resource()
         # Expect 5 items
-        page = get_collection(None, _FakeRequest())
-        self.assertEqual(page, ['one', 'two', 'three', 'four', 'five'])
+        page = resource._make_collection(_FakeRequest())
+        self.assertEqual(page['start'], 0)
+        self.assertEqual(page['total_size'], 5)
+        self.assertEqual(
+            [entry['value'] for entry in page['entries']],
+            ['one', 'two', 'three', 'four', 'five'])
 
     def test_valid_pagination_request_page_one(self):
         # ?count=2&page=1 returns the first page, with two items in it.
-        @paginate
-        def get_collection(self, request):
-            return ['one', 'two', 'three', 'four', 'five']
-        page = get_collection(None, _FakeRequest(2, 1))
-        self.assertEqual(page, ['one', 'two'])
+        resource = self._get_resource()
+        page = resource._make_collection(_FakeRequest(2, 1))
+        self.assertEqual(page['start'], 0)
+        self.assertEqual(page['total_size'], 5)
+        self.assertEqual(
+            [entry['value'] for entry in page['entries']], ['one', 'two'])
 
     def test_valid_pagination_request_page_two(self):
         # ?count=2&page=2 returns the second page, where a page has two items
         # in it.
-        @paginate
-        def get_collection(self, request):
-            return ['one', 'two', 'three', 'four', 'five']
-        page = get_collection(None, _FakeRequest(2, 2))
-        self.assertEqual(page, ['three', 'four'])
+        resource = self._get_resource()
+        page = resource._make_collection(_FakeRequest(2, 2))
+        self.assertEqual(page['start'], 2)
+        self.assertEqual(page['total_size'], 5)
+        self.assertEqual(
+            [entry['value'] for entry in page['entries']], ['three', 'four'])
 
     def test_2nd_index_larger_than_total(self):
         # ?count=2&page=3 returns the third page with page size 2, but the
         # last page only has one item in it.
-        @paginate
-        def get_collection(self, request):
-            return ['one', 'two', 'three', 'four', 'five']
-        page = get_collection(None, _FakeRequest(2, 3))
-        self.assertEqual(page, ['five'])
+        resource = self._get_resource()
+        page = resource._make_collection(_FakeRequest(2, 3))
+        self.assertEqual(page['start'], 4)
+        self.assertEqual(page['total_size'], 5)
+        self.assertEqual(
+            [entry['value'] for entry in page['entries']], ['five'])
 
     def test_out_of_range_returns_empty_list(self):
         # ?count=2&page=4 returns the fourth page, which doesn't exist, so an
         # empty collection is returned.
-        @paginate
-        def get_collection(self, request):
-            return ['one', 'two', 'three', 'four', 'five']
-        page = get_collection(None, _FakeRequest(2, 4))
-        self.assertEqual(page, [])
+        resource = self._get_resource()
+        page = resource._make_collection(_FakeRequest(2, 4))
+        self.assertEqual(page['start'], 6)
+        self.assertEqual(page['total_size'], 5)
+        self.assertNotIn('entries', page)
 
     def test_count_as_string_returns_bad_request(self):
         # ?count=two&page=2 are not valid values, so a bad request occurs.
-        @paginate
-        def get_collection(self, request):
-            return []
-        self.assertRaises(HTTPInvalidParam, get_collection,
-                          None, _FakeRequest('two', 1))
+        resource = self._get_resource()
+        self.assertRaises(HTTPInvalidParam, resource._make_collection,
+                          _FakeRequest('two', 1))
 
     def test_negative_count(self):
         # ?count=-1&page=1
-        @paginate
-        def get_collection(self, request):
-            return ['one', 'two', 'three', 'four', 'five']
-        self.assertRaises(HTTPInvalidParam, get_collection,
-                          None, _FakeRequest(-1, 1))
+        resource = self._get_resource()
+        self.assertRaises(HTTPInvalidParam, resource._make_collection,
+                          _FakeRequest(-1, 1))
 
     def test_negative_page(self):
         # ?count=1&page=-1
-        @paginate
-        def get_collection(self, request):
-            return ['one', 'two', 'three', 'four', 'five']
-        self.assertRaises(HTTPInvalidParam, get_collection,
-                          None, _FakeRequest(1, -1))
+        resource = self._get_resource()
+        self.assertRaises(HTTPInvalidParam, resource._make_collection,
+                          _FakeRequest(1, -1))
 
     def test_negative_page_and_count(self):
         # ?count=1&page=-1
-        @paginate
-        def get_collection(self, request):
-            return ['one', 'two', 'three', 'four', 'five']
-        self.assertRaises(HTTPInvalidParam, get_collection,
-                          None, _FakeRequest(-1, -1))
+        resource = self._get_resource()
+        self.assertRaises(HTTPInvalidParam, resource._make_collection,
+                          _FakeRequest(-1, -1))


=====================================
src/mailman/rest/users.py
=====================================
--- a/src/mailman/rest/users.py
+++ b/src/mailman/rest/users.py
@@ -35,8 +35,7 @@ from mailman.interfaces.usermanager import IUserManager
 from mailman.rest.addresses import UserAddresses
 from mailman.rest.helpers import (
     BadRequest, CollectionMixin, GetterSetter, NotFound, bad_request, child,
-    conflict, created, etag, forbidden, no_content, not_found, okay, paginate,
-    path_to)
+    conflict, created, etag, forbidden, no_content, not_found, okay, path_to)
 from mailman.rest.preferences import Preferences
 from mailman.rest.validator import (
     PatchValidator, Validator, list_of_strings_validator)
@@ -137,7 +136,6 @@ class _UserBase(CollectionMixin):
             resource['display_name'] = user.display_name
         return resource
 
-    @paginate
     def _get_collection(self, request):
         """See `CollectionMixin`."""
         return list(getUtility(IUserManager).users)
@@ -448,7 +446,6 @@ class OwnersForDomain(_UserBase):
             self._domain.remove_owner(email)
         return no_content(response)
 
-    @paginate
     def _get_collection(self, request):
         """See `CollectionMixin`."""
         return list(self._domain.owners)



View it on GitLab: https://gitlab.com/mailman/mailman/commit/2b2814bbe7bff7377b859af5d5f5d2dd12121f6e

[Attachment #5 (text/html)]

<html lang='en'>
<head>
<meta content='text/html; charset=utf-8' http-equiv='Content-Type'>
<title>
GitLab
</title>
</meta>
</head>
<style>
  img {
    max-width: 100%;
    height: auto;
  }
  p.details {
    font-style:italic;
    color:#777
  }
  .footer p {
    font-size:small;
    color:#777
  }
  pre.commit-message {
    white-space: pre-wrap;
  }
  .file-stats a {
    text-decoration: none;
  }
  .file-stats .new-file {
    color: #090;
  }
  .file-stats .deleted-file {
    color: #B00;
  }
</style>
<body>
<div class='content'>
<h3>Barry Warsaw pushed to branch release-3.0 at <a \
href="https://gitlab.com/mailman/mailman">mailman / Mailman</a></h3> <h4>
Commits:
</h4>
<ul>
<li>
<strong><a href="https://gitlab.com/mailman/mailman/commit/2b2814bbe7bff7377b859af5d5f5d2dd12121f6e">2b2814bb</a></strong>
 <div>
<span>by Aurélien Bompard</span>
<i>at 2015-11-06T14:23:39Z</i>
</div>
<pre class='commit-message'>Rework pagination to fix the 'start' and 'total_size' \
values</pre> </li>
</ul>
<h4>11 changed files:</h4>
<ul>
<li class='file-stats'>
<a href='#diff-0'>
src/mailman/docs/NEWS.rst
</a>
</li>
<li class='file-stats'>
<a href='#diff-1'>
src/mailman/rest/docs/lists.rst
</a>
</li>
<li class='file-stats'>
<a href='#diff-2'>
src/mailman/rest/docs/membership.rst
</a>
</li>
<li class='file-stats'>
<a href='#diff-3'>
src/mailman/rest/docs/users.rst
</a>
</li>
<li class='file-stats'>
<a href='#diff-4'>
src/mailman/rest/helpers.py
</a>
</li>
<li class='file-stats'>
<a href='#diff-5'>
src/mailman/rest/lists.py
</a>
</li>
<li class='file-stats'>
<a href='#diff-6'>
src/mailman/rest/members.py
</a>
</li>
<li class='file-stats'>
<a href='#diff-7'>
src/mailman/rest/queues.py
</a>
</li>
<li class='file-stats'>
<a href='#diff-8'>
src/mailman/rest/tests/test_lists.py
</a>
</li>
<li class='file-stats'>
<a href='#diff-9'>
src/mailman/rest/tests/test_paginate.py
</a>
</li>
<li class='file-stats'>
<a href='#diff-10'>
src/mailman/rest/users.py
</a>
</li>
</ul>
<h4>Changes:</h4>
<li id='diff-0'>
<a href='https://gitlab.com/mailman/mailman/commit/2b2814bbe7bff7377b859af5d5f5d2dd12121f6e#diff-0'>
 <strong>
src/mailman/docs/NEWS.rst
</strong>
</a>
<hr>
<pre class="highlight"><code><span style="color: #000000;background-color: \
#ffdddd">--- a/src/mailman/docs/NEWS.rst </span><span style="color: \
#000000;background-color: #ddffdd">+++ b/src/mailman/docs/NEWS.rst </span><span \
style="color: #aaaaaa">@@ -49,6 +49,8 @@ Bugs </span>  * Added Trove classifiers to \
                setup.py.  (Closes: #152)
  * Fix the processing of subscription confirmation messages when the mailing
    list is set to confirm-then-moderate.  (Closes #114)
<span style="color: #000000;background-color: #ddffdd">+ * Fix pagination values \
`start` and `total_size` in the REST API.  Given by +   Aurélien Bompard.  (Closes: \
#154) </span> 
 
 3.0.0 -- "Show Don't Tell"
</code></pre>

<br>
</li>
<li id='diff-1'>
<a href='https://gitlab.com/mailman/mailman/commit/2b2814bbe7bff7377b859af5d5f5d2dd12121f6e#diff-1'>
 <strong>
src/mailman/rest/docs/lists.rst
</strong>
</a>
<hr>
<pre class="highlight"><code><span style="color: #000000;background-color: \
#ffdddd">--- a/src/mailman/rest/docs/lists.rst </span><span style="color: \
#000000;background-color: #ddffdd">+++ b/src/mailman/rest/docs/lists.rst </span><span \
style="color: #aaaaaa">@@ -76,7 +76,7 @@ page. </span>         volume: 1
     http_etag: "..."
     start: 0
<span style="color: #000000;background-color: #ffdddd">-    total_size: 1
</span><span style="color: #000000;background-color: #ddffdd">+    total_size: 2
</span> 
     &gt;&gt;&gt; dump_json('http://localhost:9001/3.0/domains/example.com/lists'
     ...           '?count=1&amp;page=2')
<span style="color: #aaaaaa">@@ -91,8 +91,8 @@ page.
</span>         self_link: http://localhost:9001/3.0/lists/bird.example.com
         volume: 1
     http_etag: "..."
<span style="color: #000000;background-color: #ffdddd">-    start: 0
-    total_size: 1
</span><span style="color: #000000;background-color: #ddffdd">+    start: 1
+    total_size: 2
</span> 
 
 Creating lists via the API
</code></pre>

<br>
</li>
<li id='diff-2'>
<a href='https://gitlab.com/mailman/mailman/commit/2b2814bbe7bff7377b859af5d5f5d2dd12121f6e#diff-2'>
 <strong>
src/mailman/rest/docs/membership.rst
</strong>
</a>
<hr>
<pre class="highlight"><code><span style="color: #000000;background-color: \
#ffdddd">--- a/src/mailman/rest/docs/membership.rst </span><span style="color: \
#000000;background-color: #ddffdd">+++ b/src/mailman/rest/docs/membership.rst \
</span><span style="color: #aaaaaa">@@ -267,7 +267,7 @@ page. </span>         user: \
http://localhost:9001/3.0/users/3  http_etag: ...
     start: 0
<span style="color: #000000;background-color: #ffdddd">-    total_size: 1
</span><span style="color: #000000;background-color: #ddffdd">+    total_size: 2
</span> 
 This works with members of a single list as well as with all members.
 
<span style="color: #aaaaaa">@@ -285,7 +285,7 @@ This works with members of a single \
list as well as with all members. </span>         user: \
http://localhost:9001/3.0/users/3  http_etag: ...
     start: 0
<span style="color: #000000;background-color: #ffdddd">-    total_size: 1
</span><span style="color: #000000;background-color: #ddffdd">+    total_size: 5
</span> 
 
 Owners and moderators
</code></pre>

<br>
</li>
<li id='diff-3'>
<a href='https://gitlab.com/mailman/mailman/commit/2b2814bbe7bff7377b859af5d5f5d2dd12121f6e#diff-3'>
 <strong>
src/mailman/rest/docs/users.rst
</strong>
</a>
<hr>
<pre class="highlight"><code><span style="color: #000000;background-color: \
#ffdddd">--- a/src/mailman/rest/docs/users.rst </span><span style="color: \
#000000;background-color: #ddffdd">+++ b/src/mailman/rest/docs/users.rst </span><span \
style="color: #aaaaaa">@@ -84,7 +84,7 @@ page. </span>         user_id: 1
     http_etag: "..."
     start: 0
<span style="color: #000000;background-color: #ffdddd">-    total_size: 1
</span><span style="color: #000000;background-color: #ddffdd">+    total_size: 2
</span> 
     &gt;&gt;&gt; dump_json('http://localhost:9001/3.0/users?count=1&amp;page=2')
     entry 0:
<span style="color: #aaaaaa">@@ -94,8 +94,8 @@ page.
</span>         self_link: http://localhost:9001/3.0/users/2
         user_id: 2
     http_etag: "..."
<span style="color: #000000;background-color: #ffdddd">-    start: 0
-    total_size: 1
</span><span style="color: #000000;background-color: #ddffdd">+    start: 1
+    total_size: 2
</span> 
 
 Creating users
</code></pre>

<br>
</li>
<li id='diff-4'>
<a href='https://gitlab.com/mailman/mailman/commit/2b2814bbe7bff7377b859af5d5f5d2dd12121f6e#diff-4'>
 <strong>
src/mailman/rest/helpers.py
</strong>
</a>
<hr>
<pre class="highlight"><code><span style="color: #000000;background-color: \
#ffdddd">--- a/src/mailman/rest/helpers.py </span><span style="color: \
#000000;background-color: #ddffdd">+++ b/src/mailman/rest/helpers.py </span><span \
style="color: #aaaaaa">@@ -20,6 +20,7 @@ </span> __all__ = [
     'BadRequest',
     'ChildError',
<span style="color: #000000;background-color: #ddffdd">+    'CollectionMixin',
</span>     'GetterSetter',
     'NotFound',
     'bad_request',
<span style="color: #aaaaaa">@@ -112,31 +113,6 @@ def etag(resource):
</span>     return json.dumps(resource, cls=ExtendedEncoder)
 
 
<span style="color: #000000;background-color: #ffdddd">-def paginate(method):
-    """Method decorator to paginate through collection result lists.
-
-    Use this to return only a slice of a collection, specified in the request
-    itself.  The request should use query parameters `count` and `page` to
-    specify the slice they want.  The slice will start at index
-    ``(page - 1) * count`` and end (exclusive) at ``(page * count)``.
-
-    Decorated methods must take ``self`` and ``request`` as the first two
-    arguments.
-    """
-    def wrapper(self, request, *args, **kwargs):
-        # Allow falcon's HTTPBadRequest exceptions to percolate up.  They'll
-        # get turned into HTTP 400 errors.
-        count = request.get_param_as_int('count', min=0)
-        page = request.get_param_as_int('page', min=1)
-        result = method(self, request, *args, **kwargs)
-        if count is None and page is None:
-            return result
-        list_start = (page - 1) * count
-        list_end = page * count
-        return result[list_start:list_end]
-    return wrapper
-
-
</span> 
 class CollectionMixin:
     """Mixin class for common collection-ish things."""
<span style="color: #aaaaaa">@@ -168,22 +144,39 @@ class CollectionMixin:
</span>         """
         raise NotImplementedError
 
<span style="color: #000000;background-color: #ddffdd">+    def _paginate(self, \
request, collection): +        """Method to paginate through collection result lists.
+
+        Use this to return only a slice of a collection, specified in
+        the request itself.  The request should use query parameters
+        `count` and `page` to specify the slice they want.  The slice
+        will start at index ``(page - 1) * count`` and end (exclusive)
+        at ``(page * count)``.
+        """
+        # Allow falcon's HTTPBadRequest exceptions to percolate up.  They'll
+        # get turned into HTTP 400 errors.
+        count = request.get_param_as_int('count', min=0)
+        page = request.get_param_as_int('page', min=1)
+        total_size = len(collection)
+        if count is None and page is None:
+            return 0, total_size, collection
+        list_start = (page - 1) * count
+        list_end = page * count
+        return list_start, total_size, collection[list_start:list_end]
+
</span>     def _make_collection(self, request):
         """Provide the collection to the REST layer."""
<span style="color: #000000;background-color: #ffdddd">-        collection = \
                self._get_collection(request)
-        if len(collection) == 0:
-            return dict(start=0, total_size=0)
-        else:
</span><span style="color: #000000;background-color: #ddffdd">+        start, \
total_size, collection = self._paginate( +            request, \
self._get_collection(request)) +        result = dict(start=start, \
total_size=total_size) +        if len(collection) != 0:
</span>             entries = [self._resource_as_dict(resource)
                        for resource in collection]
             # Tag the resources but use the dictionaries.
             [etag(resource) for resource in entries]
             # Create the collection resource
<span style="color: #000000;background-color: #ffdddd">-            return dict(
-                start=0,
-                total_size=len(collection),
-                entries=entries,
-                )
</span><span style="color: #000000;background-color: #ddffdd">+            \
result['entries'] = entries +        return result
</span> 
 
 
</code></pre>

<br>
</li>
<li id='diff-5'>
<a href='https://gitlab.com/mailman/mailman/commit/2b2814bbe7bff7377b859af5d5f5d2dd12121f6e#diff-5'>
 <strong>
src/mailman/rest/lists.py
</strong>
</a>
<hr>
<pre class="highlight"><code><span style="color: #000000;background-color: \
#ffdddd">--- a/src/mailman/rest/lists.py </span><span style="color: \
#000000;background-color: #ddffdd">+++ b/src/mailman/rest/lists.py </span><span \
style="color: #aaaaaa">@@ -40,7 +40,7 @@ from mailman.interfaces.subscriptions import \
ISubscriptionService </span> from mailman.rest.listconf import ListConfiguration
 from mailman.rest.helpers import (
     CollectionMixin, GetterSetter, NotFound, bad_request, child, created,
<span style="color: #000000;background-color: #ffdddd">-    etag, no_content, \
not_found, okay, paginate, path_to) </span><span style="color: \
#000000;background-color: #ddffdd">+    etag, no_content, not_found, okay, path_to) \
</span> from mailman.rest.members import AMember, MemberCollection  from \
mailman.rest.post_moderation import HeldMessages  from mailman.rest.sub_moderation \
import SubscriptionRequests <span style="color: #aaaaaa">@@ -112,7 +112,6 @@ class \
_ListBase(CollectionMixin): </span>             \
self_link=path_to('lists/{0}'.format(mlist.list_id)),  )
 
<span style="color: #000000;background-color: #ffdddd">-    @paginate
</span>     def _get_collection(self, request):
         """See `CollectionMixin`."""
         return list(getUtility(IListManager))
<span style="color: #aaaaaa">@@ -230,7 +229,6 @@ class \
MembersOfList(MemberCollection): </span>         self._mlist = mailing_list
         self._role = role
 
<span style="color: #000000;background-color: #ffdddd">-    @paginate
</span>     def _get_collection(self, request):
         """See `CollectionMixin`."""
         # Overrides _MemberBase._get_collection() because we only want to
<span style="color: #aaaaaa">@@ -251,7 +249,6 @@ class ListsForDomain(_ListBase):
</span>         resource = self._make_collection(request)
         okay(response, etag(resource))
 
<span style="color: #000000;background-color: #ffdddd">-    @paginate
</span>     def _get_collection(self, request):
         """See `CollectionMixin`."""
         return list(self._domain.mailing_lists)
</code></pre>

<br>
</li>
<li id='diff-6'>
<a href='https://gitlab.com/mailman/mailman/commit/2b2814bbe7bff7377b859af5d5f5d2dd12121f6e#diff-6'>
 <strong>
src/mailman/rest/members.py
</strong>
</a>
<hr>
<pre class="highlight"><code><span style="color: #000000;background-color: \
#ffdddd">--- a/src/mailman/rest/members.py </span><span style="color: \
#000000;background-color: #ddffdd">+++ b/src/mailman/rest/members.py </span><span \
style="color: #aaaaaa">@@ -38,7 +38,7 @@ from mailman.interfaces.user import IUser, \
UnverifiedAddressError </span> from mailman.interfaces.usermanager import \
IUserManager  from mailman.rest.helpers import (
     CollectionMixin, NotFound, accepted, bad_request, child, conflict,
<span style="color: #000000;background-color: #ffdddd">-    created, etag, \
no_content, not_found, okay, paginate, path_to) </span><span style="color: \
#000000;background-color: #ddffdd">+    created, etag, no_content, not_found, okay, \
path_to) </span> from mailman.rest.preferences import Preferences, \
ReadOnlyPreferences  from mailman.rest.validator import (
     Validator, enum_validator, subscriber_validator)
<span style="color: #aaaaaa">@@ -76,7 +76,6 @@ class _MemberBase(CollectionMixin):
</span>             response['user'] = path_to('users/{}'.format(user.user_id.int))
         return response
 
<span style="color: #000000;background-color: #ffdddd">-    @paginate
</span>     def _get_collection(self, request):
         """See `CollectionMixin`."""
         return list(getUtility(ISubscriptionService))
</code></pre>

<br>
</li>
<li id='diff-7'>
<a href='https://gitlab.com/mailman/mailman/commit/2b2814bbe7bff7377b859af5d5f5d2dd12121f6e#diff-7'>
 <strong>
src/mailman/rest/queues.py
</strong>
</a>
<hr>
<pre class="highlight"><code><span style="color: #000000;background-color: \
#ffdddd">--- a/src/mailman/rest/queues.py </span><span style="color: \
#000000;background-color: #ddffdd">+++ b/src/mailman/rest/queues.py </span><span \
style="color: #aaaaaa">@@ -29,7 +29,7 @@ from mailman.app.inject import inject_text \
</span> from mailman.interfaces.listmanager import IListManager  from \
                mailman.rest.helpers import (
     CollectionMixin, bad_request, created, etag, no_content, not_found, okay,
<span style="color: #000000;background-color: #ffdddd">-    paginate, path_to)
</span><span style="color: #000000;background-color: #ddffdd">+    path_to)
</span> from mailman.rest.validator import Validator
 from zope.component import getUtility
 
<span style="color: #aaaaaa">@@ -50,7 +50,6 @@ class _QueuesBase(CollectionMixin):
</span>             self_link=path_to('queues/{}'.format(name)),
             )
 
<span style="color: #000000;background-color: #ffdddd">-    @paginate
</span>     def _get_collection(self, request):
         """See `CollectionMixin`."""
         return sorted(config.switchboards)
</code></pre>

<br>
</li>
<li id='diff-8'>
<a href='https://gitlab.com/mailman/mailman/commit/2b2814bbe7bff7377b859af5d5f5d2dd12121f6e#diff-8'>
 <strong>
src/mailman/rest/tests/test_lists.py
</strong>
</a>
<hr>
<pre class="highlight"><code><span style="color: #000000;background-color: \
#ffdddd">--- a/src/mailman/rest/tests/test_lists.py </span><span style="color: \
#000000;background-color: #ddffdd">+++ b/src/mailman/rest/tests/test_lists.py \
</span><span style="color: #aaaaaa">@@ -287,7 +287,7 @@ class \
TestListPagination(unittest.TestCase): </span>             \
'http://localhost:9001/3.0/domains/example.com/lists'  '?count=1&amp;page=1')
         # There are 6 total lists, but only the first one in the page.
<span style="color: #000000;background-color: #ffdddd">-        \
self.assertEqual(resource['total_size'], 1) </span><span style="color: \
#000000;background-color: #ddffdd">+        self.assertEqual(resource['total_size'], \
6) </span>         self.assertEqual(resource['start'], 0)
         self.assertEqual(len(resource['entries']), 1)
         entry = resource['entries'][0]
<span style="color: #aaaaaa">@@ -298,8 +298,8 @@ class \
TestListPagination(unittest.TestCase): </span>             \
'http://localhost:9001/3.0/domains/example.com/lists'  '?count=1&amp;page=2')
         # There are 6 total lists, but only the first one in the page.
<span style="color: #000000;background-color: #ffdddd">-        \
                self.assertEqual(resource['total_size'], 1)
-        self.assertEqual(resource['start'], 0)
</span><span style="color: #000000;background-color: #ddffdd">+        \
self.assertEqual(resource['total_size'], 6) +        \
self.assertEqual(resource['start'], 1) </span>         \
self.assertEqual(len(resource['entries']), 1)  entry = resource['entries'][0]
         self.assertEqual(entry['fqdn_listname'], 'bee@example.com')
<span style="color: #aaaaaa">@@ -309,8 +309,8 @@ class \
TestListPagination(unittest.TestCase): </span>             \
'http://localhost:9001/3.0/domains/example.com/lists'  '?count=1&amp;page=6')
         # There are 6 total lists, but only the first one in the page.
<span style="color: #000000;background-color: #ffdddd">-        \
                self.assertEqual(resource['total_size'], 1)
-        self.assertEqual(resource['start'], 0)
</span><span style="color: #000000;background-color: #ddffdd">+        \
self.assertEqual(resource['total_size'], 6) +        \
self.assertEqual(resource['start'], 5) </span>         \
self.assertEqual(len(resource['entries']), 1)  entry = resource['entries'][0]
         self.assertEqual(entry['fqdn_listname'], 'fly@example.com')
<span style="color: #aaaaaa">@@ -337,6 +337,6 @@ class \
TestListPagination(unittest.TestCase): </span>             \
'http://localhost:9001/3.0/domains/example.com/lists'  '?count=1&amp;page=7')
         # There are 6 total lists, but only the first one in the page.
<span style="color: #000000;background-color: #ffdddd">-        \
                self.assertEqual(resource['total_size'], 0)
-        self.assertEqual(resource['start'], 0)
</span><span style="color: #000000;background-color: #ddffdd">+        \
self.assertEqual(resource['total_size'], 6) +        \
self.assertEqual(resource['start'], 6) </span>         self.assertNotIn('entries', \
resource) </code></pre>

<br>
</li>
<li id='diff-9'>
<a href='https://gitlab.com/mailman/mailman/commit/2b2814bbe7bff7377b859af5d5f5d2dd12121f6e#diff-9'>
 <strong>
src/mailman/rest/tests/test_paginate.py
</strong>
</a>
<hr>
<pre class="highlight"><code><span style="color: #000000;background-color: \
#ffdddd">--- a/src/mailman/rest/tests/test_paginate.py </span><span style="color: \
#000000;background-color: #ddffdd">+++ b/src/mailman/rest/tests/test_paginate.py \
</span><span style="color: #aaaaaa">@@ -27,7 +27,7 @@ import unittest </span> from \
falcon import HTTPInvalidParam, Request  from mailman.app.lifecycle import \
create_list  from mailman.database.transaction import transaction
<span style="color: #000000;background-color: #ffdddd">-from mailman.rest.helpers \
import paginate </span><span style="color: #000000;background-color: #ddffdd">+from \
mailman.rest.helpers import CollectionMixin </span> from mailman.testing.layers \
import RESTLayer  
 
<span style="color: #aaaaaa">@@ -51,79 +51,84 @@ class \
TestPaginateHelper(unittest.TestCase): </span>         with transaction():
             self._mlist = create_list('test@example.com')
 
<span style="color: #000000;background-color: #ddffdd">+    def _get_resource(self):
+        class Resource(CollectionMixin):
+            def _get_collection(self, request):
+                return ['one', 'two', 'three', 'four', 'five']
+            def _resource_as_dict(self, res):
+                return {'value': res}
+        return Resource()
+
</span>     def test_no_pagination(self):
         # When there is no pagination params in the request, all 5 items in
         # the collection are returned.
<span style="color: #000000;background-color: #ffdddd">-        @paginate
-        def get_collection(self, request):
-            return ['one', 'two', 'three', 'four', 'five']
</span><span style="color: #000000;background-color: #ddffdd">+        resource = \
self._get_resource() </span>         # Expect 5 items
<span style="color: #000000;background-color: #ffdddd">-        page = \
                get_collection(None, _FakeRequest())
-        self.assertEqual(page, ['one', 'two', 'three', 'four', 'five'])
</span><span style="color: #000000;background-color: #ddffdd">+        page = \
resource._make_collection(_FakeRequest()) +        self.assertEqual(page['start'], 0)
+        self.assertEqual(page['total_size'], 5)
+        self.assertEqual(
+            [entry['value'] for entry in page['entries']],
+            ['one', 'two', 'three', 'four', 'five'])
</span> 
     def test_valid_pagination_request_page_one(self):
         # ?count=2&amp;page=1 returns the first page, with two items in it.
<span style="color: #000000;background-color: #ffdddd">-        @paginate
-        def get_collection(self, request):
-            return ['one', 'two', 'three', 'four', 'five']
-        page = get_collection(None, _FakeRequest(2, 1))
-        self.assertEqual(page, ['one', 'two'])
</span><span style="color: #000000;background-color: #ddffdd">+        resource = \
self._get_resource() +        page = resource._make_collection(_FakeRequest(2, 1))
+        self.assertEqual(page['start'], 0)
+        self.assertEqual(page['total_size'], 5)
+        self.assertEqual(
+            [entry['value'] for entry in page['entries']], ['one', 'two'])
</span> 
     def test_valid_pagination_request_page_two(self):
         # ?count=2&amp;page=2 returns the second page, where a page has two items
         # in it.
<span style="color: #000000;background-color: #ffdddd">-        @paginate
-        def get_collection(self, request):
-            return ['one', 'two', 'three', 'four', 'five']
-        page = get_collection(None, _FakeRequest(2, 2))
-        self.assertEqual(page, ['three', 'four'])
</span><span style="color: #000000;background-color: #ddffdd">+        resource = \
self._get_resource() +        page = resource._make_collection(_FakeRequest(2, 2))
+        self.assertEqual(page['start'], 2)
+        self.assertEqual(page['total_size'], 5)
+        self.assertEqual(
+            [entry['value'] for entry in page['entries']], ['three', 'four'])
</span> 
     def test_2nd_index_larger_than_total(self):
         # ?count=2&amp;page=3 returns the third page with page size 2, but the
         # last page only has one item in it.
<span style="color: #000000;background-color: #ffdddd">-        @paginate
-        def get_collection(self, request):
-            return ['one', 'two', 'three', 'four', 'five']
-        page = get_collection(None, _FakeRequest(2, 3))
-        self.assertEqual(page, ['five'])
</span><span style="color: #000000;background-color: #ddffdd">+        resource = \
self._get_resource() +        page = resource._make_collection(_FakeRequest(2, 3))
+        self.assertEqual(page['start'], 4)
+        self.assertEqual(page['total_size'], 5)
+        self.assertEqual(
+            [entry['value'] for entry in page['entries']], ['five'])
</span> 
     def test_out_of_range_returns_empty_list(self):
         # ?count=2&amp;page=4 returns the fourth page, which doesn't exist, so an
         # empty collection is returned.
<span style="color: #000000;background-color: #ffdddd">-        @paginate
-        def get_collection(self, request):
-            return ['one', 'two', 'three', 'four', 'five']
-        page = get_collection(None, _FakeRequest(2, 4))
-        self.assertEqual(page, [])
</span><span style="color: #000000;background-color: #ddffdd">+        resource = \
self._get_resource() +        page = resource._make_collection(_FakeRequest(2, 4))
+        self.assertEqual(page['start'], 6)
+        self.assertEqual(page['total_size'], 5)
+        self.assertNotIn('entries', page)
</span> 
     def test_count_as_string_returns_bad_request(self):
         # ?count=two&amp;page=2 are not valid values, so a bad request occurs.
<span style="color: #000000;background-color: #ffdddd">-        @paginate
-        def get_collection(self, request):
-            return []
-        self.assertRaises(HTTPInvalidParam, get_collection,
-                          None, _FakeRequest('two', 1))
</span><span style="color: #000000;background-color: #ddffdd">+        resource = \
self._get_resource() +        self.assertRaises(HTTPInvalidParam, \
resource._make_collection, +                          _FakeRequest('two', 1))
</span> 
     def test_negative_count(self):
         # ?count=-1&amp;page=1
<span style="color: #000000;background-color: #ffdddd">-        @paginate
-        def get_collection(self, request):
-            return ['one', 'two', 'three', 'four', 'five']
-        self.assertRaises(HTTPInvalidParam, get_collection,
-                          None, _FakeRequest(-1, 1))
</span><span style="color: #000000;background-color: #ddffdd">+        resource = \
self._get_resource() +        self.assertRaises(HTTPInvalidParam, \
resource._make_collection, +                          _FakeRequest(-1, 1))
</span> 
     def test_negative_page(self):
         # ?count=1&amp;page=-1
<span style="color: #000000;background-color: #ffdddd">-        @paginate
-        def get_collection(self, request):
-            return ['one', 'two', 'three', 'four', 'five']
-        self.assertRaises(HTTPInvalidParam, get_collection,
-                          None, _FakeRequest(1, -1))
</span><span style="color: #000000;background-color: #ddffdd">+        resource = \
self._get_resource() +        self.assertRaises(HTTPInvalidParam, \
resource._make_collection, +                          _FakeRequest(1, -1))
</span> 
     def test_negative_page_and_count(self):
         # ?count=1&amp;page=-1
<span style="color: #000000;background-color: #ffdddd">-        @paginate
-        def get_collection(self, request):
-            return ['one', 'two', 'three', 'four', 'five']
-        self.assertRaises(HTTPInvalidParam, get_collection,
-                          None, _FakeRequest(-1, -1))
</span><span style="color: #000000;background-color: #ddffdd">+        resource = \
self._get_resource() +        self.assertRaises(HTTPInvalidParam, \
resource._make_collection, +                          _FakeRequest(-1, -1))
</span></code></pre>

<br>
</li>
<li id='diff-10'>
<a href='https://gitlab.com/mailman/mailman/commit/2b2814bbe7bff7377b859af5d5f5d2dd12121f6e#diff-10'>
 <strong>
src/mailman/rest/users.py
</strong>
</a>
<hr>
<pre class="highlight"><code><span style="color: #000000;background-color: \
#ffdddd">--- a/src/mailman/rest/users.py </span><span style="color: \
#000000;background-color: #ddffdd">+++ b/src/mailman/rest/users.py </span><span \
style="color: #aaaaaa">@@ -35,8 +35,7 @@ from mailman.interfaces.usermanager import \
IUserManager </span> from mailman.rest.addresses import UserAddresses
 from mailman.rest.helpers import (
     BadRequest, CollectionMixin, GetterSetter, NotFound, bad_request, child,
<span style="color: #000000;background-color: #ffdddd">-    conflict, created, etag, \
                forbidden, no_content, not_found, okay, paginate,
-    path_to)
</span><span style="color: #000000;background-color: #ddffdd">+    conflict, created, \
etag, forbidden, no_content, not_found, okay, path_to) </span> from \
mailman.rest.preferences import Preferences  from mailman.rest.validator import (
     PatchValidator, Validator, list_of_strings_validator)
<span style="color: #aaaaaa">@@ -137,7 +136,6 @@ class _UserBase(CollectionMixin):
</span>             resource['display_name'] = user.display_name
         return resource
 
<span style="color: #000000;background-color: #ffdddd">-    @paginate
</span>     def _get_collection(self, request):
         """See `CollectionMixin`."""
         return list(getUtility(IUserManager).users)
<span style="color: #aaaaaa">@@ -448,7 +446,6 @@ class OwnersForDomain(_UserBase):
</span>             self._domain.remove_owner(email)
         return no_content(response)
 
<span style="color: #000000;background-color: #ffdddd">-    @paginate
</span>     def _get_collection(self, request):
         """See `CollectionMixin`."""
         return list(self._domain.owners)
</code></pre>

<br>
</li>

</div>
<div class='footer' style='margin-top: 10px;'>
<p>
&mdash;
<br>
<a href="https://gitlab.com/mailman/mailman/commit/2b2814bbe7bff7377b859af5d5f5d2dd12121f6e">View \
it on GitLab</a> <br>
You're receiving this email because of your account on <a \
href="https://gitlab.com/">gitlab.com</a>. If you'd like to receive fewer emails, you \
can adjust your notification settings. <script \
type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","action":{"@type":"ViewAction","name":"View \
Commit","url":"https://gitlab.com/mailman/mailman/commit/2b2814bbe7bff7377b859af5d5f5d2dd12121f6e"}}</script>
 </p>
</div>
</body>
</html>



_______________________________________________
Mailman-checkins mailing list
Mailman-checkins@python.org
Unsubscribe: https://mail.python.org/mailman/options/mailman-checkins/mailman-cvs%40progressive-comp.com



[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic