Django

Code

Changeset 7306

Show
Ignore:
Timestamp:
03/18/08 16:13:48 (4 months ago)
Author:
adrian
Message:

Added a new and improved Paginator class, which allows you to pass a Page object to the template instead of 5 or 6 separate variables. ObjectPaginator? still exists for backwards compatibility but issues a DeprecationWarning?

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • django/trunk/django/core/paginator.py

    r6702 r7306  
    22    pass 
    33 
    4 class ObjectPaginator(object): 
     4class Paginator(object): 
     5    def __init__(self, object_list, per_page, orphans=0, allow_empty_first_page=True): 
     6        self.object_list = object_list 
     7        self.per_page = per_page 
     8        self.orphans = orphans 
     9        self.allow_empty_first_page = allow_empty_first_page 
     10        self._num_pages = self._count = None 
     11 
     12    def validate_number(self, number): 
     13        "Validates the given 1-based page number." 
     14        try: 
     15            number = int(number) 
     16        except ValueError: 
     17            raise InvalidPage('That page number is not an integer') 
     18        if number < 1: 
     19            raise InvalidPage('That page number is less than 1') 
     20        if number > self.num_pages: 
     21            if number == 1 and self.allow_empty_first_page: 
     22                pass 
     23            else: 
     24                raise InvalidPage('That page contains no results') 
     25        return number 
     26 
     27    def page(self, number): 
     28        "Returns a Page object for the given 1-based page number." 
     29        number = self.validate_number(number) 
     30        bottom = (number - 1) * self.per_page 
     31        top = bottom + self.per_page 
     32        if top + self.orphans >= self.count: 
     33            top = self.count 
     34        return Page(self.object_list[bottom:top], number, self) 
     35 
     36    def _get_count(self): 
     37        "Returns the total number of objects, across all pages." 
     38        if self._count is None: 
     39            self._count = len(self.object_list) 
     40        return self._count 
     41    count = property(_get_count) 
     42 
     43    def _get_num_pages(self): 
     44        "Returns the total number of pages." 
     45        if self._num_pages is None: 
     46            hits = self.count - 1 - self.orphans 
     47            if hits < 1: 
     48                hits = 0 
     49            if hits == 0 and not self.allow_empty_first_page: 
     50                self._num_pages = 0 
     51            else: 
     52                self._num_pages = hits // self.per_page + 1 
     53        return self._num_pages 
     54    num_pages = property(_get_num_pages) 
     55 
     56    def _get_page_range(self): 
     57        """ 
     58        Returns a 1-based range of pages for iterating through within 
     59        a template for loop. 
     60        """ 
     61        return range(1, self.num_pages + 1) 
     62    page_range = property(_get_page_range) 
     63 
     64class QuerySetPaginator(Paginator): 
    565    """ 
    6     This class makes pagination easy. Feed it a QuerySet or list, plus the number 
    7     of objects you want on each page. Then read the hits and pages properties to 
    8     see how many pages it involves. Call get_page with a page number (starting 
    9     at 0) to get back a list of objects for that page. 
     66    Like Paginator, but works on QuerySets. 
     67    """ 
     68    def _get_count(self): 
     69        if self._count is None: 
     70            self._count = self.object_list.count() 
     71        return self._count 
     72    count = property(_get_count) 
    1073 
    11     Finally, check if a page number has a next/prev page using 
    12     has_next_page(page_number) and has_previous_page(page_number). 
    13      
    14     Use orphans to avoid small final pages. For example: 
    15     13 records, num_per_page=10, orphans=2 --> pages==2, len(self.get_page(0))==10 
    16     12 records, num_per_page=10, orphans=2 --> pages==1, len(self.get_page(0))==12 
     74class Page(object): 
     75    def __init__(self, object_list, number, paginator): 
     76        self.object_list = object_list 
     77        self.number = number 
     78        self.paginator = paginator 
     79 
     80    def __repr__(self): 
     81        return '<Page %s of %s>' % (self.number, self.paginator.num_pages) 
     82 
     83    def has_next(self): 
     84        return self.number < self.paginator.num_pages 
     85 
     86    def has_previous(self): 
     87        return self.number > 1 
     88 
     89    def has_other_pages(self): 
     90        return self.has_previous() or self.has_next() 
     91 
     92    def next_page_number(self): 
     93        return self.number + 1 
     94 
     95    def previous_page_number(self): 
     96        return self.number - 1 
     97 
     98    def start_index(self): 
     99        """ 
     100        Returns the 1-based index of the first object on this page, 
     101        relative to total objects in the paginator. 
     102        """ 
     103        return (self.paginator.per_page * (self.number - 1)) + 1 
     104 
     105    def end_index(self): 
     106        """ 
     107        Returns the 1-based index of the last object on this page, 
     108        relative to total objects found (hits). 
     109        """ 
     110        if self.number == self.paginator.num_pages: 
     111            return self.paginator.count 
     112        return self.number * self.paginator.per_page 
     113 
     114class ObjectPaginator(Paginator): 
     115    """ 
     116    Legacy ObjectPaginator class, for backwards compatibility. 
     117 
     118    Note that each method on this class that takes page_number expects a 
     119    zero-based page number, whereas the new API (Paginator/Page) uses one-based 
     120    page numbers. 
    17121    """ 
    18122    def __init__(self, query_set, num_per_page, orphans=0): 
     123        Paginator.__init__(self, query_set, num_per_page, orphans) 
     124        import warnings 
     125        warnings.warn("The ObjectPaginator is deprecated. Use django.core.paginator.Paginator instead.", DeprecationWarning) 
     126 
     127        # Keep these attributes around for backwards compatibility. 
    19128        self.query_set = query_set 
    20129        self.num_per_page = num_per_page 
    21         self.orphans = orphans 
    22130        self._hits = self._pages = None 
    23         self._page_range = None 
    24131 
    25132    def validate_page_number(self, page_number): 
    26133        try: 
    27             page_number = int(page_number) 
     134            page_number = int(page_number) + 1 
    28135        except ValueError: 
    29136            raise InvalidPage 
    30         if page_number < 0 or page_number > self.pages - 1: 
    31             raise InvalidPage 
    32         return page_number 
     137        return self.validate_number(page_number) 
    33138 
    34139    def get_page(self, page_number): 
    35         page_number = self.validate_page_number(page_number) 
    36         bottom = page_number * self.num_per_page 
    37         top = bottom + self.num_per_page 
    38         if top + self.orphans >= self.hits: 
    39             top = self.hits 
    40         return self.query_set[bottom:top] 
     140        try: 
     141            page_number = int(page_number) + 1 
     142        except ValueError: 
     143            raise InvalidPage 
     144        return self.page(page_number).object_list 
    41145 
    42146    def has_next_page(self, page_number): 
    43         "Does page $page_number have a 'next' page?" 
    44147        return page_number < self.pages - 1 
    45148 
     
    53156        """ 
    54157        page_number = self.validate_page_number(page_number) 
    55         return (self.num_per_page * page_number) + 1 
     158        return (self.num_per_page * (page_number - 1)) + 1 
    56159 
    57160    def last_on_page(self, page_number): 
     
    61164        """ 
    62165        page_number = self.validate_page_number(page_number) 
    63         page_number += 1   # 1-base 
    64         if page_number == self.pages: 
    65             return self.hits 
     166        if page_number == self.num_pages: 
     167            return self.count 
    66168        return page_number * self.num_per_page 
    67169 
    68     def _get_hits(self): 
    69         if self._hits is None: 
    70             # Try .count() or fall back to len(). 
     170    def _get_count(self): 
     171        # The old API allowed for self.object_list to be either a QuerySet or a 
     172        # list. Here, we handle both. 
     173        if self._count is None: 
    71174            try: 
    72                 self._hits = int(self.query_set.count()) 
    73             except (AttributeError, TypeError, ValueError): 
    74                 # AttributeError if query_set has no object count. 
    75                 # TypeError if query_set.count() required arguments. 
    76                 # ValueError if int() fails. 
    77                 self._hits = len(self.query_set) 
    78         return self._hits 
     175                self._count = self.object_list.count() 
     176            except AttributeError: 
     177                self._count = len(self.object_list) 
     178        return self._count 
     179    count = property(_get_count) 
    79180 
    80     def _get_pages(self): 
    81         if self._pages is None: 
    82             hits = (self.hits - 1 - self.orphans) 
    83             if hits < 1: 
    84                 hits = 0 
    85             self._pages = hits // self.num_per_page + 1 
    86         return self._pages 
    87      
    88     def _get_page_range(self): 
    89         """ 
    90         Returns a 1-based range of pages for iterating through within  
    91         a template for loop. 
    92         """ 
    93         if self._page_range is None: 
    94             self._page_range = range(1, self.pages + 1) 
    95         return self._page_range 
     181    # The old API called it "hits" instead of "count". 
     182    hits = count 
    96183 
    97     hits = property(_get_hits) 
    98     pages = property(_get_pages) 
    99     page_range = property(_get_page_range) 
     184    # The old API called it "pages" instead of "num_pages". 
     185    pages = Paginator.num_pages 
  • django/trunk/tests/modeltests/pagination/models.py

    r6702 r7306  
    55of code. This is often useful for dividing search results or long lists of 
    66objects into easily readable pages. 
     7 
     8In Django 0.96 and earlier, a single ObjectPaginator class implemented this 
     9functionality. In the Django development version, the behavior is split across 
     10two classes -- Paginator and Page -- that are more easier to use. The legacy 
     11ObjectPaginator class is deprecated. 
    712""" 
    813 
     
    1722 
    1823__test__ = {'API_TESTS':""" 
    19 # prepare a list of objects for pagination 
     24# Prepare a list of objects for pagination. 
    2025>>> from datetime import datetime 
    2126>>> for x in range(1, 10): 
     
    2328...     a.save() 
    2429 
    25 # create a basic paginator, 5 articles per page 
     30#################################### 
     31# New/current API (Paginator/Page) # 
     32#################################### 
     33 
     34>>> from django.core.paginator import Paginator, InvalidPage 
     35>>> paginator = Paginator(Article.objects.all(), 5) 
     36>>> paginator.count 
     37
     38>>> paginator.num_pages 
     39
     40>>> paginator.page_range 
     41[1, 2] 
     42 
     43# Get the first page. 
     44>>> p = paginator.page(1) 
     45>>> p 
     46<Page 1 of 2> 
     47>>> p.object_list 
     48[<Article: Article 1>, <Article: Article 2>, <Article: Article 3>, <Article: Article 4>, <Article: Article 5>] 
     49>>> p.has_next() 
     50True 
     51>>> p.has_previous() 
     52False 
     53>>> p.has_other_pages() 
     54True 
     55>>> p.next_page_number() 
     56
     57>>> p.previous_page_number() 
     58
     59>>> p.start_index() 
     60
     61>>> p.end_index() 
     62
     63 
     64# Get the second page. 
     65>>> p = paginator.page(2) 
     66>>> p 
     67<Page 2 of 2> 
     68>>> p.object_list 
     69[<Article: Article 6>, <Article: Article 7>, <Article: Article 8>, <Article: Article 9>] 
     70>>> p.has_next() 
     71False 
     72>>> p.has_previous() 
     73True 
     74>>> p.has_other_pages() 
     75True 
     76>>> p.next_page_number() 
     77
     78>>> p.previous_page_number() 
     79
     80>>> p.start_index() 
     81
     82>>> p.end_index() 
     83
     84 
     85# Invalid pages raise InvalidPage. 
     86>>> paginator.page(0) 
     87Traceback (most recent call last): 
     88... 
     89InvalidPage: ... 
     90>>> paginator.page(3) 
     91Traceback (most recent call last): 
     92... 
     93InvalidPage: ... 
     94 
     95# Empty paginators with allow_empty_first_page=True. 
     96>>> paginator = Paginator(Article.objects.filter(id=0), 5, allow_empty_first_page=True) 
     97>>> paginator.count 
     98
     99>>> paginator.num_pages 
     100
     101>>> paginator.page_range 
     102[1] 
     103 
     104# Empty paginators with allow_empty_first_page=False. 
     105>>> paginator = Paginator(Article.objects.filter(id=0), 5, allow_empty_first_page=False) 
     106>>> paginator.count 
     107
     108>>> paginator.num_pages 
     109
     110>>> paginator.page_range 
     111[] 
     112 
     113# Paginators work with regular lists/tuples, too -- not just with QuerySets. 
     114>>> paginator = Paginator([1, 2, 3, 4, 5, 6, 7, 8, 9], 5) 
     115>>> paginator.count 
     116
     117>>> paginator.num_pages 
     118
     119>>> paginator.page_range 
     120[1, 2] 
     121 
     122# Get the first page. 
     123>>> p = paginator.page(1) 
     124>>> p 
     125<Page 1 of 2> 
     126>>> p.object_list 
     127[1, 2, 3, 4, 5] 
     128>>> p.has_next() 
     129True 
     130>>> p.has_previous() 
     131False 
     132>>> p.has_other_pages() 
     133True 
     134>>> p.next_page_number() 
     135
     136>>> p.previous_page_number() 
     137
     138>>> p.start_index() 
     139
     140>>> p.end_index() 
     141
     142 
     143################################ 
     144# Legacy API (ObjectPaginator) # 
     145################################ 
     146 
    26147>>> from django.core.paginator import ObjectPaginator, InvalidPage 
    27148>>> paginator = ObjectPaginator(Article.objects.all(), 5) 
    28  
    29 # the paginator knows how many hits and pages it contains 
    30149>>> paginator.hits 
    311509 
    32  
    33151>>> paginator.pages 
    341522 
    35  
    36 # get the first page (zero-based) 
     153>>> paginator.page_range 
     154[1, 2] 
     155 
     156# Get the first page. 
    37157>>> paginator.get_page(0) 
    38158[<Article: Article 1>, <Article: Article 2>, <Article: Article 3>, <Article: Article 4>, <Article: Article 5>] 
    39  
    40 # get the second page 
     159>>> paginator.has_next_page(0) 
     160True 
     161>>> paginator.has_previous_page(0) 
     162False 
     163>>> paginator.first_on_page(0) 
     164
     165>>> paginator.last_on_page(0) 
     166
     167 
     168# Get the second page. 
    41169>>> paginator.get_page(1) 
    42170[<Article: Article 6>, <Article: Article 7>, <Article: Article 8>, <Article: Article 9>] 
    43  
    44 # does the first page have a next or previous page? 
    45 >>> paginator.has_next_page(0) 
    46 True 
    47  
    48 >>> paginator.has_previous_page(0) 
    49 False 
    50  
    51 # check the second page 
    52171>>> paginator.has_next_page(1) 
    53172False 
    54  
    55173>>> paginator.has_previous_page(1) 
    56174True 
    57  
    58 >>> paginator.first_on_page(0) 
    59 1 
    60175>>> paginator.first_on_page(1) 
    611766 
    62 >>> paginator.last_on_page(0) 
    63 5 
    64177>>> paginator.last_on_page(1) 
    651789 
     179 
     180# Invalid pages raise InvalidPage. 
     181>>> paginator.get_page(-1) 
     182Traceback (most recent call last): 
     183... 
     184InvalidPage: ... 
     185>>> paginator.get_page(2) 
     186Traceback (most recent call last): 
     187... 
     188InvalidPage: ... 
     189 
     190# Empty paginators with allow_empty_first_page=True. 
     191>>> paginator = ObjectPaginator(Article.objects.filter(id=0), 5) 
     192>>> paginator.count 
     1930 
     194>>> paginator.num_pages 
     1951 
     196>>> paginator.page_range 
     197[1] 
     198 
     199################## 
     200# Orphan support # 
     201################## 
    66202 
    67203# Add a few more records to test out the orphans feature. 
     
    69205...     Article(headline="Article %s" % x, pub_date=datetime(2006, 10, 6)).save() 
    70206 
    71 # With orphans set to 3 and 10 items per page, we should get all 12 items on a single page: 
     207# With orphans set to 3 and 10 items per page, we should get all 12 items on a single page. 
     208>>> paginator = Paginator(Article.objects.all(), 10, orphans=3) 
     209>>> paginator.num_pages 
     210
     211 
     212# With orphans only set to 1, we should get two pages. 
     213>>> paginator = ObjectPaginator(Article.objects.all(), 10, orphans=1) 
     214>>> paginator.num_pages 
     215
     216 
     217# LEGACY: With orphans set to 3 and 10 items per page, we should get all 12 items on a single page. 
    72218>>> paginator = ObjectPaginator(Article.objects.all(), 10, orphans=3) 
    73219>>> paginator.pages 
    742201 
    75221 
    76 # With orphans only set to 1, we should get two pages: 
     222# LEGACY: With orphans only set to 1, we should get two pages. 
    77223>>> paginator = ObjectPaginator(Article.objects.all(), 10, orphans=1) 
    78224>>> paginator.pages 
    792252 
    80  
    81 # The paginator can provide a list of all available pages. 
    82 >>> paginator = ObjectPaginator(Article.objects.all(), 10) 
    83 >>> paginator.page_range 
    84 [1, 2] 
    85226"""}