Showing preview only (350K chars total). Download the full file or copy to clipboard to get everything.
Repository: shtalinberg/django-el-pagination
Branch: develop
Commit: f795d6120ae2
Files: 134
Total size: 318.4 KB
Directory structure:
gitextract_8g56kzu8/
├── .coveragerc
├── .github/
│ └── workflows/
│ └── tox.yml
├── .gitignore
├── .pre-commit-config.yaml
├── .pylintrc
├── .readthedocs.yaml
├── .vscode/
│ ├── launch.json
│ └── settings.json
├── AUTHORS
├── HACKING
├── INSTALL
├── LICENSE
├── MANIFEST.in
├── Makefile
├── PKG-INFO
├── README.rst
├── doc/
│ ├── Makefile
│ ├── _static/
│ │ └── TRACKME
│ ├── changelog.rst
│ ├── conf.py
│ ├── contacts.rst
│ ├── contributing.rst
│ ├── current_page_number.rst
│ ├── customization.rst
│ ├── different_first_page.rst
│ ├── digg_pagination.rst
│ ├── generic_views.rst
│ ├── index.rst
│ ├── javascript.rst
│ ├── lazy_pagination.rst
│ ├── multiple_pagination.rst
│ ├── requirements.txt
│ ├── start.rst
│ ├── templatetags_reference.rst
│ ├── thanks.rst
│ └── twitter_pagination.rst
├── el_pagination/
│ ├── __init__.py
│ ├── decorators.py
│ ├── exceptions.py
│ ├── loaders.py
│ ├── locale/
│ │ ├── de/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── es/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── fr/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── it/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── pt_BR/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ └── zh_CN/
│ │ └── LC_MESSAGES/
│ │ ├── django.mo
│ │ └── django.po
│ ├── models.py
│ ├── paginators.py
│ ├── settings.py
│ ├── static/
│ │ └── el-pagination/
│ │ └── js/
│ │ └── el-pagination.js
│ ├── templates/
│ │ └── el_pagination/
│ │ ├── current_link.html
│ │ ├── next_link.html
│ │ ├── page_link.html
│ │ ├── previous_link.html
│ │ ├── show_more.html
│ │ ├── show_more_table.html
│ │ └── show_pages.html
│ ├── templatetags/
│ │ ├── __init__.py
│ │ └── el_pagination_tags.py
│ ├── tests/
│ │ ├── __init__.py
│ │ ├── integration/
│ │ │ ├── __init__.py
│ │ │ ├── test_callbacks.py
│ │ │ ├── test_chunks.py
│ │ │ ├── test_digg.py
│ │ │ ├── test_feed_wrapper.py
│ │ │ ├── test_multiple.py
│ │ │ ├── test_onscroll.py
│ │ │ └── test_twitter.py
│ │ ├── templatetags/
│ │ │ ├── __init__.py
│ │ │ └── test_el_pagination_tags.py
│ │ ├── test_decorators.py
│ │ ├── test_loaders.py
│ │ ├── test_models.py
│ │ ├── test_paginators.py
│ │ ├── test_utils.py
│ │ └── test_views.py
│ ├── utils.py
│ └── views.py
├── pyproject.toml
├── release-requirements.txt
├── setup.cfg
├── setup.py
├── tests/
│ ├── __init__.py
│ ├── develop.py
│ ├── manage.py
│ ├── project/
│ │ ├── __init__.py
│ │ ├── context_processors.py
│ │ ├── models.py
│ │ ├── settings.py
│ │ ├── static/
│ │ │ └── pagination.css
│ │ ├── templates/
│ │ │ ├── 404.html
│ │ │ ├── 500.html
│ │ │ ├── base.html
│ │ │ ├── callbacks/
│ │ │ │ ├── index.html
│ │ │ │ └── page.html
│ │ │ ├── chunks/
│ │ │ │ ├── index.html
│ │ │ │ ├── items_page.html
│ │ │ │ └── objects_page.html
│ │ │ ├── complete/
│ │ │ │ ├── articles_page.html
│ │ │ │ ├── entries_page.html
│ │ │ │ ├── index.html
│ │ │ │ ├── items_page.html
│ │ │ │ ├── objects_page.html
│ │ │ │ └── objects_simple_page.html
│ │ │ ├── digg/
│ │ │ │ ├── index.html
│ │ │ │ ├── page.html
│ │ │ │ └── table/
│ │ │ │ ├── index.html
│ │ │ │ └── page.html
│ │ │ ├── feed_wrapper/
│ │ │ │ ├── index.html
│ │ │ │ └── page.html
│ │ │ ├── home.html
│ │ │ ├── multiple/
│ │ │ │ ├── entries_page.html
│ │ │ │ ├── index.html
│ │ │ │ ├── items_page.html
│ │ │ │ └── objects_page.html
│ │ │ ├── onscroll/
│ │ │ │ ├── index.html
│ │ │ │ ├── page.html
│ │ │ │ └── table/
│ │ │ │ ├── index.html
│ │ │ │ └── page.html
│ │ │ └── twitter/
│ │ │ ├── index.html
│ │ │ ├── page.html
│ │ │ └── table/
│ │ │ ├── index.html
│ │ │ └── page.html
│ │ ├── urls.py
│ │ └── views.py
│ ├── requirements.pip
│ └── with_venv.sh
└── tox.ini
================================================
FILE CONTENTS
================================================
================================================
FILE: .coveragerc
================================================
[run]
source = el_pagination
branch = 1
[report]
omit = *tests*,*migrations*
================================================
FILE: .github/workflows/tox.yml
================================================
name: Tox
on: [push, pull_request]
jobs:
tox-lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: 3.x
- run: pip install --upgrade pip
- run: pip install tox
- run: tox -e lint || true # Fix error and remove "|| true"!
tox-docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: 3.x
- run: pip install --upgrade pip
- run: pip install tox
- run: tox -e docs || true # Fix error and remove "|| true"!
tox-docs-linkcheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: 3.x
- run: pip install --upgrade pip
- run: pip install tox
- run: tox -e docs-linkcheck || true # Fix error and remove "|| true"!
build:
strategy:
fail-fast: false
max-parallel: 5
matrix:
os: [ubuntu-latest] # [macos-latest, ubuntu-latest, windows-latest]
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13']
django-version: ["==3.2.*", "==4.1.*", "==4.2.*", "==5.0.*", "==5.1.*", "==5.2.*"]
exclude:
# https://docs.djangoproject.com/en/stable/faq/install/#what-python-version-can-i-use-with-django
- python-version: 3.11
django-version: "==3.2.*"
- python-version: 3.12
django-version: "==3.2.*"
- python-version: 3.12
django-version: "==4.1.*"
- python-version: 3.8
django-version: "==5.0.*"
- python-version: 3.8
django-version: "==5.1.*"
- python-version: 3.8
django-version: "==5.2.*"
- python-version: 3.9
django-version: "==5.0.*"
- python-version: 3.9
django-version: "==5.1.*"
- python-version: 3.9
django-version: "==5.2.*"
# # Django 4.0 no longer supports python 3.7
# - python-version: 3.7
# django-version: "==4.0.*"
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python }}
- name: Get pip cache dir
id: pip-cache
run: |
echo "dir=$(pip cache dir)" >> $GITHUB_OUTPUT
- name: Cache
uses: actions/cache@v3
with:
path: ${{ steps.pip-cache.outputs.dir }}
key:
-${{ matrix.python-version }}-v1-${{ hashFiles('**/setup.py') }}
restore-keys: |
-${{ matrix.python-version }}-v1-
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install --upgrade tox tox-gh-actions
- name: Tox tests
run: |
tox -v
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
name: Python ${{ matrix.python-version }}
================================================
FILE: .gitignore
================================================
# Python
*.py[cod]
__pycache__/
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# Sphinx documentation
_static/TRACKME
.doctrees/
doc/_build
doc/.doctrees
*.sublime-*
.idea*
/help-man
~$
.venv/
.coverage
.ropeproject
.DS_Store
MANIFEST
tests/settings_local.py
*.log
.tox/
================================================
FILE: .pre-commit-config.yaml
================================================
repos:
- repo: https://github.com/pycqa/isort
rev: 5.13.2
hooks:
- id: isort
- repo: local
hooks:
- id: black
name: black
language: system
entry: sh -c 'make black_diff'
types: [python]
exclude: (migrations/|\.venv|\.git)
- repo: local
hooks:
- id: pylint
name: pylint
language: system
entry: sh -c 'make pylint'
types: [python]
exclude: (migrations/|\.venv|\.git)
================================================
FILE: .pylintrc
================================================
[MAIN]
# Analyse import fallback blocks. This can be used to support both Python 2 and
# 3 compatible code, which means that the block might have code that exists
# only in one or another interpreter, leading to false positives when analysed.
analyse-fallback-blocks=no
# Load and enable all available extensions. Use --list-extensions to see a list
# all available extensions.
#enable-all-extensions=
# In error mode, messages with a category besides ERROR or FATAL are
# suppressed, and no reports are done by default. Error mode is compatible with
# disabling specific errors.
#errors-only=
# Always return a 0 (non-error) status code, even if lint errors are found.
# This is primarily useful in continuous integration scripts.
#exit-zero=
# A comma-separated list of package or module names from where C extensions may
# be loaded. Extensions are loading into the active Python interpreter and may
# run arbitrary code.
extension-pkg-allow-list=
# A comma-separated list of package or module names from where C extensions may
# be loaded. Extensions are loading into the active Python interpreter and may
# run arbitrary code. (This is an alternative name to extension-pkg-allow-list
# for backward compatibility.)
extension-pkg-whitelist=
# Return non-zero exit code if any of these messages/categories are detected,
# even if score is above --fail-under value. Syntax same as enable. Messages
# specified are enabled, while categories only check already-enabled messages.
fail-on=
# Specify a score threshold to be exceeded before program exits with error.
fail-under=10
# Interpret the stdin as a python script, whose filename needs to be passed as
# the module_or_package argument.
#from-stdin=
# Files or directories to be skipped. They should be base names, not paths.
ignore=tests
# Add files or directories matching the regex patterns to the ignore-list. The
# regex matches against paths and can be in Posix or Windows format.
ignore-paths=logs,
docs,
help-man
# Files or directories matching the regex patterns are skipped. The regex
# matches against base names, not paths. The default value ignores Emacs file
# locks
ignore-patterns=conftest.py,tests.py,test_*, test*
# List of module names for which member attributes should not be checked
# (useful for modules/projects where namespaces are manipulated during runtime
# and thus existing member attributes cannot be deduced by static analysis). It
# supports qualified module names, as well as Unix pattern matching.
ignored-modules=
# Python code to execute, usually for sys.path manipulation such as
# pygtk.require().
#init-hook='import sys; sys.path = ["src"] + sys.path'
init-hook='import os, sys; sys.path.append("/el_pagination"); sys.path.append("/tests"); sys.path.append(".")'
# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the
# number of processors available to use, and will cap the count on Windows to
# avoid hangs.
jobs=1
# Control the amount of potential inferred values when inferring a single
# object. This can help the performance when dealing with large functions or
# complex, nested conditions.
limit-inference-results=100
# List of plugins (as comma separated values of python module names) to load,
# usually to register additional checkers.
load-plugins=pylint_django
# Pickle collected data for later comparisons.
persistent=no
# Minimum Python version to use for version dependent checks. Will default to
# the version used to run pylint.
py-version=3.10
# Discover python modules and packages in the file system subtree.
recursive=no
# When enabled, pylint would attempt to guess common misconfiguration and emit
# user-friendly hints instead of false-positive error messages.
suggestion-mode=yes
# Allow loading of arbitrary C extensions. Extensions are imported into the
# active Python interpreter and may run arbitrary code.
unsafe-load-any-extension=no
# In verbose mode, extra non-checker-related info will be displayed.
#verbose=
[REPORTS]
# Python expression which should return a score less than or equal to 10. You
# have access to the variables 'fatal', 'error', 'warning', 'refactor',
# 'convention', and 'info' which contain the number of messages in each
# category, as well as 'statement' which is the total number of statements
# analyzed. This score is used by the global evaluation report (RP0004).
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
# Template used to display messages. This is a python new-style format string
# used to format the message information. See doc for all details.
msg-template=
# Set the output format. Available formats are text, parseable, colorized, json
# and msvs (visual studio). You can also give a reporter class, e.g.
# mypackage.mymodule.MyReporterClass.
output-format=colorized
# Tells whether to display a full report or only the messages.
reports=no
# Activate the evaluation score.
score=yes
[MESSAGES CONTROL]
# Only show warnings with the listed confidence levels. Leave empty to show
# all. Valid levels: HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE,
# UNDEFINED.
confidence=HIGH,
CONTROL_FLOW,
INFERENCE,
INFERENCE_FAILURE,
UNDEFINED
# Disable the message, report, category or checker with the given id(s). You
# can either give multiple identifiers separated by comma (,) or put this
# option multiple times (only on the command line, not in the configuration
# file where it should appear only once). You can also use "--disable=all" to
# disable everything first and then re-enable specific checks. For example, if
# you want to run only the similarities checker, you can use "--disable=all
# --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use "--disable=all --enable=classes
# --disable=W".
disable=django-settings-module-not-found,
missing-module-docstring,
missing-class-docstring,
missing-function-docstring,
too-few-public-methods,
unused-argument,
# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option
# multiple time (only on the command line, not in the configuration file where
# it should appear only once). See also the "--disable" option for examples.
enable=c-extension-no-member
[SIMILARITIES]
# Comments are removed from the similarity computation
ignore-comments=yes
# Docstrings are removed from the similarity computation
ignore-docstrings=yes
# Imports are removed from the similarity computation
ignore-imports=yes
# Signatures are removed from the similarity computation
ignore-signatures=yes
# Minimum lines number of a similarity.
min-similarity-lines=4
[IMPORTS]
# List of modules that can be imported at any level, not just the top level
# one.
allow-any-import-level=
# Allow wildcard imports from modules that define __all__.
allow-wildcard-with-all=no
# Deprecated modules which should not be used, separated by a comma.
deprecated-modules=optparse,tkinter.tix
# Output a graph (.gv or any supported image format) of external dependencies
# to the given file (report RP0402 must not be disabled).
ext-import-graph=
# Output a graph (.gv or any supported image format) of all (i.e. internal and
# external) dependencies to the given file (report RP0402 must not be
# disabled).
import-graph=
# Output a graph (.gv or any supported image format) of internal dependencies
# to the given file (report RP0402 must not be disabled).
int-import-graph=
# Force import order to recognize a module as part of the standard
# compatibility libraries.
known-standard-library=
# Force import order to recognize a module as part of a third party library.
known-third-party=enchant
# Couples of modules and preferred modules, separated by a comma.
preferred-modules=
[TYPECHECK]
# List of decorators that produce context managers, such as
# contextlib.contextmanager. Add to this list to register other decorators that
# produce valid context managers.
contextmanager-decorators=contextlib.contextmanager
# List of members which are set dynamically and missed by pylint inference
# system, and so shouldn't trigger E1101 when accessed. Python regular
# expressions are accepted.
generated-members=cv2.*
# Tells whether to warn about missing members when the owner of the attribute
# is inferred to be None.
ignore-none=yes
# This flag controls whether pylint should warn about no-member and similar
# checks whenever an opaque object is returned when inferring. The inference
# can return multiple potential results while evaluating a Python object, but
# some branches might not be evaluated, which results in partial inference. In
# that case, it might be useful to still emit no-member and other checks for
# the rest of the inferred objects.
ignore-on-opaque-inference=yes
# List of symbolic message names to ignore for Mixin members.
ignored-checks-for-mixins=no-member,
not-async-context-manager,
not-context-manager,
attribute-defined-outside-init
# List of class names for which member attributes should not be checked (useful
# for classes with dynamically set attributes). This supports the use of
# qualified names.
ignored-classes=optparse.Values,thread._local,_thread._local
# Show a hint with possible names when a member name was not found. The aspect
# of finding the hint is based on edit distance.
missing-member-hint=yes
# The minimum edit distance a name should have in order to be considered a
# similar match for a missing member name.
missing-member-hint-distance=1
# The total number of similar names that should be taken in consideration when
# showing a hint for a missing member.
missing-member-max-choices=1
# Regex pattern to define which classes are considered mixins.
mixin-class-rgx=.*[Mm]ixin
# List of decorators that change the signature of a decorated function.
signature-mutators=
[LOGGING]
# The type of string formatting that logging methods do. `old` means using %
# formatting, `new` is for `{}` formatting.
logging-format-style=old
# Logging modules to check that the string format arguments are in logging
# function parameter format.
logging-modules=logging
[VARIABLES]
# List of additional names supposed to be defined in builtins. Remember that
# you should avoid defining new builtins when possible.
additional-builtins=
# Tells whether unused global variables should be treated as a violation.
allow-global-unused-variables=yes
# List of names allowed to shadow builtins
allowed-redefined-builtins=
# List of strings which can identify a callback function by name. A callback
# name must start or end with one of those strings.
callbacks=cb_,
_cb
# A regular expression matching the name of dummy variables (i.e. expected to
# not be used).
dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_
# Argument names that match this expression will be ignored. Default to name
# with leading underscore.
ignored-argument-names=_.*|^ignored_|^unused_
# Tells whether we should check for unused import in __init__ files.
init-import=no
# List of qualified module names which can have objects that can redefine
# builtins.
redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io
[STRING]
# This flag controls whether inconsistent-quotes generates a warning when the
# character used as a quote delimiter is used inconsistently within a module.
check-quote-consistency=no
# This flag controls whether the implicit-str-concat should generate a warning
# on implicit string concatenation in sequences defined over several lines.
check-str-concat-over-line-jumps=no
[DESIGN]
# List of regular expressions of class ancestor names to ignore when counting
# public methods (see R0903)
exclude-too-few-public-methods=
# List of qualified class names to ignore when counting class parents (see
# R0901)
ignored-parents=
# Maximum number of arguments for function / method.
max-args=10
# Maximum number of attributes for a class (see R0902).
max-attributes=13
# Maximum number of boolean expressions in an if statement (see R0916).
max-bool-expr=5
# Maximum number of branch for function / method body.
max-branches=15
# Maximum number of locals for function / method body.
max-locals=20
# Maximum number of parents for a class (see R0901).
max-parents=10
# Maximum number of positional arguments for function / method.
max-positional-arguments=10
# Maximum number of public methods for a class (see R0904).
max-public-methods=20
# Maximum number of return / yield for function / method body.
max-returns=6
# Maximum number of statements in function / method body.
max-statements=50
# Minimum number of public methods for a class (see R0903).
min-public-methods=2
[REFACTORING]
# Maximum number of nested blocks for function / method body
max-nested-blocks=5
# Complete name of functions that never returns. When checking for
# inconsistent-return-statements if a never returning function is called then
# it will be considered as an explicit return statement and no message will be
# printed.
never-returning-functions=sys.exit
[BASIC]
# Naming style matching correct argument names.
argument-naming-style=snake_case
# Regular expression matching correct argument names. Overrides argument-
# naming-style. If left empty, argument names will be checked with the set
# naming style.
#argument-rgx=
# Naming style matching correct attribute names.
attr-naming-style=snake_case
# Regular expression matching correct attribute names. Overrides attr-naming-
# style. If left empty, attribute names will be checked with the set naming
# style.
#attr-rgx=
# Bad variable names which should always be refused, separated by a comma.
bad-names=foo,
bar,
baz,
toto,
tutu,
tata
# Bad variable names regexes, separated by a comma. If names match any regex,
# they will always be refused
bad-names-rgxs=
# Naming style matching correct class attribute names.
class-attribute-naming-style=any
# Regular expression matching correct class attribute names. Overrides class-
# attribute-naming-style. If left empty, class attribute names will be checked
# with the set naming style.
#class-attribute-rgx=
# Naming style matching correct class constant names.
class-const-naming-style=UPPER_CASE
# Regular expression matching correct class constant names. Overrides class-
# const-naming-style. If left empty, class constant names will be checked with
# the set naming style.
#class-const-rgx=
# Naming style matching correct class names.
class-naming-style=PascalCase
# Regular expression matching correct class names. Overrides class-naming-
# style. If left empty, class names will be checked with the set naming style.
#class-rgx=
# Naming style matching correct constant names.
const-naming-style=UPPER_CASE
# Regular expression matching correct constant names. Overrides const-naming-
# style. If left empty, constant names will be checked with the set naming
# style.
#const-rgx=
# Minimum line length for functions/classes that require docstrings, shorter
# ones are exempt.
docstring-min-length=-1
# Naming style matching correct function names.
function-naming-style=snake_case
# Regular expression matching correct function names. Overrides function-
# naming-style. If left empty, function names will be checked with the set
# naming style.
#function-rgx=
# Good variable names which should always be accepted, separated by a comma.
good-names=i,
j,
k,
ex,
Run,
_
# Good variable names regexes, separated by a comma. If names match any regex,
# they will always be accepted
good-names-rgxs=
# Include a hint for the correct naming format with invalid-name.
include-naming-hint=no
# Naming style matching correct inline iteration names.
inlinevar-naming-style=any
# Regular expression matching correct inline iteration names. Overrides
# inlinevar-naming-style. If left empty, inline iteration names will be checked
# with the set naming style.
#inlinevar-rgx=
# Naming style matching correct method names.
method-naming-style=snake_case
# Regular expression matching correct method names. Overrides method-naming-
# style. If left empty, method names will be checked with the set naming style.
#method-rgx=
# Naming style matching correct module names.
module-naming-style=snake_case
# Regular expression matching correct module names. Overrides module-naming-
# style. If left empty, module names will be checked with the set naming style.
#module-rgx=
# Colon-delimited sets of names that determine each other's naming style when
# the name regexes allow several styles.
name-group=
# Regular expression which should only match function or class names that do
# not require a docstring.
no-docstring-rgx=^_
# List of decorators that produce properties, such as abc.abstractproperty. Add
# to this list to register other decorators that produce valid properties.
# These decorators are taken in consideration only for invalid-name.
property-classes=abc.abstractproperty
# Regular expression matching correct type variable names. If left empty, type
# variable names will be checked with the set naming style.
#typevar-rgx=
# Naming style matching correct variable names.
variable-naming-style=snake_case
# Regular expression matching correct variable names. Overrides variable-
# naming-style. If left empty, variable names will be checked with the set
# naming style.
variable-rgx=(([a-z_][a-z0-9_]+)|(_[a-z0-9_]*)|(__[a-z][a-z0-9_]+__))$
[MISCELLANEOUS]
# List of note tags to take in consideration, separated by a comma.
notes=FIXME,
XXX,
TODO
# Regular expression of note tags to take in consideration.
notes-rgx=
[FORMAT]
# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
expected-line-ending-format=
# Regexp for a line that is allowed to be longer than the limit.
ignore-long-lines=^\s*(# )?<?https?://\S+>?$
# Number of spaces of indent required inside a hanging or continued line.
indent-after-paren=4
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
# tab).
indent-string=' '
# Maximum number of characters on a single line.
max-line-length=119
# Maximum number of lines in a module.
max-module-lines=1200
# Allow the body of a class to be on the same line as the declaration if body
# contains single statement.
single-line-class-stmt=no
# Allow the body of an if to be on the same line as the test if there is no
# else.
single-line-if-stmt=no
[SPELLING]
# Limits count of emitted suggestions for spelling mistakes.
max-spelling-suggestions=4
# Spelling dictionary name. Available dictionaries: none. To make it work,
# install the 'python-enchant' package.
spelling-dict=
# List of comma separated words that should be considered directives if they
# appear at the beginning of a comment and should not be checked.
spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy:
# List of comma separated words that should not be checked.
spelling-ignore-words=
# A path to a file that contains the private dictionary; one word per line.
spelling-private-dict-file=
# Tells whether to store unknown words to the private dictionary (see the
# --spelling-private-dict-file option) instead of raising a message.
spelling-store-unknown-words=no
[CLASSES]
# Warn about protected attribute access inside special methods
check-protected-access-in-special-methods=no
# List of method names used to declare (i.e. assign) instance attributes.
defining-attr-methods=__init__,
__new__,
setUp
# List of member names, which should be excluded from the protected access
# warning.
exclude-protected=_asdict,
_fields,
_replace,
_source,
_make
# List of valid names for the first argument in a class method.
valid-classmethod-first-arg=cls
# List of valid names for the first argument in a metaclass class method.
valid-metaclass-classmethod-first-arg=cls
[EXCEPTIONS]
# Exceptions that will emit a warning when caught.
overgeneral-exceptions=builtins.BaseException,
builtins.Exception
[DJANGO FOREIGN KEYS REFERENCED BY STRINGS]
# A module containing Django settings to be used while linting.
django-settings-module=project.settings
================================================
FILE: .readthedocs.yaml
================================================
# .readthedocs.yaml
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
# Required
version: 2
# Set the OS, Python version and other tools you might need
build:
os: ubuntu-22.04
tools:
python: "3.10"
# Build documentation in the docs/ directory with Sphinx
sphinx:
configuration: doc/conf.py
fail_on_warning: true
# Optionally build your docs in additional formats such as PDF and ePub
formats:
- pdf
# Optionally declare the Python requirements required to build your docs
python:
install:
- method: pip
path: .
extra_requirements:
- doc
- requirements: doc/requirements.txt
# Don't install the package in editable mode
# This ensures we're testing the actual installation
python:
install:
- method: pip
path: .
- requirements: doc/requirements.txt
================================================
FILE: .vscode/launch.json
================================================
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Python Demo: Django",
"type": "debugpy",
"request": "launch",
"program": "${workspaceFolder}/tests/manage.py",
"console": "internalConsole",
"args": [
"runserver",
"127.0.0.1:8000",
"--noreload",
"--settings=project.settings"
],
"django": true,
"autoStartBrowser": false
},
{
"type": "chrome",
"request": "attach",
"name": "Attach to Chrome",
"port": 9222,
"urlFilter": "http://127.0.0.1:8000/*",
"webRoot": "${workspaceFolder}"
},
],
"compounds": [
{
"name": "Django",
"configurations": [
"Python Demo: Django",
]
},
]
}
================================================
FILE: .vscode/settings.json
================================================
{
"editor.trimAutoWhitespace": true,
"editor.useTabStops": true,
"files.trimTrailingWhitespace": true,
"html.format.enable": false,
"html.format.templating": true,
"javascript.format.insertSpaceAfterFunctionKeywordForAnonymousFunctions": false,
"javascript.format.insertSpaceAfterKeywordsInControlFlowStatements": false,
"javascript.format.insertSpaceAfterCommaDelimiter": false,
"javascript.format.insertSpaceAfterOpeningAndBeforeClosingEmptyBraces": false,
"javascript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces": false,
"javascript.format.insertSpaceBeforeAndAfterBinaryOperators": false,
"javascript.format.insertSpaceAfterSemicolonInForStatements": false,
"[django-html]": {
"editor.defaultFormatter": "monosans.djlint"
},
"emmet.includeLanguages": {
"django-html": "html",
},
"files.associations": {
"**/templates{/**,*}.html": "django-html",
"**/templates{/**,*}.htm": "django-html",
"**/templates{/**,*}.txt": "django-txt",
"**/requirements{/**,*}.{txt,pip}": "pip-requirements",
"**/*.html": "html"
},
"[python]": {
"editor.formatOnType": true,
"editor.defaultFormatter": "ms-python.black-formatter"
},
"python.analysis.extraPaths": [
"./src/apps",
"./src/compat"
],
"python.testing.pytestArgs": [
"src"
],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true,
}
================================================
FILE: AUTHORS
================================================
Oleksandr Shtalinberg <O.Shtalinberg@gmail.com>
Francesco Banconi <francesco.banconi@gmail.com>
Christian Clauss
================================================
FILE: HACKING
================================================
Hacking Django EL(Endless) Pagination
=================================
Here are the steps needed to set up a development and testing environment.
Creating a development environment
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The development environment is created in a venv. The environment
creation requires the *make* program to be installed.
To install *make* under Debian/Ubuntu::
$ sudo apt-get install build-essential
Under Mac OS/X, *make* is available as part of XCode.
At this point, from the root of this branch, run the command::
$ make
This command will create a ``.venv`` directory in the branch root, ignored
by DVCSes, containing the development virtual environment with all the
dependencies.
Testing the application
~~~~~~~~~~~~~~~~~~~~~~~
Run the tests::
$ make test
The command above also runs all tests except the available integration. They use
Selenium and require Firefox to be installed. To include executing integration
tests, define the environment variable USE_SELENIUM, e.g.::
$ make test USE_SELENIUM=1
Integration tests are excluded by default when using Python 3. The test suite
requires Python >= 3.8.0.
Run the tests and lint/pep8 checks::
$ make check
Again, to exclude integration tests::
$ make check USE_SELENIUM=1
Debugging
~~~~~~~~~
Run the Django shell (Python interpreter)::
$ make shell
Run the Django development server for manual testing::
$ make server
After executing the command above, it is possible to navigate the testing
project going to <http://localhost:8000>.
See all the available make targets, including info on how to create a Python 3
development environment::
$ make help
Thanks for contributing, and have fun!
Pipy testing before bump new vervion
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Before register our package with PyPI, it is important to test if the package actually works.
pip install -e git+https://shtalinberg@github.com/shtalinberg/django-el-pagination#egg=django-el-pagination
pip install -e git+https://shtalinberg@github.com/shtalinberg/django-el-pagination@develop#egg=django-el-pagination
================================================
FILE: INSTALL
================================================
To install django-el-pagination, run the following command
inside this directory:
make install
Or if you'd prefer you can simply place the included ``el_pagination``
package on your Python path.
================================================
FILE: LICENSE
================================================
Copyright (c) 2009-2013 Francesco Banconi
Copyright (c) 2015-2024 Oleksandr Shtalinberg
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: MANIFEST.in
================================================
include AUTHORS
include HACKING
include INSTALL
include LICENSE
include Makefile
include MANIFEST.in
include README.rst
# Documentation
recursive-include doc *
# Test files
recursive-include tests *
# Package resources
recursive-include el_pagination/static *
recursive-include el_pagination/locale *
recursive-include el_pagination/templates *
recursive-include el_pagination/templatetags *.py
# Exclude cache files and compiled python files
recursive-exclude * __pycache__
recursive-exclude * *.py[cod]
recursive-exclude * *.so
recursive-exclude * .*.swp
recursive-exclude * .DS_Store
# Exclude version control
prune .git
prune .github
prune .gitignore
================================================
FILE: Makefile
================================================
# Django Endless Pagination Makefile.
# Define these variables based on the system Python versions.
PYTHON ?= python3
VENV = .venv
WITH_VENV = ./tests/with_venv.sh $(VENV)
MANAGE = $(PYTHON) ./tests/manage.py
LINTER = flake8 --show-source el_pagination/ tests/
DOC_INDEX = doc/_build/html/index.html
.PHONY: all clean cleanall check develop help install lint doc opendoc release server shell source test
all: develop
# Virtual environment
$(VENV)/bin/activate: tests/develop.py tests/requirements.pip
@$(PYTHON) tests/develop.py
@touch $(VENV)/bin/activate
$(VENV)/bin/pip install --upgrade pip setuptools wheel
$(VENV)/bin/pip install -r tests/requirements.pip
develop: $(VENV)/bin/activate
$(VENV)/bin/pip install -e .
# Documentation
$(DOC_INDEX): $(wildcard doc/*.rst)
@$(WITH_VENV) make -C doc html
doc: develop $(DOC_INDEX)
clean:
pip uninstall django-el-pagination -y || true
rm -rf .coverage build/ dist/ doc/_build MANIFEST *.egg-info
find . -name '*.pyc' -delete
find . -name '__pycache__' -type d -delete
cleanall: clean
rm -rf $(VENV)
check: test lint
install:
pip install --force-reinstall -e .
lint: develop
@$(WITH_VENV) $(LINTER)
black: develop
@echo "*** Black - Reformat pycode ***"
@echo ""
@$(WITH_VENV) black el_pagination/ tests/
black_diff: develop
@echo "*** Black - Show pycode diff ***"
@echo ""
black --diff --check --color el_pagination/ tests/
pylint: develop
@echo "Running pylint"
pylint --rcfile=.pylintrc el_pagination/ tests/
@echo "Finish pylint"
opendoc: doc
@firefox $(DOC_INDEX)
server: develop
@$(WITH_VENV) $(MANAGE) runserver 0.0.0.0:8000
shell: develop
@$(WITH_VENV) $(MANAGE) shell
source:
$(PYTHON) setup.py sdist
test: develop
@$(WITH_VENV) $(MANAGE) test
build-dist: clean develop
@echo "Installing build dependencies..."
$(VENV)/bin/pip install build twine
@echo "Building distribution..."
$(VENV)/bin/python -m build
check-dist: build-dist
@echo "Checking distribution..."
$(VENV)/bin/twine check dist/*
upload-dist: check-dist
@echo "Uploading to PyPI..."
$(VENV)/bin/twine upload dist/*
release: clean develop
@echo "Starting release process..."
@if [ -z "$$SKIP_CONFIRMATION" ]; then \
read -p "Are you sure you want to release to PyPI? [y/N] " confirm; \
if [ "$$confirm" != "y" ]; then \
echo "Release cancelled."; \
exit 1; \
fi \
fi
$(MAKE) build-dist
$(MAKE) check-dist
@echo "Ready to upload to PyPI..."
@if [ -z "$$SKIP_CONFIRMATION" ]; then \
read -p "Proceed with upload? [y/N] " confirm; \
if [ "$$confirm" != "y" ]; then \
echo "Upload cancelled."; \
exit 1; \
fi \
fi
$(MAKE) upload-dist
@echo "Release completed successfully!"
help:
@echo 'Django Endless Pagination - Available commands:'
@echo
@echo 'Development:'
@echo ' make - Set up development environment'
@echo ' make install - Install package locally'
@echo ' make server - Run development server'
@echo ' make shell - Enter Django shell'
@echo
@echo 'Testing:'
@echo ' make test - Run tests'
@echo ' make lint - Run code linting'
@echo ' make check - Run tests and linting'
@echo
@echo 'Documentation:'
@echo ' make doc - Build documentation'
@echo ' make opendoc - Build and open documentation'
@echo
@echo 'Cleaning:'
@echo ' make clean - Remove build artifacts'
@echo ' make cleanall - Remove all generated files including venv'
@echo
@echo 'Distribution:'
@echo ' make source - Create source package'
@echo ' make release - Upload to PyPI'
@echo
@echo 'Environment Variables:'
@echo ' USE_SELENIUM=1 - Include integration tests'
@echo ' SHOW_BROWSER=1 - Show browser during Selenium tests'
@echo
================================================
FILE: PKG-INFO
================================================
Metadata-Version: 1.2
Name: django-el-pagination
Version: 4.2.0
Summary: Django pagination tools supporting Ajax, multiple and lazy pagination, Twitter-style and Digg-style pagination.
Home-page: https://github.com/shtalinberg/django-el-pagination
Author: Oleksandr Shtalinberg
Author-email: O.Shtalinberg@gmail.com
License: MIT
Description: Django EL(Endless) Pagination can be used to provide Twitter-style or
Digg-style pagination, with optional Ajax support and other features
like multiple or lazy pagination.
The initial idea, which has guided the development of this application,
is to allow pagination of web contents in `very few steps
<http://django-el-pagination.readthedocs.org/en/latest/start.html>`_.
**Documentation** is `avaliable online
<http://django-el-pagination.readthedocs.org/>`_, or in the docs
directory of the project.
To file **bugs and requests**, please use
https://github.com/shtalinberg/django-el-pagination/issues.
The **source code** for this app is hosted at
https://github.com/shtalinberg/django-el-pagination.
Keywords: django pagination ajax
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Web Environment
Classifier: Framework :: Django
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Utilities
================================================
FILE: README.rst
================================================
=============================
Django EL(Endless) Pagination
=============================
| |pypi-pkg-version| |python-versions| |django-versions| |pypi-status| |docs|
| |build-ci-status| |tox-ci-status| |codecov|
Django EL(Endless) Pagination can be used to provide Twitter-style or
Digg-style pagination, with optional Ajax support and other features
like multiple or lazy pagination.
This app **django-el-pagination** forked from django-endless-pagination==2.0 (https://github.com/frankban/django-endless-pagination)
From version 4.0.0 drop support Django<3.2. For support Django<3.2 use django-endless-pagination<4.0.x
From version 4.1.2 added support Django 5.0 and python 3.12
The initial idea, which has guided the development of this application,
is to allow pagination of web contents in `very few steps
<http://django-el-pagination.readthedocs.org/en/latest/start.html>`_.
**Documentation** is `available online
<http://django-el-pagination.readthedocs.org/>`_, or in the doc
directory of the project.
To file **bugs and requests**, please use
https://github.com/shtalinberg/django-el-pagination/issues.
The **source code** for this app is hosted at
https://github.com/shtalinberg/django-el-pagination.
Pull requests are welcome. See `Contributing Guide
<http://django-el-pagination.readthedocs.io/en/latest/contributing.html>`_.
.. |build-ci-status| image:: https://github.com/shtalinberg/django-el-pagination/actions/workflows/tox.yml/badge.svg?branch=master
:target: https://github.com/shtalinberg/django-el-pagination/actions/workflows/tox.yml
:alt: Build release status
.. |docs| image:: https://readthedocs.org/projects/django-el-pagination/badge/?version=latest
:target: https://django-el-pagination.readthedocs.io/en/latest/?badge=latest
:alt: Documentation Status
.. |pypi-pkg-version| image:: https://img.shields.io/pypi/v/django-el-pagination.svg
:target: https://pypi.python.org/pypi/django-el-pagination/
.. |pypi-status| image:: https://img.shields.io/pypi/status/coverage.svg
:target: https://pypi.python.org/pypi/django-el-pagination/
.. |python-versions| image:: https://img.shields.io/pypi/pyversions/django-el-pagination.svg
.. |django-versions| image:: https://img.shields.io/pypi/djversions/django-el-pagination.svg
.. |codecov| image:: https://codecov.io/gh/shtalinberg/django-el-pagination/branch/master/graph/badge.svg
:target: https://codecov.io/gh/shtalinberg/django-el-pagination
:alt: Code coverage
.. |tox-ci-status| image:: https://github.com/shtalinberg/django-el-pagination/actions/workflows/tox.yml/badge.svg?branch=develop
:target: https://github.com/shtalinberg/django-el-pagination/actions/workflows/tox.yml
:alt: Tox develop status
================================================
FILE: doc/Makefile
================================================
# Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = _build
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
clean:
-rm -rf $(BUILDDIR)/*
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
singlehtml:
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/DjangoEndlessPagination.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/DjangoEndlessPagination.qhc"
devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/DjangoEndlessPagination"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/DjangoEndlessPagination"
@echo "# devhelp"
epub:
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
latexpdf:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..."
make -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."
================================================
FILE: doc/_static/TRACKME
================================================
Placeholder file to let Hg include this directory.
================================================
FILE: doc/changelog.rst
================================================
Changelog
=========
Unreleased
~~~~~~~~~~
**New feature**: Django 5.2.x support.
Django EL(Endless) Pagination now supports Django from 4.2.x to 5.2.x
Version 4.2.0
~~~~~~~~~~~~~
**New feature**: Django 5.1.x support.
Django EL(Endless) Pagination now supports Django from 4.2.x to 5.1.x
supports Python 3.8, 3.9, 3.10, 3.11, 3.12, 3.13 (only 5.1.x)
**Source code validation**:
add black formatting rules
add pylint checks
Version 4.1.2
~~~~~~~~~~~~~
**Fix**: in 4.1.1 problem with loading template_tag
Version 4.1.1
~~~~~~~~~~~~~
**Fix**: fixed readthedocs documentation
Version 4.1.0
~~~~~~~~~~~~~
**New feature**: Django 4.2.x, 5.x support.
Django EL(Endless) Pagination now supports Django 3.2.x, 4.2.x and 5.0
supports Python
Django Python versions
3.2 3.8, 3.9, 3.10 (added in 3.2.9)
4.2 3.8, 3.9, 3.10, 3.11, 3.12 (added in 4.2.8)
5.0 3.10, 3.11, 3.12
Version 4.0.0
~~~~~~~~~~~~~
**New feature**: Django 4.1.x support.
Django EL(Endless) Pagination now supports Django from 3.2.x to 4.1.x
supports Python 3.8, 3.9, 3.10
Version 3.3.0
~~~~~~~~~~~~~
**New feature**: Django 3.0.x support.
Django EL(Endless) Pagination now supports Django from 1.11.x to 3.0.x
Dropped support for Python 2.x
Version 3.2.4
~~~~~~~~~~~~~
**Fix**: compatible with jQuery 3.x
Version 3.2.3
~~~~~~~~~~~~~
Bug-fix release
**Fix**: cycle in show_pages with django 2.0
fix tests for PageList.get_rendered()
Version 3.2.2
~~~~~~~~~~~~~
Bug-fix release
**Fix**: fix UnicodeEncodeError with translate in templates
Version 3.2.0
~~~~~~~~~~~~~
**New feature**: Django 2.0.x support.
Django EL(Endless) Pagination now supports Django from 1.8.x to 2.0.x
**New feature**: settings.USE_NEXT_PREVIOUS_LINKS: default=False
if True:
Add `is_previous` & `is_next` flags for `previous` and `next` pages
Add `next_link.html` & `previous_link.html` templates
**New feature**: `__unicode__` is removed from class ELPage
It's Fix Causes Fatal Python error with django-debug-toolbar
In templates:
- {{ page }} now use as {{ page.render_link }}
- {{ pages }} now use as {{ pages.get_rendered }}
**Template changes**:
show_pages.html:
`page|default_if_none` replaced `page.render_link|default`
----
**Cleanup**:
utils.UnicodeMixin
utils.text
Version 3.1.0
~~~~~~~~~~~~~
**Template changes**:
link attribute rel="{{ querystring_key }}" replaced by data-el-querystring-key="{{ querystring_key }}"
**New feature**: Django 1.11 support.
**New feature**:
added view for maintaining original functionality on page index out of range, but setting response code to 404
``PAGE_OUT_OF_RANGE_404`` default *False* If True on page out of range, throw a 404 exception, otherwise display the first page
**Documentation**: render_to_response deprecated in django 1.10
replaced to ``return render(request, template, context)``
Version 3.0.0
~~~~~~~~~~~~~
**New feature**: Django 1.10 support.
New app Django EL(Endless) Pagination now supports Django from 1.8.x to 1.10
----
**New feature**: Travic CI support
add tox and Travic CI config
----
**Documentation**: general clean up.
Version 2.1.1
~~~~~~~~~~~~~
Bug-fix release
**Fix**: page_template decorator doesn't change template of ajax call
----
**Fix**: Fix syntax error in declaring variable in javascript
Version 2.1.0
~~~~~~~~~~~~~
New name app: django-el-pagination
**New feature**: Django 1.8 and 1.9 support.
New app Django EL(Endless) Pagination now supports Django from 1.4.x to 1.9
new jQuery plugin that can be found in
``static/el-pagination/js/el-pagination.js``.
Support get the numbers of objects are normally display in per page
Usage:
.. code-block:: html+django
{{ pages.per_page_number }}
add a class on chunk complete
Each time a chunk size is complete, the class ``endless_chunk_complete`` is added to the *show more* link,
Version 2.0
~~~~~~~~~~~
**New feature**: Python 3 support.
Django Endless Pagination now supports both Python 2 and **Python 3**. Dropped
support for Python 2.5. See :doc:`start` for the new list of requirements.
----
**New feature**: the **JavaScript refactoring**.
This version introduces a re-designed Ajax support for pagination. Ajax can
now be enabled using a brand new jQuery plugin that can be found in
``static/el-pagination/js/el-pagination.js``.
Usage:
.. code-block:: html+django
{% block js %}
{{ block.super }}
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="{{ STATIC_URL }}el-pagination/js/el-pagination.js"></script>
<script>$.endlessPaginate();</script>
{% endblock %}
The last line in the block above enables Ajax requests to retrieve new
pages for each pagination in the page. That's basically the same as the old
approach of loading the file ``endless.js``. The new approach, however,
is more jQuery-idiomatic, increases the flexibility of how objects can be
paginated, implements some :doc:`new features </javascript>` and also contains
some bug fixes.
For backward compatibility, the application still includes the two JavaScript
``endless.js`` and ``endless_on_scroll.js`` files. However, please consider
:ref:`migrating<javascript-migrate>` as soon as possible: the old JavaScript
files are deprecated, are no longer maintained, and don't provide the new
JavaScript features. Also note that the old Javascript files will not work if
jQuery >= 1.9 is used.
New features include ability to **paginate different objects with different
options**, precisely **selecting what to bind**, ability to **register
callbacks**, support for **pagination in chunks** and much more.
Please refer to the :doc:`javascript` for a detailed overview of the new
features and for instructions on :ref:`how to migrate<javascript-migrate>` from
the old JavaScript files to the new one.
----
**New feature**: the :ref:`page_templates<multiple-page-templates>` decorator
also accepts a sequence of ``(template, key)`` pairs, functioning as a dict
mapping templates and keys (still present), e.g.::
from endless_pagination.decorators import page_templates
@page_templates((
('myapp/entries_page.html', None),
('myapp/other_entries_page.html', 'other_entries_page'),
))
def entry_index():
...
This also supports serving different paginated objects with the same template.
----
**New feature**: ability to provide nested context variables in the
:ref:`templatetags-paginate` and :ref:`templatetags-lazy-paginate` template
tags, e.g.:
.. code-block:: html+django
{% paginate entries.all as myentries %}
The code above is basically equivalent to:
.. code-block:: html+django
{% with entries.all as myentries %}
{% paginate myentries %}
{% endwith %}
In this case, and only in this case, the `as` argument is mandatory, and a
*TemplateSyntaxError* will be raised if the variable name is missing.
----
**New feature**: the page list object returned by the
:ref:`templatetags-get-pages` template tag has been improved adding the
following new methods:
.. code-block:: html+django
{# whether the page list contains more than one page #}
{{ pages.paginated }}
{# the 1-based index of the first item on the current page #}
{{ pages.current_start_index }}
{# the 1-based index of the last item on the current page #}
{{ pages.current_end_index }}
{# the total number of objects, across all pages #}
{{ pages.total_count }}
{# the first page represented as an arrow #}
{{ pages.first_as_arrow }}
{# the last page represented as an arrow #}
{{ pages.last_as_arrow }}
In the *arrow* representation, the page label defaults to ``<<`` for the first
page and to ``>>`` for the last one. As a consequence, the labels of the
previous and next pages are now single brackets, respectively ``<`` and ``>``.
First and last pages' labels can be customized using
``settings.ENDLESS_PAGINATION_FIRST_LABEL`` and
``settings.ENDLESS_PAGINATION_LAST_LABEL``: see :doc:`customization`.
----
**New feature**: The sequence returned by the callable
``settings.ENDLESS_PAGINATION_PAGE_LIST_CALLABLE`` can now contain two new
values:
- *'first'*: will display the first page as an arrow;
- *'last'*: will display the last page as an arrow.
The :ref:`templatetags-show-pages` template tag documentation describes how to
customize Digg-style pagination defining your own page list callable.
When using the default Digg-style pagination (i.e. when
``settings.ENDLESS_PAGINATION_PAGE_LIST_CALLABLE`` is set to *None*), it is
possible to enable first / last page arrows by setting the new flag
``settings.ENDLESS_PAGINATION_DEFAULT_CALLABLE_ARROWS`` to *True*.
----
**New feature**: ``settings.ENDLESS_PAGINATION_PAGE_LIST_CALLABLE`` can now be
either a callable or a **dotted path** to a callable, e.g.::
ENDLESS_PAGINATION_PAGE_LIST_CALLABLE = 'path.to.callable'
In addition to the default, ``endless_pagination.utils.get_page_numbers``, an
alternative implementation is now available:
``endless_pagination.utils.get_elastic_page_numbers``. It adapts its output
to the number of pages, making it arguably more usable when there are many
of them. To enable it, add the following line to your ``settings.py``::
ENDLESS_PAGINATION_PAGE_LIST_CALLABLE = (
'endless_pagination.utils.get_elastic_page_numbers')
----
**New feature**: ability to create a development and testing environment
(see :doc:`contributing`).
----
**New feature**: in addition to the ability to provide a customized pagination
URL as a context variable, the :ref:`templatetags-paginate` and
:ref:`templatetags-lazy-paginate` tags now support hardcoded pagination URL
endpoints, e.g.:
.. code-block:: html+django
{% paginate 20 entries with "/mypage/" %}
----
**New feature**: ability to specify negative indexes as values for the
``starting from page`` argument of the :ref:`templatetags-paginate` template
tag.
When changing the default page, it is now possible to reference the last page
(or the second last page, and so on) by using negative indexes, e.g:
.. code-block:: html+django
{% paginate entries starting from page -1 %}
See :doc:`templatetags_reference`.
----
**Documentation**: general clean up.
----
**Documentation**: added a :doc:`contributing` page. Have a look!
----
**Documentation**: included a comprehensive :doc:`javascript`.
----
**Fix**: ``endless_pagination.views.AjaxListView`` no longer subclasses
``django.views.generic.list.ListView``. Instead, the base objects and
mixins composing the final view are now defined by this app.
This change eliminates the ambiguity of having two separate pagination
machineries in place: the Django Endless Pagination one and the built-in
Django ``ListView`` one.
----
**Fix**: the *using* argument of :ref:`templatetags-paginate` and
:ref:`templatetags-lazy-paginate` template tags now correctly handles
querystring keys containing dashes, e.g.:
.. code-block:: html+django
{% lazy_paginate entries using "entries-page" %}
----
**Fix**: replaced namespace ``endless_pagination.paginator`` with
``endless_pagination.paginators``: the module contains more than one
paginator classes.
----
**Fix**: in some corner cases, loading ``endless_pagination.models`` raised
an *ImproperlyConfigured* error while trying to pre-load the templates.
----
**Fix**: replaced doctests with proper unittests. Improved the code coverage
as a consequence. Also introduced integration tests exercising JavaScript,
based on Selenium.
----
**Fix**: overall code lint and clean up.
Version 1.1
~~~~~~~~~~~
**New feature**: now it is possible to set the bottom margin used for
pagination on scroll (default is 1 pixel).
For example, if you want the pagination on scroll to be activated when
20 pixels remain until the end of the page:
.. code-block:: html+django
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="{{ STATIC_URL }}endless_pagination/js/endless.js"></script>
<script src="{{ STATIC_URL }}endless_pagination/js/endless_on_scroll.js"></script>
{# add the lines below #}
<script type="text/javascript" charset="utf-8">
var endless_on_scroll_margin = 20;
</script>
----
**New feature**: added ability to avoid Ajax requests when multiple pagination
is used.
A template for multiple pagination with Ajax support may look like this
(see :doc:`multiple_pagination`):
.. code-block:: html+django
{% block js %}
{{ block.super }}
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="{{ STATIC_URL }}endless_pagination/js/endless.js"></script>
{% endblock %}
<h2>Entries:</h2>
<div class="endless_page_template">
{% include "myapp/entries_page.html" %}
</div>
<h2>Other entries:</h2>
<div class="endless_page_template">
{% include "myapp/other_entries_page.html" %}
</div>
But what if you need Ajax pagination for *entries* but not for *other entries*?
You will only have to add a class named ``endless_page_skip`` to the
page container element, e.g.:
.. code-block:: html+django
<h2>Other entries:</h2>
<div class="endless_page_template endless_page_skip">
{% include "myapp/other_entries_page.html" %}
</div>
----
**New feature**: implemented a class-based generic view allowing
Ajax pagination of a list of objects (usually a queryset).
Intended as a substitution of *django.views.generic.ListView*, it recreates
the behaviour of the *page_template* decorator.
For a complete explanation, see :doc:`generic_views`.
----
**Fix**: the ``page_template`` and ``page_templates`` decorators no longer
hide the original view name and docstring (*update_wrapper*).
----
**Fix**: pagination on scroll now works on Firefox >= 4.
----
**Fix**: tests are now compatible with Django 1.3.
================================================
FILE: doc/conf.py
================================================
"""Django EL(Endless) Pagination documentation build configuration file."""
import os
import sys
sys.path.insert(0, os.path.abspath('..'))
AUTHOR = 'Francesco Banconi, Oleksandr Shtalinberg'
APP = 'Django EL(Endless) Pagination'
TITLE = APP + ' Documentation'
VERSION = '4.2.0'
# General information about the project.
project = APP
copyright = '2009-2024, ' + AUTHOR
author = 'Oleksandr Shtalinberg'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.intersphinx',
'sphinx.ext.viewcode',
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
exclude_patterns = [
'_build',
'Thumbs.db',
'.DS_Store',
'_static/TRACKME', # Exclude the TRACKME file from processing
]
# The suffix of source filenames.
source_suffix = '.rst'
# The master toctree document.
master_doc = 'index'
# The short X.Y version.
version = release = VERSION
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['_build']
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme = 'sphinx_rtd_theme'
html_theme_options = {
'navigation_depth': 4,
'collapse_navigation': False,
'sticky_navigation': True,
'includehidden': True,
'titles_only': False,
}
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
epub_exclude_files = [
'_static/TRACKME',
'search.html',
'_static/websupport.js',
]
# -- Epub options ---------------------------------------------------------
epub_show_urls = 'footnote'
epub_tocdepth = 3
epub_tocdup = True
epub_guide = (('toc', 'index.html', 'Table of Contents'),)
# Output file base name for HTML help builder.
htmlhelp_basename = 'DjangoELPaginationdoc'
# Grouping the document tree into LaTeX files. List of tuples (source start
# file, target name, title, author, documentclass [howto/manual]).
latex_documents = [(
'index', 'DjangoELPagination.tex', TITLE, AUTHOR, 'manual')]
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [('index', 'djangoelpagination', TITLE, [AUTHOR], 1)]
# Intersphinx configuration
intersphinx_mapping = {
'python': ('https://docs.python.org/3', None),
'django': (
'https://docs.djangoproject.com/en/stable/',
'https://docs.djangoproject.com/en/stable/_objects/',
),
}
================================================
FILE: doc/contacts.rst
================================================
Source code and contacts
========================
Repository and bugs
~~~~~~~~~~~~~~~~~~~
The **source code** for this app is hosted on
https://github.com/shtalinberg/django-el-pagination.
To file **bugs and requests**, please use
https://github.com/shtalinberg/django-el-pagination/issues.
Contacts
~~~~~~~~
Oleksandr Shtalinberg
- Email: ``o.shtalinberg at gmail.com``
Francesco Banconi
- Email: ``frankban at gmail.com``
- IRC: ``frankban@freenode``
================================================
FILE: doc/contributing.rst
================================================
Contributing
============
Here are the steps needed to set up a development and testing environment.
**WARNING**
This app use *git flow* for branching strategy and release management.
Please, change code and submit all pull requests into branch `develop`
Creating a development environment
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The development environment is created in a venv. The environment
creation requires the *make* program to be installed.
To install *make* under Debian/Ubuntu::
$ sudo apt-get install build-essential
Under Mac OS/X, *make* is available as part of XCode.
At this point, from the root of this branch, run the command::
$ make
This command will create a ``.venv`` directory in the branch root, ignored
by DVCSes, containing the development virtual environment with all the
dependencies.
Testing the application
~~~~~~~~~~~~~~~~~~~~~~~
To install *xvfb* (for integration tests) under Debian/Ubuntu::
$ sudo apt-get install xvfb
If you are on CentOS and using yum, it's::
$ yum install xorg-X11-server-Xvfb
Run the tests::
$ make test
The command above also runs all tests except the available integration. They use
Selenium and require Firefox to be installed. To include executing integration
tests, define the environment variable USE_SELENIUM, e.g.::
$ make test USE_SELENIUM=1
Integration tests are excluded by default when using Python 3. The test suite
requires Python >= 3.8.x.
Run the tests and lint/pep8 checks::
$ make check
Again, to exclude integration tests::
$ make check USE_SELENIUM=1
Debugging
~~~~~~~~~
Run the Django shell (Python interpreter)::
$ make shell
Run the Django development server for manual testing::
$ make server
After executing the command above, it is possible to navigate the testing
project going to <http://localhost:8000>.
See all the available make targets, including info on how to create a Python 3
development environment::
$ make help
Thanks for contributing, and have fun!
================================================
FILE: doc/current_page_number.rst
================================================
Getting the current page number
===============================
In the template
~~~~~~~~~~~~~~~
You can get and display the current page number in the template using
the :ref:`templatetags-show-current-number` template tag, e.g.:
.. code-block:: html+django
{% show_current_number %}
This call will display the current page number, but you can also
insert the value in the context as a template variable:
.. code-block:: html+django
{% show_current_number as page_number %}
{{ page_number }}
See the :ref:`templatetags-show-current-number` refrence for more information
on accepted arguments.
In the view
~~~~~~~~~~~
If you need to get the current page number in the view, you can use an utility
function called ``get_page_number_from_request``, e.g.::
from el_pagination import utils
page = utils.get_page_number_from_request(request)
If you are using :doc:`multiple pagination<multiple_pagination>`, or you have
changed the default querystring for pagination, you can pass the querystring
key as an optional argument::
page = utils.get_page_number_from_request(request, querystring_key=mykey)
If the page number is not present in the request, by default *1* is returned.
You can change this behaviour using::
page = utils.get_page_number_from_request(request, default=3)
================================================
FILE: doc/customization.rst
================================================
Customization
=============
Settings
~~~~~~~~
You can customize the application using ``settings.py``.
================================================= =========== ==============================================
Name Default Description
================================================= =========== ==============================================
``EL_PAGINATION_PER_PAGE`` 10 How many objects are normally displayed
in a page (overwriteable by templatetag).
------------------------------------------------- ----------- ----------------------------------------------
``EL_PAGINATION_PAGE_LABEL`` 'page' The querystring key of the page number
(e.g. ``http://example.com?page=2``).
------------------------------------------------- ----------- ----------------------------------------------
``EL_PAGINATION_ORPHANS`` 0 See Django *Paginator* definition of orphans.
------------------------------------------------- ----------- ----------------------------------------------
``EL_PAGINATION_LOADING`` 'loading' If you use the default ``show_more`` template,
here you can customize the content of the
loader hidden element. HTML is safe here,
e.g. you can show your pretty animated GIF
``EL_PAGINATION_LOADING = """<img src="/static/img/loader .gif" alt="loading" />"""``.
------------------------------------------------- ----------- ----------------------------------------------
``EL_PAGINATION_PREVIOUS_LABEL`` '<' Default label for the *previous* page link.
------------------------------------------------- ----------- ----------------------------------------------
``EL_PAGINATION_NEXT_LABEL`` '>' Default label for the *next* page link.
------------------------------------------------- ----------- ----------------------------------------------
``EL_PAGINATION_FIRST_LABEL`` '<<' Default label for the *first* page link.
------------------------------------------------- ----------- ----------------------------------------------
``EL_PAGINATION_LAST_LABEL`` '>>' Default label for the *last* page link.
------------------------------------------------- ----------- ----------------------------------------------
``EL_PAGINATION_ADD_NOFOLLOW`` *False* Set to *True* if your SEO alchemist
wants search engines not to follow
pagination links.
------------------------------------------------- ----------- ----------------------------------------------
``EL_PAGINATION_PAGE_LIST_CALLABLE`` *None* Callable (or dotted path to a callable) that
returns pages to be displayed.
If *None*, a default callable is used;
that produces :doc:`digg_pagination`.
The applicationt provides also a callable
producing elastic pagination:
``EL_pagination.utils.get_elastic_page_numbers``.
It adapts its output to the number of pages,
making it arguably more usable when there are
many of them.
See :doc:`templatetags_reference` for
information about writing custom callables.
------------------------------------------------- ----------- ----------------------------------------------
``EL_PAGINATION_DEFAULT_CALLABLE_EXTREMES`` 3 Default number of *extremes* displayed when
:doc:`digg_pagination` is used with the
default callable.
------------------------------------------------- ----------- ----------------------------------------------
``EL_PAGINATION_DEFAULT_CALLABLE_AROUNDS`` 2 Default number of *arounds* displayed when
:doc:`digg_pagination` is used with the
default callable.
------------------------------------------------- ----------- ----------------------------------------------
``EL_PAGINATION_DEFAULT_CALLABLE_ARROWS`` *False* Whether or not the first and last pages arrows
are displayed when :doc:`digg_pagination` is
used with the default callable.
------------------------------------------------- ----------- ----------------------------------------------
``EL_PAGINATION_TEMPLATE_VARNAME`` 'template' Template variable name used by the
``page_template`` decorator. You can change
this value if you are going to decorate
generic views using a different variable name
for the template (e.g. ``template_name``).
------------------------------------------------- ----------- ----------------------------------------------
``EL_PAGINATION_PAGE_OUT_OF_RANGE_404`` *False* If True on page out of range, throw a 404
exception, otherwise display the first page.
There is a view that maintains the original
functionality but sets the 404 status code
found in el_pagination\\views.py
------------------------------------------------- ----------- ----------------------------------------------
``EL_PAGINATION_USE_NEXT_PREVIOUS_LINKS`` *False* Add `is_previous` & `is_next` flags
for `previous` and `next` pages
================================================= =========== ==============================================
Templates and CSS
~~~~~~~~~~~~~~~~~
You can override the default template for ``show_more`` templatetag following
some rules:
- *more* link is shown only if the variable ``querystring`` is not False;
- the container (most external html element) class is *endless_container*;
- the *more* link and the loader hidden element live inside the container;
- the *more* link class is *endless_more*;
- the *more* link data-el-querystring-key attribute is ``{{ querystring_key }}``;
- the loader hidden element class is *endless_loading*.
================================================
FILE: doc/different_first_page.rst
================================================
Different number of items on the first page
===========================================
Sometimes you might want to show on the first page a different number of
items than on subsequent pages (e.g. in a movie detail page you want to show
4 images of the movie as a reminder, making the user click to see the next 20
images). To achieve this, use the :ref:`templatetags-paginate` or
:ref:`templatetags-lazy-paginate` tags with comma separated *first page* and
*per page* arguments, e.g.:
.. code-block:: html+django
{% load el_pagination_tags %}
{% lazy_paginate 4,20 entries %}
{% for entry in entries %}
{# your code to show the entry #}
{% endfor %}
{% show_more %}
This code will display 4 entries on the first page and 20 entries on the other
pages.
Of course the *first page* and *per page* arguments can be passed
as template variables, e.g.:
.. code-block:: html+django
{% lazy_paginate first_page,per_page entries %}
================================================
FILE: doc/digg_pagination.rst
================================================
Digg-style pagination
=====================
Digg-style pagination of queryset objects is really easy to implement. If Ajax
pagination is not needed, all you have to do is modifying the template, e.g.:
.. code-block:: html+django
{% load el_pagination_tags %}
{% paginate entries %}
{% for entry in entries %}
{# your code to show the entry #}
{% endfor %}
{% show_pages %}
That's it! As seen, the :ref:`templatetags-paginate` template tag takes care of
customizing the given queryset and the current template context. The
:ref:`templatetags-show-pages` one displays the page links allowing for
navigation to other pages.
Page by page
~~~~~~~~~~~~
If you only want to display previous and next links (in a page-by-page
pagination) you have to use the lower level :ref:`templatetags-get-pages`
template tag, e.g.:
.. code-block:: html+django
{% load el_pagination_tags %}
{% paginate entries %}
{% for entry in entries %}
{# your code to show the entry #}
{% endfor %}
{% get_pages %}
{{ pages.previous }} {{ pages.next }}
:doc:`customization` explains how to customize the arrows that go to previous
and next pages.
Showing indexes
~~~~~~~~~~~~~~~
The :ref:`templatetags-get-pages` template tag adds to the current template
context a ``pages`` variable containing several methods that can be used to
fully customize how the page links are displayed. For example, assume you want
to show the indexes of the entries in the current page, followed by the total
number of entries:
.. code-block:: html+django
{% load el_pagination_tags %}
{% paginate entries %}{% get_pages %}
{% for entry in entries %}
{# your code to show the entry #}
{% endfor %}
Showing entries
{{ pages.current_start_index }}-{{ pages.current_end_index }} of
{{ pages.total_count }}.
{# Just print pages to render the Digg-style pagination. #}
{{ pages.get_rendered }}
Number of pages
~~~~~~~~~~~~~~~
You can use ``{{ pages|length }}`` to retrieve and display the pages count.
A common use case is to change the layout or display additional info based
on whether the page list contains more than one page. This can be achieved
checking ``{% if pages|length > 1 %}``, or, in a more convenient way, using
``{{ pages.paginated }}``. For example, assume you want to change the layout,
or display some info, only if the page list contains more than one page, i.e.
the results are actually paginated:
.. code-block:: html+django
{% load el_pagination_tags %}
{% paginate entries %}
{% for entry in entries %}
{# your code to show the entry #}
{% endfor %}
{% get_pages %}
{% if pages.paginated %}
Some info/layout to display only if the available
objects span multiple pages...
{{ pages.get_rendered }}
{% endif %}
Again, for a full overview of the :ref:`templatetags-get-pages` and all the
other template tags, see the :doc:`templatetags_reference`.
.. _digg-ajax:
Adding Ajax
~~~~~~~~~~~
The view is exactly the same as the one used in
:ref:`Twitter-style Pagination<twitter-page-template>`::
from el_pagination.decorators import page_template
@page_template('myapp/entry_index_page.html') # just add this decorator
def entry_index(
request, template='myapp/entry_index.html', extra_context=None):
context = {
'entries': Entry.objects.all(),
}
if extra_context is not None:
context.update(extra_context)
return render(request, template, context)
As seen before in :doc:`twitter_pagination`, you have to
:ref:`split the templates<twitter-split-template>`, separating the main one from
the fragment representing the single page. However, this time a container for
the page template is also required and, by default, must be an element having a
class named *endless_page_template*.
*myapp/entry_index.html* becomes:
.. code-block:: html+django
<h2>Entries:</h2>
<div class="endless_page_template">
{% include page_template %}
</div>
{% block js %}
{{ block.super }}
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="{{ STATIC_URL }}el-pagination/js/el-pagination.js"></script>
<script>$.endlessPaginate();</script>
{% endblock %}
*myapp/entry_index_page.html* becomes:
.. code-block:: html+django
{% load el_pagination_tags %}
{% paginate entries %}
{% for entry in entries %}
{# your code to show the entry #}
{% endfor %}
{% show_pages %}
Done.
It is possible to manually
:ref:`override the container selector<javascript-selectors>` used by
*$.endlessPaginate()* to update the page contents. This can be easily achieved
by customizing the *pageSelector* option of *$.endlessPaginate()*, e.g.:
.. code-block:: html+django
<h2>Entries:</h2>
<div id="entries">
{% include page_template %}
</div>
{% block js %}
{{ block.super }}
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="{{ STATIC_URL }}el-pagination/js/el-pagination.js"></script>
<script>$.endlessPaginate({pageSelector: 'div#entries'});</script>
{% endblock %}
See the :doc:`javascript` for a detailed explanation of how to integrate
JavaScript and Ajax features in Django Endless Pagination.
================================================
FILE: doc/generic_views.rst
================================================
Generic views
=============
This application provides a customized class-based view, similar to
*django.views.generic.ListView*, that allows Ajax pagination of a
list of objects (usually a queryset).
AjaxListView reference
~~~~~~~~~~~~~~~~~~~~~~
.. py:module:: el_pagination.views
.. py:class:: AjaxListView(django.views.generic.ListView)
A class based view, similar to *django.views.generic.ListView*,
that allows Ajax pagination of a list of objects.
You can use this class based view in place of *ListView* in order to
recreate the behaviour of the *page_template* decorator.
For instance, assume you have this code (taken from Django docs)::
from django.conf.urls import url
from django.views.generic import ListView
from books.models import Publisher
urlpatterns = [
url(r'^publishers/$', ListView.as_view(model=Publisher)),
]
You want to Ajax paginate publishers, so, as seen, you need to switch
the template if the request is Ajax and put the page template
into the context as a variable named *page_template*.
This is straightforward, you only need to replace the view class, e.g.::
from django.conf.urls import *
from books.models import Publisher
from el_pagination.views import AjaxListView
urlpatterns = [
url(r'^publishers/$', AjaxListView.as_view(model=Publisher)),
]
.. py:attribute:: key
the querystring key used for the current pagination
(default: *settings.EL_PAGINATION_PAGE_LABEL*)
.. py:attribute:: page_template
the template used for the paginated objects
.. py:attribute:: page_template_suffix
the template suffix used for autogenerated page_template name
(when not given, default='_page')
.. py:method:: get_context_data(self, **kwargs)
Adds the *page_template* variable in the context.
If the *page_template* is not given as a kwarg of the *as_view*
method then it is invented using app label, model name
(obviously if the list is a queryset), *self.template_name_suffix*
and *self.page_template_suffix*.
For instance, if the list is a queryset of *blog.Entry*,
the template will be *myapp/publisher_list_page.html*.
.. py:method:: get_template_names(self)
Switch the templates for Ajax requests.
.. py:method:: get_page_template(self, **kwargs)
Only called if *page_template* is not given as a kwarg of
*self.as_view*.
Generic view example
~~~~~~~~~~~~~~~~~~~~
If the developer wants pagination of publishers, in *views.py* we have code class-based::
from django.views.generic import ListView
class EntryListView(ListView)
model = Publisher
template_name = "myapp/publisher_list.html"
context_object_name = "publisher_list"
or function-based::
def entry_index(request, template='myapp/publisher_list.html'):
context = {
'publisher_list': Entry.objects.all(),
}
return render(request, template, context)
In *myapp/publisher_list.html*:
.. code-block:: html+django
<h2>Entries:</h2>
{% for entry in publisher_list %}
{# your code to show the entry #}
{% endfor %}
This is just a basic example. To continue exploring more AjaxListView examples,
have a look at :doc:`twitter_pagination`
================================================
FILE: doc/index.rst
================================================
=============================
Django EL(Endless) Pagination
=============================
This application provides Twitter- and Digg-style pagination, with multiple
and lazy pagination and optional Ajax support. It is devoted to implementing
web pagination in very few steps.
The **source code** for this app is hosted at
https://github.com/shtalinberg/django-el-pagination.
:doc:`start` is easy!
Contents:
.. toctree::
:maxdepth: 2
changelog
start
twitter_pagination
digg_pagination
multiple_pagination
lazy_pagination
different_first_page
current_page_number
templatetags_reference
javascript
generic_views
customization
contributing
contacts
thanks
================================================
FILE: doc/javascript.rst
================================================
JavaScript reference
====================
For each type of pagination it is possible to enable Ajax so that the requested
page is loaded using an asynchronous request to the server. This is especially
important for :doc:`twitter_pagination` and
:ref:`endless pagination on scroll<javascript-pagination-on-scroll>`, but
:doc:`digg_pagination` can also take advantage of this technique.
Activating Ajax support
~~~~~~~~~~~~~~~~~~~~~~~
Ajax support is activated linking jQuery and the ``el-pagination.js`` file
included in this app. It is then possible to use the *$.endlessPaginate()*
jQuery plugin to enable Ajax pagination, e.g.:
.. code-block:: html+django
<h2>Entries:</h2>
<div class="endless_page_template">
{% include page_template %}
</div>
{% block js %}
{{ block.super }}
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="{{ STATIC_URL }}el-pagination/js/el-pagination.js"></script>
<script>$.endlessPaginate();</script>
{% endblock %}
This example assumes that you
:ref:`separated the fragment<twitter-split-template>` containing the single
page (*page_tempate*) from the main template (the code snipper above). More on
this in :doc:`twitter_pagination` and :doc:`digg_pagination`.
The *$.endlessPaginate()* call activates Ajax for each pagination present in
the page.
.. _javascript-pagination-on-scroll:
Pagination on scroll
~~~~~~~~~~~~~~~~~~~~
If you want new items to load when the user scrolls down the browser page,
you can use the **pagination on scroll** feature: just set the
*paginateOnScroll* option of *$.endlessPaginate()* to *true*, e.g.:
.. code-block:: html+django
<h2>Entries:</h2>
<div class="endless_page_template">
{% include page_template %}
</div>
{% block js %}
{{ block.super }}
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="{{ STATIC_URL }}el-pagination/js/el-pagination.js"></script>
<script>$.endlessPaginate({paginateOnScroll: true});</script>
{% endblock %}
That's all. See the :doc:`templatetags_reference` page to improve usage of
the included templatetags.
It is possible to set the **bottom margin** used for pagination on scroll
(default is 1 pixel). For example, if you want the pagination on scroll
to be activated when 20 pixels remain to the end of the page:
.. code-block:: html+django
<h2>Entries:</h2>
<div class="endless_page_template">
{% include page_template %}
</div>
{% block js %}
{{ block.super }}
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="{{ STATIC_URL }}el-pagination/js/el-pagination.js"></script>
<script>
$.endlessPaginate({
paginateOnScroll: true,
paginateOnScrollMargin: 200
});
</script>
{% endblock %}
Attaching callbacks
~~~~~~~~~~~~~~~~~~~
It is possible to customize the behavior of JavaScript pagination by attaching
callbacks to *$.endlessPaginate()*, called when the following events are fired:
- *onClick*: the user clicks on a page link;
- *onCompleted*: the new page is fully loaded and inserted in the DOM.
The context of both callbacks is the clicked link fragment: in other words,
inside the callbacks, *this* will be the HTML fragment representing the clicked
link, e.g.:
.. code-block:: html+django
<h2>Entries:</h2>
<div class="endless_page_template">
{% include page_template %}
</div>
{% block js %}
{{ block.super }}
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="{{ STATIC_URL }}el-pagination/js/el-pagination.js"></script>
<script>
$.endlessPaginate({
onClick: function() {
console.log('Label:', $(this).text());
}
});
</script>
{% endblock %}
Both callbacks also receive a *context* argument containing information about
the requested page:
- *context.url*: the requested URL;
- *context.key*: the querystring key used to retrieve the requested contents.
If the *onClick* callback returns *false*, the pagination process is stopped,
the Ajax request is not performed and the *onCompleted* callback never called.
The *onCompleted* callbacks also receives a second argument: the data returned
by the server. Basically this is the HTML fragment representing the new
requested page.
To wrap it up, here is an example showing the callbacks' signatures:
.. code-block:: html+django
<h2>Entries:</h2>
<div class="endless_page_template">
{% include page_template %}
</div>
{% block js %}
{{ block.super }}
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="{{ STATIC_URL }}el-pagination/js/el-pagination.js"></script>
<script>
$.endlessPaginate({
onClick: function(context) {
console.log('Label:', $(this).text());
console.log('URL:', context.url);
console.log('Querystring key:', context.key);
if (forbidden) { // to be defined...
return false;
}
},
onCompleted: function(context, fragment) {
console.log('Label:', $(this).text());
console.log('URL:', context.url);
console.log('Querystring key:', context.key);
console.log('Fragment:', fragment);
}
});
</script>
{% endblock %}
Manually selecting what to bind
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
As seen above, *$.endlessPaginate()* enables Ajax support for each pagination
in the page. But assuming you are using :doc:`multiple_pagination`, e.g.:
.. code-block:: html+django
<h2>Entries:</h2>
<div id="entries" class="endless_page_template">
{% include "myapp/entries_page.html" %}
</div>
<h2>Other entries:</h2>
<div id="other-entries" class="endless_page_template">
{% include "myapp/other_entries_page.html" %}
</div>
{% block js %}
{{ block.super }}
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="{{ STATIC_URL }}el-pagination/js/el-pagination.js"></script>
<script>$.endlessPaginate();</script>
{% endblock %}
What if you need Ajax pagination only for *entries* and not for
*other entries*? You can do this in a straightforward way using jQuery
selectors, e.g.:
.. code-block:: html+django
{% block js %}
{{ block.super }}
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="{{ STATIC_URL }}el-pagination/js/el-pagination.js"></script>
<script>$('#entries').endlessPaginate();</script>
{% endblock %}
The call to *$('#entries').endlessPaginate()* applies Ajax pagination starting
from the DOM node with id *entries* and to all sub-nodes. This means that
*other entries* are left intact. Of course you can use any selector supported
by jQuery.
At this point, you might have already guessed that *$.endlessPaginate()*
is just an alias for *$('body').endlessPaginate()*.
Customize each pagination
~~~~~~~~~~~~~~~~~~~~~~~~~
You can also call *$.endlessPaginate()* multiple times if you want to customize
the behavior of each pagination. E.g. if you need to register a callback for
*entries* but not for *other entries*:
.. code-block:: html+django
<h2>Entries:</h2>
<div id="entries" class="endless_page_template">
{% include "myapp/entries_page.html" %}
</div>
<h2>Other entries:</h2>
<div id="other-entries" class="endless_page_template">
{% include "myapp/other_entries_page.html" %}
</div>
{% block js %}
{{ block.super }}
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="{{ STATIC_URL }}el-pagination/js/el-pagination.js"></script>
<script>
$('#entries').endlessPaginate({
onCompleted: function(data) {
console.log('New entries loaded.');
}
});
$('#other-entries').endlessPaginate();
</script>
{% endblock %}
.. _javascript-selectors:
Selectors
~~~~~~~~~
Each time *$.endlessPaginate()* is used, several JavaScript selectors are used
to select DOM nodes. Here is a list of them all:
- containerSelector: '.endless_container'
(Twitter-style pagination container selector);
- loadingSelector: '.endless_loading' -
(Twitter-style pagination loading selector);
- moreSelector: 'a.endless_more' -
(Twitter-style pagination link selector);
- contentSelector: null -
(Twitter-style pagination content wrapper);
- pageSelector: '.endless_page_template'
(Digg-style pagination page template selector);
- pagesSelector: 'a.endless_page_link'
(Digg-style pagination link selector).
An example can better explain the meaning of the selectors above. Assume you
have a Digg-style pagination like the following:
.. code-block:: html+django
<h2>Entries:</h2>
<div id="entries" class="endless_page_template">
{% include "myapp/entries_page.html" %}
</div>
{% block js %}
{{ block.super }}
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="{{ STATIC_URL }}el-pagination/js/el-pagination.js"></script>
<script>
$('#entries').endlessPaginate();
</script>
{% endblock %}
Here the ``#entries`` node is selected and Digg-style pagination is applied.
Digg-style needs to know which DOM node will be updated with new contents,
and in this case it's the same node we selected, because we added the
*endless_page_template* class to that node, and *.endless_page_template*
is the selector used by default. However, the following example is equivalent
and does not involve adding another class to the container:
.. code-block:: html+django
<h2>Entries:</h2>
<div id="entries">
{% include "myapp/entries_page.html" %}
</div>
{% block js %}
{{ block.super }}
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="{{ STATIC_URL }}el-pagination/js/el-pagination.js"></script>
<script>
$('#entries').endlessPaginate({
pageSelector: '#entries'
});
</script>
{% endblock %}
.. _javascript-chunks:
On scroll pagination using chunks
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Sometimes, when using on scroll pagination, you may want to still display
the *show more* link after each *N* pages. In Django Endless Pagination this is
called *chunk size*. For instance, a chunk size of 5 means that a *show more*
link is displayed after page 5 is loaded, then after page 10, then after page
15 and so on. Activating this functionality is straightforward, just use the
*paginateOnScrollChunkSize* option:
.. code-block:: html+django
{% block js %}
{{ block.super }}
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="{{ STATIC_URL }}el-pagination/js/el-pagination.js"></script>
<script>
$.endlessPaginate({
paginateOnScroll: true,
paginateOnScrollChunkSize: 5
});
</script>
{% endblock %}
Each time a chunk size is complete, the class ``endless_chunk_complete`` is added to the *show more* link,
so you still have a way to distinguish between the implicit
click done by the scroll event and a real click on the button.
.. _javascript-migrate:
Migrate from version 1.1 to 2.1
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Django Endless Pagination v2.0 introduces changes in how Ajax pagination
is handled by JavaScript. These changes are discussed in this document and in
the :doc:`changelog`.
The JavaScript code now lives in a file named ``el-pagination.js``.
The two JavaScript files ``el-pagination-endless.js`` and ``el-pagination_on_scroll.js`` was removed.
However, please consider migrating: the old JavaScript files was removed, are
no longer maintained, and don't provide the new JavaScript features.
Instructions on how to migrate from the old version to the new one follow.
Basic migration
---------------
Before:
.. code-block:: html+django
<h2>Entries:</h2>
{% include page_template %}
{% block js %}
{{ block.super }}
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="{{ STATIC_URL }}el-pagination/js/el-pagination-endless.js"></script>
{% endblock %}
Now:
.. code-block:: html+django
<h2>Entries:</h2>
{% include page_template %}
{% block js %}
{{ block.super }}
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="{{ STATIC_URL }}el-pagination/js/el-pagination.js"></script>
<script>$.endlessPaginate();</script>
{% endblock %}
Pagination on scroll
--------------------
Before:
.. code-block:: html+django
<h2>Entries:</h2>
{% include page_template %}
{% block js %}
{{ block.super }}
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="{{ STATIC_URL }}el-pagination/js/el-pagination-endless.js"></script>
<script src="{{ STATIC_URL }}el-pagination/js/el-pagination_on_scroll.js"></script>
{% endblock %}
Now:
.. code-block:: html+django
<h2>Entries:</h2>
{% include page_template %}
{% block js %}
{{ block.super }}
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="{{ STATIC_URL }}el-pagination/js/el-pagination.js"></script>
<script>
$.endlessPaginate({paginateOnScroll: true});
</script>
{% endblock %}
Pagination on scroll with customized bottom margin
--------------------------------------------------
Before:
.. code-block:: html+django
<h2>Entries:</h2>
{% include page_template %}
{% block js %}
{{ block.super }}
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="{{ STATIC_URL }}el-pagination/js/el-pagination-endless.js"></script>
<script src="{{ STATIC_URL }}el-pagination/js/el-pagination_on_scroll.js"></script>
<script>
var endless_on_scroll_margin = 200;
</script>
{% endblock %}
Now:
.. code-block:: html+django
<h2>Entries:</h2>
{% include page_template %}
{% block js %}
{{ block.super }}
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="{{ STATIC_URL }}el-pagination/js/el-pagination.js"></script>
<script>
$.endlessPaginate({
paginateOnScroll: true,
paginateOnScrollMargin: 200
});
</script>
{% endblock %}
Avoid enabling Ajax on one or more paginations
----------------------------------------------
Before:
.. code-block:: html+django
<h2>Other entries:</h2>
<div class="endless_page_template endless_page_skip">
{% include "myapp/other_entries_page.html" %}
</div>
{% block js %}
{{ block.super }}
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="{{ STATIC_URL }}el-pagination/js/el-pagination-endless.js"></script>
{% endblock %}
Now:
.. code-block:: html+django
<h2>Other entries:</h2>
<div class="endless_page_template endless_page_skip">
{% include "myapp/other_entries_page.html" %}
</div>
{% block js %}
{{ block.super }}
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="{{ STATIC_URL }}el-pagination/js/el-pagination.js"></script>
<script>$('not:(.endless_page_skip)').endlessPaginate();</script>
{% endblock %}
In this last example, activating Ajax just where you want might be preferred
over excluding nodes.
================================================
FILE: doc/lazy_pagination.rst
================================================
Lazy pagination
===============
Usually pagination requires hitting the database to get the total number of
items to display. Lazy pagination avoids this *select count* query and results
in a faster page load, with a disadvantage: you won't know the total number of
pages in advance.
For this reason it is better to use lazy pagination in conjunction with
:doc:`twitter_pagination` (e.g. using the :ref:`templatetags-show-more`
template tag).
In order to switch to lazy pagination you have to use the
:ref:`templatetags-lazy-paginate` template tag instead of the
:ref:`templatetags-paginate` one, e.g.:
.. code-block:: html+django
{% load el_pagination_tags %}
{% lazy_paginate entries %}
{% for entry in entries %}
{# your code to show the entry #}
{% endfor %}
{% show_more %}
The :ref:`templatetags-lazy-paginate` tag can take all the args of the
:ref:`templatetags-paginate` one, with one exception: negative indexes can not
be passed to the ``starting from page`` argument.
================================================
FILE: doc/multiple_pagination.rst
================================================
Multiple paginations in the same page
=====================================
Sometimes it is necessary to show different types of paginated objects in the
same page. In this case we have to associate a different querystring key
to every pagination.
Normally, the key used is the one specified in
``settings.ENDLESS_PAGINATION_PAGE_LABEL`` (see :doc:`customization`),
but in the case of multiple pagination the application provides a simple way to
override the settings.
If you do not need Ajax, the only file you need to edit is the template.
Here is an example with 2 different paginations (*entries* and *other_entries*)
in the same page, but there is no limit to the number of different paginations
in a page:
.. code-block:: html+django
{% load el_pagination_tags %}
{% paginate entries %}
{% for entry in entries %}
{# your code to show the entry #}
{% endfor %}
{% show_pages %}
{# "other_entries_page" is the new querystring key #}
{% paginate other_entries using "other_entries_page" %}
{% for entry in other_entries %}
{# your code to show the entry #}
{% endfor %}
{% show_pages %}
The ``using`` argument of the :ref:`templatetags-paginate` template tag allows
you to choose the name of the querystring key used to track the page number.
If not specified the system falls back to
``settings.EL_PAGINATION_PAGE_LABEL``.
In the example above, the url *http://example.com?page=2&other_entries_page=3*
requests the second page of *entries* and the third page of *other_entries*.
The name of the querystring key can also be dinamically passed in the template
context, e.g.:
.. code-block:: html+django
{# page_variable is not surrounded by quotes #}
{% paginate other_entries using page_variable %}
You can use any style of pagination: :ref:`templatetags-show-pages`,
:ref:`templatetags-get-pages`, :ref:`templatetags-show-more` etc...
(see :doc:`templatetags_reference`).
Adding Ajax for multiple pagination
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Obviously each pagination needs a template for the page contents. Remember to
box each page in a div with a class called *endless_page_template*, or to
specify the container selector passing an option to *$.endlessPaginate()* as
seen in :ref:`Digg-style pagination and Ajax<digg-ajax>`.
*myapp/entry_index.html*:
.. code-block:: html+django
<h2>Entries:</h2>
<div class="endless_page_template">
{% include "myapp/entries_page.html" %}
</div>
<h2>Other entries:</h2>
<div class="endless_page_template">
{% include "myapp/other_entries_page.html" %}
</div>
{% block js %}
{{ block.super }}
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="{{ STATIC_URL }}el-pagination/js/el-pagination.js"></script>
<script>$.endlessPaginate();</script>
{% endblock %}
See the :doc:`javascript` for further details on how to use the included
jQuery plugin.
*myapp/entries_page.html*:
.. code-block:: html+django
{% load el_pagination_tags %}
{% paginate entries %}
{% for entry in entries %}
{# your code to show the entry #}
{% endfor %}
{% show_pages %}
*myapp/other_entries_page.html*:
.. code-block:: html+django
{% load el_pagination_tags %}
{% paginate other_entries using other_entries_page %}
{% for entry in other_entries %}
{# your code to show the entry #}
{% endfor %}
{% show_pages %}
As seen :ref:`before<twitter-page-template>`, the decorator ``page_template``
simplifies the management of Ajax requests in views. You must, however, map
different paginations to different page templates.
You can chain decorator calls relating a template to the associated
querystring key, e.g.::
from endless_pagination.decorators import page_template
@page_template('myapp/entries_page.html')
@page_template('myapp/other_entries_page.html', key='other_entries_page')
def entry_index(
request, template='myapp/entry_index.html', extra_context=None):
context = {
'entries': Entry.objects.all(),
'other_entries': OtherEntry.objects.all(),
}
if extra_context is not None:
context.update(extra_context)
return render_to_response(
template, context, context_instance=RequestContext(request))
As seen in previous examples, if you do not specify the *key* kwarg in the
decorator, then the page template is associated to the querystring key
defined in the settings.
.. _multiple-page-templates:
You can use the ``page_templates`` (note the trailing *s*) decorator in
substitution of a decorator chain when you need multiple Ajax paginations.
The previous example can be written as::
from endless_pagination.decorators import page_templates
@page_templates({
'myapp/entries_page.html': None,
'myapp/other_entries_page.html': 'other_entries_page',
})
def entry_index():
...
As seen, a dict object is passed to the ``page_templates`` decorator, mapping
templates to querystring keys. Alternatively, you can also pass a sequence
of ``(template, key)`` pairs, e.g.::
from endless_pagination.decorators import page_templates
@page_templates((
('myapp/entries_page.html', None),
('myapp/other_entries_page.html', 'other_entries_page'),
))
def entry_index():
...
This also supports serving different paginated objects with the same template.
Manually selecting what to bind
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
What if you need Ajax pagination only for *entries* and not for
*other entries*? You can do this in a straightforward way using jQuery
selectors, e.g.:
.. code-block:: html+django
{% block js %}
{{ block.super }}
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="{{ STATIC_URL }}el-pagination/js/el-pagination.js"></script>
<script>$('#entries').endlessPaginate();</script>
{% endblock %}
The call to *$('#entries').endlessPaginate()* applies Ajax pagination starting
from the DOM node with id *entries* and to all sub-nodes. This means that
*other entries* are left intact. Of course you can use any selector supported
by jQuery.
Refer to the :doc:`javascript` for an explanation of other features like
calling *$.endlessPaginate()* multiple times in order to customize the behavior
of each pagination in a multiple pagination view.
================================================
FILE: doc/requirements.txt
================================================
# Documentation dependencies
sphinx>=5.0.0,<6.0.0
sphinx-rtd-theme>=1.0.0
sphinxcontrib-applehelp>=1.0.4
sphinxcontrib-devhelp>=1.0.2
sphinxcontrib-htmlhelp>=2.0.0
sphinxcontrib-serializinghtml>=1.1.5
sphinxcontrib-qthelp>=1.0.3
docutils>=0.17.1
jinja2>=3.0.0
# Project dependencies
django>=3.2.0
================================================
FILE: doc/start.rst
================================================
Getting started
===============
Requirements
~~~~~~~~~~~~
====== ====================
Python >= 3.8
Django >= 3.2
jQuery >= 1.11.1
====== ====================
Installation
~~~~~~~~~~~~
The Git repository can be cloned with this command::
git clone https://github.com/shtalinberg/django-el-pagination.git
The ``el_pagination`` package, included in the distribution, should be
placed on the ``PYTHONPATH``.
Otherwise you can just ``easy_install -Z django-el-pagination``
or ``pip install django-el-pagination``.
Settings
~~~~~~~~
Add the request context processor to your *settings.py*, e.g.:
.. code-block:: python
from django.conf.global_settings import TEMPLATES
TEMPLATES[0]['OPTIONS']['context_processors'].insert(0, 'django.core.context_processors.request')
or just adding it to the context_processors manually like so:
.. code-block:: python
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates'), ],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'...',
'...',
'...',
'...',
'django.template.context_processors.request', ## For EL-pagination
],
},
},
]
Add ``'el_pagination'`` to the ``INSTALLED_APPS`` to your *settings.py*.
See the :doc:`customization` section for other settings.
Quickstart
~~~~~~~~~~
Given a template like this:
.. code-block:: html+django
{% for entry in entries %}
{# your code to show the entry #}
{% endfor %}
you can use Digg-style pagination to display objects just by adding:
.. code-block:: html+django
{% load el_pagination_tags %}
{% paginate entries %}
{% for entry in entries %}
{# your code to show the entry #}
{% endfor %}
{% show_pages %}
Done.
This is just a basic example. To continue exploring all the Django Endless
Pagination features, have a look at :doc:`twitter_pagination` or
:doc:`digg_pagination`.
================================================
FILE: doc/templatetags_reference.rst
================================================
Templatetags reference
======================
.. _templatetags-paginate:
paginate
~~~~~~~~
Usage:
.. code-block:: html+django
{% paginate entries %}
After this call, the *entries* variable in the template context is replaced
by only the entries of the current page.
You can also keep your *entries* original variable (usually a queryset)
and add to the context another name that refers to entries of the current page,
e.g.:
.. code-block:: html+django
{% paginate entries as page_entries %}
The *as* argument is also useful when a nested context variable is provided
as queryset. In this case, and only in this case, the resulting variable
name is mandatory, e.g.:
.. code-block:: html+django
{% paginate entries.all as entries %}
The number of paginated entries is taken from settings, but you can
override the default locally, e.g.:
.. code-block:: html+django
{% paginate 20 entries %}
Of course you can mix it all:
.. code-block:: html+django
{% paginate 20 entries as paginated_entries %}
By default, the first page is displayed the first time you load the page,
but you can change this, e.g.:
.. code-block:: html+django
{% paginate entries starting from page 3 %}
When changing the default page, it is also possible to reference the last
page (or the second last page, and so on) by using negative indexes, e.g:
.. code-block:: html+django
{% paginate entries starting from page -1 %}
This can be also achieved using a template variable that was passed to the
context, e.g.:
.. code-block:: html+django
{% paginate entries starting from page page_number %}
If the passed page number does not exist, the first page is displayed.
Note that negative indexes are specific to the ``{% paginate %}`` tag: this
feature cannot be used when contents are lazy paginated (see `lazy_paginate`_
below).
If you have multiple paginations in the same page, you can change the
querydict key for the single pagination, e.g.:
.. code-block:: html+django
{% paginate entries using article_page %}
In this case *article_page* is intended to be a context variable, but you can
hardcode the key using quotes, e.g.:
.. code-block:: html+django
{% paginate entries using 'articles_at_page' %}
Again, you can mix it all (the order of arguments is important):
.. code-block:: html+django
{% paginate 20 entries starting from page 3 using page_key as paginated_entries %}
Additionally you can pass a path to be used for the pagination:
.. code-block:: html+django
{% paginate 20 entries using page_key with pagination_url as paginated_entries %}
This way you can easily create views acting as API endpoints, and point your
Ajax calls to that API. In this case *pagination_url* is considered a
context variable, but it is also possible to hardcode the URL, e.g.:
.. code-block:: html+django
{% paginate 20 entries with "/mypage/" %}
If you want the first page to contain a different number of items than
subsequent pages, you can separate the two values with a comma, e.g. if
you want 3 items on the first page and 10 on other pages:
.. code-block:: html+django
{% paginate 3,10 entries %}
You must use this tag before calling the `show_more`_, `get_pages`_ or
`show_pages`_ ones.
.. _templatetags-lazy-paginate:
lazy_paginate
~~~~~~~~~~~~~
Paginate objects without hitting the database with a *select count* query.
Usually pagination requires hitting the database to get the total number of
items to display. Lazy pagination avoids this *select count* query and results
in a faster page load, with a disadvantage: you won't know the total number of
pages in advance.
Use this in the same way as `paginate`_ tag when you are not interested in the
total number of pages.
The ``lazy_paginate`` tag can take all the args of the ``paginate`` one, with
one exception: negative indexes can not be passed to the ``starting from page``
argument.
.. _templatetags-show-more:
show_more
~~~~~~~~~
Show the link to get the next page in a :doc:`twitter_pagination`. Usage:
.. code-block:: html+django
{% show_more %}
Alternatively you can override the label passed to the default template:
.. code-block:: html+django
{% show_more "even more" %}
You can override the loading text too:
.. code-block:: html+django
{% show_more "even more" "working" %}
Must be called after `paginate`_ or `lazy_paginate`_.
.. _templatetags-show-more-table:
show_more_table
~~~~~~~~~~~~~~~
Same as the `show_more`_, but for table pagination. Usage:
.. code-block:: html+django
{% show_more_table %}
If use table in a :doc:`twitter_pagination`:
.. code-block:: html+django
<table>
{% include page_template %}
</table>
then page template:
.. code-block:: html+django
{% load el_pagination_tags %}
{% paginate 5 objects %}
{% for object in objects %}
<tr>
<td>
{{ object.title }}
</td>
</tr>
{% endfor %}
{% show_more_table "More results" %}
For :doc:`digg_pagination` use instead `show_more_table` in page template:
.. code-block:: html+django
<tr>
<td>{% show_pages %}</td>
</td>
.. _templatetags-get-pages:
get_pages
~~~~~~~~~
Usage:
.. code-block:: html+django
{% get_pages %}
This is mostly used for :doc:`digg_pagination`.
This call inserts in the template context a *pages* variable, as a sequence
of page links. You can use *pages* in different ways:
- just print *pages* and you will get Digg-style pagination displayed:
.. code-block:: html+django
{{ pages.get_rendered }}
- display pages count:
.. code-block:: html+django
{{ pages|length }}
- display numbers of objects in per page:
.. code-block:: html+django
{{ pages.per_page_number }}
- check if the page list contains more than one page:
.. code-block:: html+django
{{ pages.paginated }}
{# the following is equivalent #}
{{ pages|length > 1 }}
- get a specific page:
.. code-block:: html+django
{# the current selected page #}
{{ pages.current }}
{# the first page #}
{{ pages.first }}
{# the last page #}
{{ pages.last }}
{# the previous page (or nothing if you are on first page) #}
{{ pages.previous }}
{# the next page (or nothing if you are in last page) #}
{{ pages.next }}
{# the third page #}
{{ pages.3 }}
{# this means page.1 is the same as page.first #}
{# the 1-based index of the first item on the current page #}
{{ pages.current_start_index }}
{# the 1-based index of the last item on the current page #}
{{ pages.current_end_index }}
{# the total number of objects, across all pages #}
{{ pages.total_count }}
{# the first page represented as an arrow #}
{{ pages.first_as_arrow }}
{# the last page represented as an arrow #}
{{ pages.last_as_arrow }}
- iterate over *pages* to get all pages:
.. code-block:: html+django
{% for page in pages %}
{# display page link #}
{{ page.render_link }}
{# the page url (beginning with "?") #}
{{ page.url }}
{# the page path #}
{{ page.path }}
{# the page number #}
{{ page.number }}
{# a string representing the page (commonly the page number) #}
{{ page.label }}
{# check if the page is the current one #}
{{ page.is_current }}
{# check if the page is the first one #}
{{ page.is_first }}
{# check if the page is the last one #}
{{ page.is_last }}
{### next two example work only with settings.EL_PAGINATION_USE_NEXT_PREVIOUS_LINKS = True ###}
{# check if the page is previous #}
{{ page.is_previous }}
{# check if the page is_next #}
{{ page.is_next }}
{% endfor %}
You can change the variable name, e.g.:
.. code-block:: html+django
{% get_pages as page_links %}
{{ page_links.get_rendered }}
{# the current selected page #}
{{ page_links.current }}
This must be called after `paginate`_ or `lazy_paginate`_.
.. _templatetags-show-pages:
show_pages
~~~~~~~~~~
Show page links. Usage:
.. code-block:: html+django
{% show_pages %}
It is just a shortcut for:
.. code-block:: html+django
{% get_pages %}
{{ pages.get_rendered }}
You can set ``EL_PAGINATION_PAGE_LIST_CALLABLE`` in your *settings.py* to
a callable used to customize the pages that are displayed.
``EL_PAGINATION_PAGE_LIST_CALLABLE`` can also be a dotted path
representing a callable, e.g.::
EL_PAGINATION_PAGE_LIST_CALLABLE = 'path.to.callable'
The callable takes the current page number and the total number of pages,
and must return a sequence of page numbers that will be displayed.
The sequence can contain other values:
- *'previous'*: will display the previous page in that position;
- *'next'*: will display the next page in that position;
- *'first'*: will display the first page as an arrow;
- *'last'*: will display the last page as an arrow;
- *None*: a separator will be displayed in that position.
Here is an example of a custom callable that displays the previous page, then
the first page, then a separator, then the current page, and finally the last
page::
def get_page_numbers(current_page, num_pages):
return ('previous', 1, None, current_page, 'last')
If ``EL_PAGINATION_PAGE_LIST_CALLABLE`` is *None* the internal callable
``endless_pagination.utils.get_page_numbers`` is used, generating a Digg-style
pagination.
An alternative implementation is available:
``endless_pagination.utils.get_elastic_page_numbers``: it adapts its output
to the number of pages, making it arguably more usable when there are many
of them.
This must be called after `paginate`_ or `lazy_paginate`_.
.. _templatetags-show-current-number:
show_current_number
~~~~~~~~~~~~~~~~~~~
Show the current page number, or insert it in the context.
This tag can for example be useful to change the page title according to
the current page number.
To just show current page number:
.. code-block:: html+django
{% show_current_number %}
If you use multiple paginations in the same page, you can get the page
number for a specific pagination using the querystring key, e.g.:
.. code-block:: html+django
{% show_current_number using mykey %}
The default page when no querystring is specified is 1. If you changed it
in the `paginate`_ template tag, you have to call ``show_current_number``
according to your choice, e.g.:
.. code-block:: html+django
{% show_current_number starting from page 3 %}
This can be also achieved using a template variable you passed to the
context, e.g.:
.. code-block:: html+django
{% show_current_number starting from page page_number %}
You can of course mix it all (the order of arguments is important):
.. code-block:: html+django
{% show_current_number starting from page 3 using mykey %}
If you want to insert the current page number in the context, without
actually displaying it in the template, use the *as* argument, i.e.:
.. code-block:: html+django
{% show_current_number as page_number %}
{% show_current_number starting from page 3 using mykey as page_number %}
================================================
FILE: doc/thanks.rst
================================================
Thanks
======
This application was initially inspired by the excellent tool
*django-pagination* (see https://github.com/ericflo/django-pagination).
Thanks to Francesco Banconi for improving previous version of this application
(django-endless-pagination)
Thanks to Jannis Leidel for improving the application with some new features,
and for contributing the German translation.
And thanks to Nicola 'tekNico' Larosa for reviewing the documentation and for
implementing the elastic pagination feature.
================================================
FILE: doc/twitter_pagination.rst
================================================
Twitter-style Pagination
========================
Assuming the developer wants Twitter-style pagination of
entries of a blog post, in *views.py* we have class-based::
from el_pagination.views import AjaxListView
class EntryListView(AjaxListView):
context_object_name = "entry_list"
template_name = "myapp/entry_list.html"
def get_queryset(self):
return Entry.objects.all()
or function-based::
def entry_index(request, template='myapp/entry_list.html'):
context = {
'entry_list': Entry.objects.all(),
}
return render(request, template, context)
In *myapp/entry_list.html*:
.. code-block:: html+django
<h2>Entries:</h2>
{% for entry in entry_list %}
{# your code to show the entry #}
{% endfor %}
.. _twitter-split-template:
Split the template
~~~~~~~~~~~~~~~~~~
The response to an Ajax request should not return the entire template,
but only the portion of the page to be updated or added.
So it is convenient to extract from the template the part containing the
entries, and use it to render the context if the request is Ajax.
The main template will include the extracted part, so it is convenient
to put the page template name in the context.
*views.py* class-based becomes::
from el_pagination.views import AjaxListView
class EntryListView(AjaxListView):
context_object_name = "entry_list"
template_name = "myapp/entry_list.html"
page_template='myapp/entry_list_page.html'
def get_queryset(self):
return Entry.objects.all()
or function-based::
def entry_list(request,
template='myapp/entry_list.html',
page_template='myapp/entry_list_page.html'):
context = {
'entry_list': Entry.objects.all(),
'page_template': page_template,
}
if request.headers.get('x-requested-with') == 'XMLHttpRequest':
template = page_template
return render(request, template, context)
See :ref:`below<twitter-page-template>` how to obtain the same result
**just decorating the view**.
*myapp/entry_list.html* becomes:
.. code-block:: html+django
<h2>Entries:</h2>
{% include page_template %}
*myapp/entry_list_page.html* becomes:
.. code-block:: html+django
{% for entry in entry_list %}
{# your code to show the entry #}
{% endfor %}
.. _twitter-page-template:
A shortcut for ajaxed views
~~~~~~~~~~~~~~~~~~~~~~~~~~~
A good practice in writing views is to allow other developers to inject
the template name and extra data, so that they are added to the context.
This allows the view to be easily reused. Let's resume the original view
with extra context injection:
*views.py*::
def entry_index(request,
template='myapp/entry_list.html', extra_context=None):
context = {
'entry_list': Entry.objects.all(),
}
if extra_context is not None:
context.update(extra_context)
return render(request, template, context)
Splitting templates and putting the Ajax template name in the context
is easily achievable by using an included decorator.
*views.py* becomes::
from el_pagination.decorators import page_template
@page_template('myapp/entry_list_page.html') # just add this decorator
def entry_list(request,
template='myapp/entry_list.html', extra_context=None):
context = {
'entry_list': Entry.objects.all(),
}
if extra_context is not None:
context.update(extra_context)
return render(request, template, context)
Paginating objects
~~~~~~~~~~~~~~~~~~
All that's left is changing the page template and loading the
:doc:`endless templatetags<templatetags_reference>`, the jQuery library and the
jQuery plugin ``el-pagination.js`` included in the distribution under
``/static/el-pagination/js/``.
*myapp/entry_list.html* becomes:
.. code-block:: html+django
<h2>Entries:</h2>
{% include page_template %}
{% block js %}
{{ block.super }}
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="{{ STATIC_URL }}el-pagination/js/el-pagination.js"></script>
<script>$.endlessPaginate();</script>
{% endblock %}
*myapp/entry_list_page.html* becomes:
.. code-block:: html+django
{% load el_pagination_tags %}
{% paginate entry_list %}
{% for entry in entry_list %}
{# your code to show the entry #}
{% endfor %}
{% show_more %}
The :ref:`templatetags-paginate` template tag takes care of customizing the
given queryset and the current template context. In the context of a
Twitter-style pagination the :ref:`templatetags-paginate` tag is often replaced
by the :ref:`templatetags-lazy-paginate` one, which offers, more or less, the
same functionalities and allows for reducing database access: see
:doc:`lazy_pagination`.
The :ref:`templatetags-show-more` one displays the link to navigate to the next
page.
You might want to glance at the :doc:`javascript` for a detailed explanation of
how to integrate JavaScript and Ajax features in Django Endless Pagination.
Pagination on scroll
~~~~~~~~~~~~~~~~~~~~
If you want new items to load when the user scroll down the browser page,
you can use the :ref:`pagination on scroll<javascript-pagination-on-scroll>`
feature: just set the *paginateOnScroll* option of *$.endlessPaginate()* to
*true*, e.g.:
.. code-block:: html+django
<h2>Entries:</h2>
{% include page_template %}
{% block js %}
{{ block.super }}
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="{{ STATIC_URL }}el-pagination/js/el-pagination.js"></script>
<script>$.endlessPaginate({paginateOnScroll: true});</script>
{% endblock %}
That's all. See the :doc:`templatetags_reference` to improve the use of
included templatetags.
It is possible to set the bottom margin used for
:ref:`pagination on scroll<javascript-pagination-on-scroll>` (default is 1
pixel). For example, if you want the pagination on scroll to be activated when
20 pixels remain to the end of the page:
.. code-block:: html+django
<h2>Entries:</h2>
{% include page_template %}
{% block js %}
{{ block.super }}
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="{{ STATIC_URL }}el-pagination/js/el-pagination.js"></script>
<script>
$.endlessPaginate({
paginateOnScroll: true,
paginateOnScrollMargin: 20
});
</script>
{% endblock %}
Again, see the :doc:`javascript`.
On scroll pagination using chunks
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Sometimes, when using on scroll pagination, you may want to still display
the *show more* link after each *N* pages. In Django Endless Pagination this is
called *chunk size*. For instance, a chunk size of 5 means that a *show more*
link is displayed after page 5 is loaded, then after page 10, then after page
15 and so on. Activating :ref:`chunks<javascript-chunks>` is straightforward,
just use the *paginateOnScrollChunkSize* option:
.. code-block:: html+django
{% block js %}
{{ block.super }}
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="{{ STATIC_URL }}el-pagination/js/el-pagination.js"></script>
<script>
$.endlessPaginate({
paginateOnScroll: true,
paginateOnScrollChunkSize: 5
});
</script>
{% endblock %}
Specifying where the content will be inserted
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you are paginating a table, you can use :ref:`templatetags-show-more-table` or you may want to include the *show_more* link
after the table itself, but the loaded content should be placed inside the
table.
For any case like this, you may specify the *contentSelector* option that
points to the element that will wrap the cumulative data:
.. code-block:: html+django
{% block js %}
{{ block.super }}
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="{{ STATIC_URL }}el-pagination/js/el-pagination.js"></script>
<script>
$.endlessPaginate({
contentSelector: '.endless_content_wrapper'
});
</script>
{% endblock %}
.. note::
By default, the contentSelector is null, making each new page be inserted
before the *show_more* link container.
When using this approach, you should take 2 more actions.
At first, the page template must be splitted a little different. You must do
the pagination in the main template and only apply pagination in the page
template if under ajax:
*myapp/entry_list.html* becomes:
.. code-block:: html+django
<h2>Entries:</h2>
{% paginate entry_list %}
<ul>
{% include page_template %}
</ul>
{% show_more %}
{% block js %}
{{ block.super }}
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="{{ STATIC_URL }}el-pagination/js/el-pagination.js"></script>
<script>$.endlessPaginate();</script>
{% endblock %}
*myapp/entry_list_page.html* becomes:
.. code-block:: html+django
{% load el_pagination_tags %}
{% if request.is_ajax %}{% paginate entry_list %}{% endif %}
{% for entry in entry_list %}
{# your code to show the entry #}
{% endfor %}
This is needed because the *show_more* button now is taken off the
page_template and depends of the *paginate* template tag. To avoid apply
pagination twice, we avoid run it a first time in the page_template.
You may also set the *EL_PAGINATION_PAGE_OUT_OF_RANGE_404* to True, so a blank
page wouldn't render the first page (the default behavior). When a blank page
is loaded and propagates the 404 error, the *show_more* link is removed.
Before version 2.0
~~~~~~~~~~~~~~~~~~
Django Endless Pagination v2.0 introduces a redesigned Ajax support for
pagination. As seen above, Ajax can now be enabled using a brand new jQuery
plugin that can be found in
``static/el-pagination/js/el-pagination.js``.
Old code was removed:
.. code-block:: html+django
<script src="http://code.jquery.com/jquery-latest.js"></script>
{# new jQuery plugin #}
<script src="{{ STATIC_URL }}el-pagination/js/el-pagination.js"></script>
{# Removed. #}
<script src="{{ STATIC_URL }}el-pagination/js/el-pagination-endless.js"></script>
<script src="{{ STATIC_URL }}el-pagination/js/el-pagination_on_scroll.js"></script>
However, please consider :ref:`migrating<javascript-migrate>` as soon as
possible: the old JavaScript files are removed.
Please refer to the :doc:`javascript` for a detailed overview of the new
features and for instructions on :ref:`how to migrate<javascript-migrate>` from
the old JavaScript files to the new one.
================================================
FILE: el_pagination/__init__.py
================================================
"""Django pagination tools supporting Ajax, multiple and lazy pagination,
Twitter-style and Digg-style pagination.
"""
VERSION = (4, 1, 2)
__version__ = '.'.join(map(str, VERSION))
def get_version():
"""Return the Django EL Pagination version as a string."""
return __version__
================================================
FILE: el_pagination/decorators.py
================================================
"""View decorators for Ajax powered pagination."""
from functools import wraps
from el_pagination.settings import PAGE_LABEL, TEMPLATE_VARNAME
QS_KEY = "querystring_key"
def page_template(template, key=PAGE_LABEL):
"""Return a view dynamically switching template if the request is Ajax.
Decorate a view that takes a *template* and *extra_context* keyword
arguments (like generic views).
The template is switched to *page_template* if request is ajax and
if *querystring_key* variable passed by the request equals to *key*.
This allows multiple Ajax paginations in the same page.
The name of the page template is given as *page_template* in the
extra context.
"""
def decorator(view):
@wraps(view)
def decorated(request, *args, **kwargs):
# Trust the developer: he wrote ``context.update(extra_context)``
# in his view.
extra_context = kwargs.setdefault("extra_context", {})
extra_context["page_template"] = template
# Switch the template when the request is Ajax.
querystring_key = request.GET.get(
QS_KEY, request.POST.get(QS_KEY, PAGE_LABEL)
)
if (
request.headers.get("x-requested-with") == "XMLHttpRequest"
and querystring_key == key
):
kwargs[TEMPLATE_VARNAME] = template
return view(request, *args, **kwargs)
return decorated
return decorator
def _get_template(querystring_key, mapping):
"""Return the template corresponding to the given ``querystring_key``."""
default = None
try:
template_and_keys = mapping.items()
except AttributeError:
template_and_keys = mapping
for template, key in template_and_keys:
if key is None:
key = PAGE_LABEL
default = template
if key == querystring_key:
return template
return default
def page_templates(mapping):
"""Like the *page_template* decorator but manage multiple paginations.
You can map multiple templates to *querystring_keys* using the *mapping*
dict, e.g.::
@page_templates({
'page_contents1.html': None,
'page_contents2.html': 'go_to_page',
})
def myview(request):
...
When the value of the dict is None then the default *querystring_key*
(defined in settings) is used. You can use this decorator instead of
chaining multiple *page_template* calls.
"""
def decorator(view):
@wraps(view)
def decorated(request, *args, **kwargs):
# Trust the developer: he wrote ``context.update(extra_context)``
# in his view.
extra_context = kwargs.setdefault("extra_context", {})
querystring_key = request.GET.get(
QS_KEY, request.POST.get(QS_KEY, PAGE_LABEL)
)
template = _get_template(querystring_key, mapping)
extra_context["page_template"] = template
# Switch the template when the request is Ajax.
if request.headers.get("x-requested-with") == "XMLHttpRequest" and template:
kwargs[TEMPLATE_VARNAME] = template
return view(request, *args, **kwargs)
return decorated
return decorator
================================================
FILE: el_pagination/exceptions.py
================================================
"""Pagination exceptions."""
class PaginationError(Exception):
"""Error in the pagination process."""
================================================
FILE: el_pagination/loaders.py
================================================
"""Django EL Pagination object loaders."""
from importlib import import_module
from django.core.exceptions import ImproperlyConfigured
def load_object(path):
"""Return the Python object represented by dotted *path*."""
i = path.rfind('.')
module_name, object_name = path[:i], path[i + 1 :]
# Load module.
try:
module = import_module(module_name)
except ImportError as exc:
raise ImproperlyConfigured(f'Module {module_name} not found') from exc
except ValueError as exc:
raise ImproperlyConfigured(f'Invalid module {module_name}') from exc
# Load object.
try:
return getattr(module, object_name)
except AttributeError as exc:
msg = 'Module %r does not define an object named %r'
raise ImproperlyConfigured(msg % (module_name, object_name)) from exc
================================================
FILE: el_pagination/locale/de/LC_MESSAGES/django.po
================================================
# Django Endless Pagination Locale
# Copyright (C) 2009-2013 Francesco Banconi
# This file is distributed under the same license as the django-endless-pagination package.
# Francesco Banconi <francesco.banconi@gmail.com>, 2009.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2010-03-04 17:52+0100\n"
"PO-Revision-Date: 2010-03-04 18:00+0100\n"
"Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
#: templates/endless/show_more.html:4
msgid "more"
msgstr "mehr"
================================================
FILE: el_pagination/locale/es/LC_MESSAGES/django.po
================================================
# Django Endless Pagination Locale
# Copyright (C) 2009-2013 Francesco Banconi
# This file is distributed under the same license as the django-endless-pagination package.
# Francesco Banconi <francesco.banconi@gmail.com>, 2013.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-02-11 18:03+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Francesco Banconi <francesco.banconi@gmail.com>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
#: templates/endless/show_more.html:5
msgid "more"
msgstr "Más resultados"
================================================
FILE: el_pagination/locale/fr/LC_MESSAGES/django.po
================================================
# Django Endless Pagination Locale
# Copyright (C) 2009-2013 Francesco Banconi
# This file is distributed under the same license as the django-endless-pagination package.
# Francesco Banconi <francesco.banconi@gmail.com>, 2013.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-02-11 16:06+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1)\n"
#: templates/endless/show_more.html:5
msgid "more"
msgstr "En voir plus"
================================================
FILE: el_pagination/locale/it/LC_MESSAGES/django.po
================================================
# Django Endless Pagination Locale
# Copyright (C) 2009-2013 Francesco Banconi
# This file is distributed under the same license as the django-endless-pagination package.
# Francesco Banconi <francesco.banconi@gmail.com>, 2009.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2009-08-22 19:21+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Francesco Banconi <francesco.banconi@gmail.com>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: templates/endless/show_more.html:4
msgid "more"
msgstr "mostra altri"
================================================
FILE: el_pagination/locale/pt_BR/LC_MESSAGES/django.po
================================================
# Django Endless Pagination Locale
# Copyright (C) 2009-2013 Francesco Banconi
# This file is distributed under the same license as the django-endless-pagination package.
# Francesco Banconi <francesco.banconi@gmail.com>, 2009.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-05-08 20:34-0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Michel Sabchuk <michel@sabchuk.com.br>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: templates/el_pagination/show_more.html:5
msgid "more"
msgstr "mais"
#: views.py:102
msgid "Empty list and ``%(class_name)s.allow_empty`` is False."
msgstr "Lista vazia e ``%(class_name)s.allow_empty`` é Falso."
================================================
FILE: el_pagination/locale/zh_CN/LC_MESSAGES/django.po
================================================
# Django Endless Pagination Locale
# Copyright (C) 2009-2013 Francesco Banconi
# This file is distributed under the same license as the django-endless-pagination package.
# Francesco Banconi <francesco.banconi@gmail.com>, 2013.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2009-08-22 19:21+0200\n"
"PO-Revision-Date: 2013-02-11 16:54+0800\n"
"Last-Translator: mozillazg <mozillazg101@gmail.com>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: templates/endless/show_more.html:4
msgid "more"
msgstr "查看更多"
================================================
FILE: el_pagination/models.py
================================================
"""Ephemeral models used to represent a page and a list of pages."""
from django.template import loader
from django.utils.encoding import force_str, iri_to_uri
from el_pagination import loaders, settings, utils
# Page templates cache.
_template_cache = {}
class ELPage:
"""A page link representation.
Interesting attributes:
- *self.number*: the page number;
- *self.label*: the label of the link
(usually the page number as string);
- *self.url*: the url of the page (strting with "?");
- *self.path*: the path of the page;
- *self.is_current*: return True if page is the current page displayed;
- *self.is_first*: return True if page is the first page;
- *self.is_last*: return True if page is the last page.
"""
def __init__(
self,
request,
number,
current_number,
total_number,
querystring_key,
label=None,
default_number=1,
override_path=None,
context=None,
):
self._request = request
self.number = number
self.label = force_str(number) if label is None else label
self.querystring_key = querystring_key
self.context = context or {}
self.context['request'] = request
self.is_current = number == current_number
self.is_first = number == 1
self.is_last = number == total_number
if settings.USE_NEXT_PREVIOUS_LINKS:
self.is_previous = label and number == current_number - 1
self.is_next = label and number == current_number + 1
self.url = utils.get_querystring_for_page(
request, number, self.querystring_key, default_number=default_number
)
path = iri_to_uri(override_path or request.path)
self.path = f"{path}{self.url}"
def render_link(self):
"""Render the page as a link."""
extra_context = {
'add_nofollow': settings.ADD_NOFOLLOW,
'page': self,
'querystring_key': self.querystring_key,
}
if self.is_current:
template_name = 'el_pagination/current_link.html'
else:
template_name = 'el_pagination/page_link.html'
if settings.USE_NEXT_PREVIOUS_LINKS:
if self.is_previous:
template_name = 'el_pagination/previous_link.html'
if self.is_next:
template_name = 'el_pagination/next_link.html'
if template_name not in _template_cache:
_template_cache[template_name] = loader.get_template(template_name)
template = _template_cache[template_name]
with self.context.push(**extra_context):
return template.render(self.context.flatten())
class PageList:
"""A sequence of endless pages."""
def __init__(
self,
request,
page,
querystring_key,
context,
default_number=None,
override_path=None,
):
self._request = request
self._page = page
self.context = context
self.context['request'] = request
if default_number is None:
self._default_number = 1
else:
self._default_number = int(default_number)
self._querystring_key = querystring_key
self._override_path = override_path
self._pages_list = []
def _endless_page(self, number, label=None):
"""Factory function that returns a *ELPage* instance.
This method works just like a partial constructor.
"""
return ELPage(
self._request,
number,
self._page.number,
len(self),
self._querystring_key,
label=label,
default_number=self._default_number,
override_path=self._override_path,
context=self.context,
)
def __getitem__(self, value):
# The type conversion is required here because in templates Django
# performs a dictionary lookup before the attribute lookups
# (when a dot is encountered).
try:
value = int(value)
except (TypeError, ValueError) as exc:
# A TypeError says to django to continue with an attribute lookup.
raise TypeError from exc
if 1 <= value <= len(self):
return self._endless_page(value)
raise IndexError('page list index out of range')
def __len__(self):
"""The length of the sequence is the total number of pages."""
return self._page.paginator.num_pages
def __iter__(self):
"""Iterate over all the endless pages (from first to last)."""
for i in range(len(self)):
yield self[i + 1]
def __str__(self):
"""Return a rendered Digg-style pagination (by default).
The callable *settings.PAGE_LIST_CALLABLE* can be used to customize
how the pages are displayed. The callable takes the current page number
and the total number of pages, and must return a sequence of page
numbers that will be displayed. The sequence can contain other values:
- *'previous'*: will display the previous page in that position;
- *'next'*: will display the next page in that position;
- *'first'*: will display the first page as an arrow;
- *'last'*: will display the last page as an arrow;
- *None*: a separator will be displayed in that position.
Here is an example of custom calable that displays the previous page,
then the first page, then a separator, then the current page, and
finally the last page::
def get_page_numbers(current_page, num_pages):
return ('previous', 1, None, current_page, 'last')
If *settings.PAGE_LIST_CALLABLE* is None an internal callable is used,
generating a Digg-style pagination. The value of
*settings.PAGE_LIST_CALLABLE* can also be a dotted path to a callable.
"""
return ''
def get_pages_list(self):
if not self._pages_list:
callable_or_path = settings.PAGE_LIST_CALLABLE
if callable_or_path:
if callable(callable_or_path):
pages_callable = callable_or_path
else:
pages_callable = loaders.load_object(callable_or_path)
else:
pages_callable = utils.get_page_numbers
pages = []
for item in pages_callable(self._page.number, len(self)):
if item is None:
pages.append(None)
elif item == 'previous':
pages.append(self.previous())
elif item == 'next':
pages.append(self.next())
elif item == 'first':
pages.append(self.first_as_arrow())
elif item == 'last':
pages.append(self.last_as_arrow())
else:
pages.append(self[item])
self._pages_list = pages
return self._pages_list
def get_rendered(self):
if len(self) > 1:
template = loader.get_template('el_pagination/show_pages.html')
with self.context.push(pages=self.get_pages_list()):
return template.render(self.context.flatten())
return ''
def current(self):
"""Return the current page."""
return self._endless_page(self._page.number)
def current_start_index(self):
"""Return the 1-based index of the first item on the current page."""
return self._page.start_index()
def current_end_index(self):
"""Return the 1-based index of the last item on the current page."""
return self._page.end_index()
def total_count(self):
"""Return the total number of objects, across all pages."""
return self._page.paginator.count
def first(self, label=None):
"""Return the first page."""
return self._endless_page(1, label=label)
def last(self, label=None):
"""Return the last page."""
return self._endless_page(len(self), label=label)
def first_as_arrow(self):
"""Return the first page as an arrow.
The page label (arrow) is defined in ``settings.FIRST_LABEL``.
"""
return self.first(label=settings.FIRST_LABEL)
def last_as_arrow(self):
"""Return the last page as an arrow.
The page label (arrow) is defined in ``settings.LAST_LABEL``.
"""
return self.last(label=settings.LAST_LABEL)
def previous(self):
"""Return the previous page.
The page label is defined in ``settings.PREVIOUS_LABEL``.
Return an empty string if current page is the first.
"""
if self._page.has_previous():
return self._endless_page(
self._page.previous_page_number(), label=settings.PREVIOUS_LABEL
)
return ''
def next(self):
"""Return the next page.
The page label is defined in ``settings.NEXT_LABEL``.
Return an empty string if current page is the last.
"""
if self._page.has_next():
return self._endless_page(
self._page.next_page_number(), label=settings.NEXT_LABEL
)
return ''
def paginated(self):
"""Return True if this page list contains more than one page."""
return len(self) > 1
def per_page_number(self):
"""Return the numbers of objects are normally display in per page."""
return self._page.paginator.per_page
================================================
FILE: el_pagination/paginators.py
================================================
"""Customized Django paginators."""
from math import ceil
from django.core.paginator import EmptyPage, Page, PageNotAnInteger, Paginator
class CustomPage(Page):
"""Handle different number of items on the first page."""
def start_index(self):
"""Return the 1-based index of the first item on this page."""
paginator = self.paginator
# Special case, return zero if no items.
if paginator.count == 0:
return 0
if self.number == 1:
return 1
return (self.number - 2) * paginator.per_page + paginator.first_page + 1
def end_index(self):
"""Return the 1-based index of the last item on this page."""
paginator = self.paginator
# Special case for the last page because there can be orphans.
if self.number == paginator.num_pages:
return paginator.count
return (self.number - 1) * paginator.per_page + paginator.first_page
class BasePaginator(Paginator):
"""A base paginator class subclassed by the other real paginators.
Handle different number of items on the first page.
"""
def __init__(self, object_list, per_page, **kwargs):
self._num_pages = None
if 'first_page' in kwargs:
self.first_page = kwargs.pop('first_page')
else:
self.first_page = per_page
super().__init__(object_list, per_page, **kwargs)
def get_current_per_page(self, number):
return self.first_page if number == 1 else self.per_page
class DefaultPaginator(BasePaginator):
"""The default paginator used by this application."""
def page(self, number):
number = self.validate_number(number)
if number == 1:
bottom = 0
else:
bottom = (number - 2) * self.per_page + self.first_page
top = bottom + self.get_current_per_page(number)
if top + self.orphans >= self.count:
top = self.count
return CustomPage(self.object_list[bottom:top], number, self)
def _get_num_pages(self):
if self._num_pages is None:
if self.count == 0 and not self.allow_empty_first_page:
self._num_pages = 0
else:
hits = max(0, self.count - self.orphans - self.first_page)
try:
self._num_pages = int(ceil(hits / float(self.per_page))) + 1
except ZeroDivisionError:
self._num_pages = 0 # fallback to a safe value
return self._num_pages
num_pages = property(_get_num_pages)
class LazyPaginatorCustomPage(Page):
"""Handle different number of items on the first page."""
def start_index(self):
"""Return the 1-based index of the first item on this page."""
paginator = self.paginator
if self.number == 1:
return 1
return (self.number - 2) * paginator.per_page + paginator.first_page + 1
def end_index(self):
"""Return the 1-based index of the last item on this page."""
paginator = self.paginator
return (self.number - 1) * paginator.per_page + paginator.first_page
class LazyPaginator(BasePaginator):
"""Implement lazy pagination."""
def validate_number(self, number):
try:
number = int(number)
except ValueError as exc:
raise PageNotAnInteger('That page number is not an integer') from exc
if number < 1:
raise EmptyPage('That page number is less than 1')
return number
def page(self, number):
number = self.validate_number(number)
current_per_page = self.get_current_per_page(number)
if number == 1:
bottom = 0
else:
bottom = (number - 2) * self.per_page + self.first_page
top = bottom + current_per_page
# Retrieve more objects to check if there is a next page.
objects = list(self.object_list[bottom : top + self.orphans + 1])
objects_count = len(objects)
if objects_count > (current_per_page + self.orphans):
# If another page is found, increase the total number of pages.
self._num_pages = number + 1
# In any case, return only objects for this page.
objects = objects[:current_per_page]
elif (number != 1) and (objects_count <= self.orphans):
raise EmptyPage('That page contains no results')
else:
# This is the last page.
self._num_pages = number
return LazyPaginatorCustomPage(objects, number, self)
def _get_count(self):
raise NotImplementedError
count = property(_get_count)
def _get_num_pages(self):
return self._num_pages
num_pages = property(_get_num_pages)
def _get_page_range(self):
raise NotImplementedError
page_range = property(_get_page_range)
================================================
FILE: el_pagination/settings.py
================================================
# """Django Endless Pagination settings file."""
from django.conf import settings
# How many objects are normally displayed in a page
# (overwriteable by templatetag).
PER_PAGE = getattr(settings, 'EL_PAGINATION_PER_PAGE', 10)
# The querystring key of the page number.
PAGE_LABEL = getattr(settings, 'EL_PAGINATION_PAGE_LABEL', 'page')
# See django *Paginator* definition of orphans.
ORPHANS = getattr(settings, 'EL_PAGINATION_ORPHANS', 0)
# If you use the default *show_more* template, here you can customize
# the content of the loader hidden element.
# Html is safe here, e.g. you can show your pretty animated gif:
# EL_PAGINATION_LOADING = """
# <img src="/static/img/loader.gif" alt="loading" />
# """
LOADING = getattr(settings, 'EL_PAGINATION_LOADING', 'loading')
USE_NEXT_PREVIOUS_LINKS = getattr(
settings, 'EL_PAGINATION_USE_NEXT_PREVIOUS_LINKS', False
)
# Labels for previous and next page links.
PREVIOUS_LABEL = getattr(settings, 'EL_PAGINATION_PREVIOUS_LABEL', '<')
NEXT_LABEL = getattr(settings, 'EL_PAGINATION_NEXT_LABEL', '>')
# Labels for first and last page links.
FIRST_LABEL = getattr(settings, 'EL_PAGINATION_FIRST_LABEL', '<<')
LAST_LABEL = getattr(settings, 'EL_PAGINATION_LAST_LABEL', '>>')
# Set to True if your SEO alchemist wants all the links in Digg-style
# pagination to be ``nofollow``.
ADD_NOFOLLOW = getattr(settings, 'EL_PAGINATION_ADD_NOFOLLOW', False)
# Callable (or dotted path to a callable) returning pages to be displayed.
# If None, a default callable is used (which produces Digg-style pagination).
PAGE_LIST_CALLABLE = getattr(settings, 'EL_PAGINATION_PAGE_LIST_CALLABLE', None)
# The default callable returns a sequence of pages producing Digg-style
# pagination, and depending on the settings below.
DEFAULT_CALLABLE_EXTREMES = getattr(
settings, 'EL_PAGINATION_DEFAULT_CALLABLE_EXTREMES', 3
)
DEFAULT_CALLABLE_AROUNDS = getattr(
settings, 'EL_PAGINATION_DEFAULT_CALLABLE_AROUNDS', 2
)
# Whether or not the first and last pages arrows are displayed.
DEFAULT_CALLABLE_ARROWS = getattr(
settings, 'EL_PAGINATION_DEFAULT_CALLABLE_ARROWS', False
)
# Template variable name for *page_template* decorator.
TEMPLATE_VARNAME = getattr(settings, 'EL_PAGINATION_TEMPLATE_VARNAME', 'template')
# If page out of range, throw a 404 exception
PAGE_OUT_OF_RANGE_404 = getattr(settings, 'EL_PAGINATION_PAGE_OUT_OF_RANGE_404', False)
================================================
FILE: el_pagination/static/el-pagination/js/el-pagination.js
================================================
'use strict';
(function ($) {
$.fn.endlessPaginate = function(options) {
var defaults = {
// Twitter-style pagination container selector.
containerSelector: '.endless_container',
// Twitter-style pagination loading selector.
loadingSelector: '.endless_loading',
// Twitter-style pagination link selector.
moreSelector: 'a.endless_more',
// Twitter-style pagination content wrapper selector.
contentSelector: null,
// Digg-style pagination page template selector.
pageSelector: '.endless_page_template',
// Digg-style pagination link selector.
pagesSelector: 'a.endless_page_link',
// Callback called when the user clicks to get another page.
onClick: function() {},
// Callback called when the new page is correctly displayed.
onCompleted: function() {},
// Set this to true to use the paginate-on-scroll feature.
paginateOnScroll: false,
// If paginate-on-scroll is on, this margin will be used.
paginateOnScrollMargin : 1,
// If paginate-on-scroll is on, it is possible to define chunks.
paginateOnScrollChunkSize: 0
},
settings = $.extend(defaults, options);
var getContext = function(link) {
return {
key: link.data("el-querystring-key").split(' ')[0],
url: link.attr('href')
};
};
return this.each(function() {
var element = $(this),
loadedPages = 1;
// Twitter-style pagination.
element.on('click', settings.moreSelector, function() {
var link = $(this),
html_link = link.get(0),
content_wrapper = element.find(settings.contentSelector),
container = link.closest(settings.containerSelector),
loading = container.find(settings.loadingSelector);
// Avoid multiple Ajax calls.
if (loading.is(':visible')) {
return false;
}
link.hide();
loading.show();
var context = getContext(link);
// Fire onClick callback.
if (settings.onClick.apply(html_link, [context]) !== false) {
var data = 'querystring_key=' + context.key;
// Send the Ajax request.
$.get(context.url, data, function (fragment) {
// Increase the number of loaded pages.
loadedPages += 1;
if (!content_wrapper.length) {
// Replace pagination container (the default behavior)
container.before(fragment);
container.remove();
} else {
// Insert the content in the specified wrapper and increment link
content_wrapper.append(fragment);
var nextPage = 'page=' + (loadedPages + 1);
link.attr('href', link.attr('href').replace(/page=\d+/, nextPage));
link.show();
loading.hide();
}
// Fire onCompleted callback.
settings.onCompleted.apply(
html_link, [context, $.trim(fragment)]);
}).fail(function (xhr, textStatus, error) {
// Remove the container left if any
container.remove();
});
}
return false;
});
// On scroll pagination.
if (settings.paginateOnScroll) {
var win = $(window),
doc = $(document);
doc.on('scroll', function () {
if (doc.height() - win.height() -
win.scrollTop() <= settings.paginateOnScrollMargin) {
// Do not paginate on scroll if chunks are used and
// the current chunk is complete.
var chunckSize = settings.paginateOnScrollChunkSize;
if (!chunckSize || loadedPages % chunckSize) {
element.find(settings.moreSelector).trigger('click');
} else {
element.find(settings.moreSelector).addClass('endless_chunk_complete');
}
}
});
}
// Digg-style pagination.
element.on('click', settings.pagesSelector, function() {
var link = $(this),
html_link = link.get(0),
context = getContext(link);
// Fire onClick callback.
if (settings.onClick.apply(html_link, [context]) !== false) {
var page_template = link.closest(settings.pageSelector),
data = 'querystring_key=' + context.key;
// Send the Ajax request.
page_template.load(context.url, data, function(fragment) {
// Fire onCompleted callback.
settings.onCompleted.apply(
html_link, [context, $.trim(fragment)]);
});
}
return false;
});
});
};
$.endlessPaginate = function(options) {
return $('body').endlessPaginate(options);
};
})(jQuery);
================================================
FILE: el_pagination/templates/el_pagination/current_link.html
================================================
<span class="endless_page_current">
<strong>{{ page.label|safe }}</strong>
</span>
================================================
FILE: el_pagination/templates/el_pagination/next_link.html
================================================
<a href="{{ page.path }}"
rel="next{% if add_nofollow %} nofollow{% endif %}"
data-el-querystring-key="{{ querystring_key }}"
class="endless_page_link">{{ page.label|safe }}</a>
================================================
FILE: el_pagination/templates/el_pagination/page_link.html
================================================
<a href="{{ page.path }}"
{% if add_nofollow %}rel="nofollow"{% endif %}
data-el-querystring-key="{{ querystring_key }}"
class="endless_page_link">{{ page.label|safe }}</a>
================================================
FILE: el_pagination/templates/el_pagination/previous_link.html
================================================
<a href="{{ page.path }}"
rel="prev{% if add_nofollow %} nofollow{% endif %}"
data-el-querystring-key="{{ querystring_key }}"
class="endless_page_link">{{ page.label|safe }}</a>
================================================
FILE: el_pagination/templates/el_pagination/show_more.html
================================================
{% load i18n %}
{% if querystring %}
<div class="endless_container">
<a class="endless_more{% if class_name %} {{ class_name }}{% endif %}" href="{{ path }}{{ querystring }}"
data-el-querystring-key="{{ querystring_key }}">{% if label %}{{ label|safe }}{% else %}{% trans "more" %}{% endif %}</a>
<div class="endless_loading" style="display: none;">{{ loading|safe }}</div>
</div>
{% endif %}
================================================
FILE: el_pagination/templates/el_pagination/show_more_table.html
================================================
{% load i18n %}
{% if querystring %}
<tr class="endless_container">
<td colspan="100%">
<a class="endless_more{% if class_name %} {{ class_name }}{% endif %}" href="{{ path }}{{ querystring }}"
data-el-querystring-key="{{ querystring_key }}">{% if label %}{{ label|safe }}{% else %}{% trans "more" %}{% endif %}</a>
<span class="endless_loading" style="display: none;">{{ loading|safe }}</span>
</td>
</tr>
{% endif %}
================================================
FILE: el_pagination/templates/el_pagination/show_pages.html
================================================
{% for page in pages %}
{{ page.render_link|default:'<span class="endless_separator">...</span>' }}
{% endfor %}
================================================
FILE: el_pagination/templatetags/__init__.py
================================================
================================================
FILE: el_pagination/templatetags/el_pagination_tags.py
================================================
"""Django EL(Endless) Pagination template tags."""
import re
from django import template
from django.http import Http404
from django.utils.encoding import force_str, iri_to_uri
from el_pagination import models, settings, utils
from el_pagination.paginators import DefaultPaginator, EmptyPage, LazyPaginator
register = template.Library()
__all__ = ['register']
PAGINATE_EXPRESSION = re.compile(
r"""
^ # Beginning of line.
(((?P<first_page>\w+)\,)?(?P<per_page>\w+(\.\w+)?)\s+)? # First page, per page.
(?P<objects>[\.\w]+) # Objects / queryset.
(\s+starting\s+from\s+page\s+(?P<number>[\-]?\d+|\w+))? # Page start.
(\s+using\s+(?P<key>[\"\'\-\w]+))? # Querystring key.
(\s+with\s+(?P<override_path>[\"\'\/\w]+))? # Override path.
(\s+as\s+(?P<var_name>\w+))? # Context variable name.
$ # End of line.
""",
re.VERBOSE,
)
SHOW_CURRENT_NUMBER_EXPRESSION = re.compile(
r"""
^ # Beginning of line.
(starting\s+from\s+page\s+(?P<number>\w+))?\s* # Page start.
(using\s+(?P<key>[\"\'\-\w]+))?\s* # Querystring key.
(as\s+(?P<var_name>\w+))? # Context variable name.
$ # End of line.
""",
re.VERBOSE,
)
@register.tag
def paginate(parser, token, paginator_class=None):
"""Paginate objects.
Usage:
.. code-block:: html+django
{% paginate entries %}
After this call, the *entries* variable in the template context is replaced
by only the entries of the current page.
You can also keep your *entries* original variable (usually a queryset)
and add to the context another name that refers to entries of the current
page, e.g.:
.. code-block:: html+django
{% paginate entries as page_entries %}
The *as* argument is also useful when a nested context variable is provided
as queryset. In this case, and only in this case, the resulting variable
name is mandatory, e.g.:
.. code-block:: html+django
{% paginate entries.all as entries %}
The number of paginated entries is taken from settings, but you can
override the default locally, e.g.:
.. code-block:: html+django
{% paginate 20 entries %}
Of course you can mix it all:
.. code-block:: html+django
{% paginate 20 entries as paginated_entries %}
By default, the first page is displayed the first time you load the page,
but you can change this, e.g.:
.. code-block:: html+django
{% paginate entries starting from page 3 %}
When changing the default page, it is also possible to reference the last
page (or the second last page, and so on) by using negative indexes, e.g:
.. code-block:: html+django
{% paginate entries starting from page -1 %}
This can be also achieved using a template variable that was passed to the
context, e.g.:
.. code-block:: html+django
{% paginate entries starting from page page_number %}
If the passed page number does not exist, the first page is displayed.
If you have multiple paginations in the same page, you can change the
querydict key for the single pagination, e.g.:
.. code-block:: html+django
{% paginate entries using article_page %}
In this case *article_page* is intended to be a context variable, but you
can hardcode the key using quotes, e.g.:
.. code-block:: html+django
{% paginate entries using 'articles_at_page' %}
Again, you can mix it all (the order of arguments is important):
.. code-block:: html+django
{% paginate 20 entries
starting from page 3 using page_key as paginated_entries %}
Additionally you can pass a path to be used for the pagination:
.. code-block:: html+django
{% paginate 20 entries
using page_key with pagination_url as paginated_entries %}
This way you can easily create views acting as API endpoints, and point
your Ajax calls to that API. In this case *pagination_url* is considered a
context variable, but it is also possible to hardcode the URL, e.g.:
.. code-block:: html+django
{% paginate 20 entries with "/mypage/" %}
If you want the first page to contain a different number of items than
subsequent pages, you can separate the two values with a comma, e.g. if
you want 3 items on the first page and 10 on other pages:
.. code-block:: html+django
{% paginate 3,10 entries %}
You must use this tag before calling the {% show_more %} one.
"""
# Validate arguments.
try:
tag_name, tag_args = token.contents.split(None, 1)
except ValueError as exc:
tag = token.contents.split()[0]
msg = f'{tag!r} tag requires arguments'
raise template.TemplateSyntaxError(msg) from exc
# Use a regexp to catch args.
match = PAGINATE_EXPRESSION.match(tag_args)
if match is None:
msg = f'Invalid arguments for {tag_name!r} tag'
raise template.TemplateSyntaxError(msg)
# Retrieve objects.
kwargs = match.groupdict()
objects = kwargs.pop('objects')
# The variable name must be present if a nested context variable is passed.
if '.' in objects and kwargs['var_name'] is None:
msg = (
'%(tag)r tag requires a variable name `as` argumnent if the '
'queryset is provided as a nested context variable (%(objects)s). '
'You must either pass a direct queryset (e.g. taking advantage '
'of the `with` template tag) or provide a new variable name to '
'store the resulting queryset (e.g. `%(tag)s %(objects)s as '
'objects`).'
) % {'tag': tag_name, 'objects': objects}
raise template.TemplateSyntaxError(msg)
# Call the node.
return PaginateNode(paginator_class, objects, **kwargs)
@register.tag
def lazy_paginate(parser, token):
"""Lazy paginate objects.
Paginate objects without hitting the database with a *select count* query.
Use this the same way as *paginate* tag when you are not interested
in the total number of pages.
"""
return paginate(parser, token, paginator_class=LazyPaginator)
class PaginateNode(template.Node):
"""Add to context the objects of the current page.
Also add the Django paginator's *page* object.
"""
def __init__(
self,
paginator_class,
objects,
first_page=None,
per_page=None,
var_name=None,
number=None,
key=None,
override_path=None,
):
self.paginator = paginator_class or DefaultPaginator
self.objects = template.Variable(objects)
# If *var_name* is not passed, then the queryset name will be used.
self.var_name = objects if var_name is None else var_name
# If *per_page* is not passed then the default value form settings
# will be used.
self.per_page_variable = None
if per_page is None:
self.per_page = settings.PER_PAGE
elif per_page.isdigit():
self.per_page = int(per_page)
else:
self.per_page_variable = template.Variable(per_page)
# Handle first page: if it is not passed then *per_page* is used.
self.first_page_variable = None
if first_page is None:
self.first_page = None
elif first_page.isdigit():
self.first_page = int(first_page)
else:
self.first_page_variable = template.Variable(first_page)
# Handle page number when it is not specified in querystring.
self.page_number_variable = None
if number is None:
self.page_number = 1
else:
try:
self.page_number = int(number)
except ValueError:
self.page_number_variable = template.Variable(number)
# Set the querystring key attribute.
self.querystring_key_variable = None
if key is None:
self.querystring_key = settings.PAGE_LABEL
elif key[0] in ('"', "'") and key[-1] == key[0]:
self.querystring_key = key[1:-1]
else:
self.querystring_key_variable = template.Variable(key)
# Handle *override_path*.
self.override_path_variable = None
if override_path is None:
self.override_path = None
elif (
override_path[0] in ('"', "'") and override_path[-1] == override_path[0]
): # noqa
self.override_path = override_path[1:-1]
else:
self.override_path_variable = template.Variable(override_path)
def render(self, context):
# Handle page number when it is not specified in querystring.
if self.page_number_variable is None:
default_number = self.page_number
else:
default_number = int(self.page_number_variable.resolve(context))
# Calculate the number of items to show on each page.
if self.per_page_variable is None:
per_page = self.per_page
else:
per_page = int(self.per_page_variable.resolve(context))
# Calculate the number of items to show in the first page.
if self.first_page_variable is None:
first_page = self.first_page or per_page
else:
first_page = int(self.first_page_variable.resolve(context))
# User can override the querystring key to use in the template.
# The default value is defined in the settings file.
if self.querystring_key_variable is None:
querystring_key = self.querystring_key
else:
querystring_key = self.querystring_key_variable.resolve(context)
# Retrieve the override path if used.
if self.override_path_variable is None:
override_path = self.override_path
else:
override_path = self.override_path_variable.resolve(context)
# Retrieve the queryset and create the paginator object.
objects = self.objects.resolve(context)
paginator = self.paginator(
objects, per_page, first_page=first_page, orphans=settings.ORPHANS
)
# Normalize the default page number if a negative one is provided.
if default_number < 0:
default_number = utils.normalize_page_number(
default_number, paginator.page_range
)
# The current request is used to get the requested page number.
page_number = utils.get_page_number_from_request(
context['request'], querystring_key, default=default_number
)
# Get the page.
try:
page = paginator.page(page_number)
except EmptyPage:
page = paginator.page(1)
if settings.PAGE_OUT_OF_RANGE_404:
raise Http404('Page out of range') # pylint: disable=raise-missing-from
# Populate the context with required data.
data = {
'default_number': default_number,
'override_path': override_path,
'page': page,
'querystring_key': querystring_key,
}
context.update({'endless': data, self.var_name: page.object_list})
return ''
@register.inclusion_tag('el_pagination/show_more.html', takes_context=True)
def show_more(context, label=None, loading=settings.LOADING, class_name=None):
"""Show the link to get the next page in a Twitter-like pagination.
Usage::
{% show_more %}
Alternatively you can override the label passed to the default template::
{% show_more "even more" %}
You can override the loading text too::
{% show_more "even more" "working" %}
You could pass in the extra CSS style class name as a third argument
{% show_more "even more" "working" "class_name" %}
Must be called after ``{% paginate objects %}``.
"""
# This template tag could raise a PaginationError: you have to call
# *paginate* or *lazy_paginate* before including the showmore template.
data = utils.get_data_from_context(context)
page = data['page']
# show the template only if there is a next page
if page.has_next():
request = context['request']
page_number = page.next_page_number()
# Generate the querystring.
querystring_key = data['querystring_key']
querystring = utils.get_querystring_for_page(
request, page_number, querystring_key, default_number=data['default_number']
)
return {
'label': label,
'loading': loading,
'class_name': class_name,
'path': iri_to_uri(data['override_path'] or request.path),
'querystring': querystring,
'querystring_key': querystring_key,
'request': request,
}
# No next page, nothing to see.
return {}
@register.inclusion_tag('el_pagination/show_more_table.html', takes_context=True)
def show_more_table(context, label=None, loading=settings.LOADING):
"""Show the link to get the next page in a Twitter-like pagination in a
template for table.
Usage::
{% show_more_table %}
Alternatively you can override the label passed to the default template::
{% show_more_table "even more" %}
You can override the loading text too::
{% show_more_table "even more" "working" %}
Must be called after ``{% paginate objects %}``.
"""
# This template tag could raise a PaginationError: you have to call
# *paginate* or *lazy_paginate* before including the showmore template.
return show_more(context, label, loading)
@register.tag
def get_pages(parser, token):
"""Add to context the list of page links.
Usage:
.. code-block:: html+django
{% get_pages %}
This is mostly used for Digg-style pagination.
This call inserts in the template context a *pages* variable, as a sequence
of page links. You can use *pages* in different ways:
- just print *pages.get_rendered* and you will get Digg-style pagination displayed:
.. code-block:: html+django
{{ pages.get_rendered }}
- display pages count:
.. code-block:: html+django
{{ pages|length }}
- check if the page list contains more than one page:
.. code-block:: html+django
{{ pages.paginated }}
{# the following is equivalent #}
{{ pages|length > 1 }}
- get a specific page:
.. code-block:: html+django
{# the current selected page #}
{{ pages.current }}
{# the first page #}
{{ pages.first }}
{# the last page #}
{{ pages.last }}
{# the previous page (or nothing if you are on first page) #}
{{ pages.previous }}
{# the next page (or nothing if you are in last page) #}
{{ pages.next }}
{# the third page #}
{{ pages.3 }}
{# this means page.1 is the same as page.first #}
{# the 1-based index of the first item on the current page #}
{{ pages.current_start_index }}
{# the 1-based index of the last item on the current page #}
{{ pages.current_end_index }}
{# the total number of objects, across all pages #}
{{ pages.total_count }}
{# the first page represented as an arrow #}
{{ pages.first_as_arrow }}
{# the last page represented as an arrow #}
{{ pages.last_as_arrow }}
- iterate over *pages* to get all pages:
.. code-block:: html+django
{% for page in pages %}
{# display page link #}
{{ page.render_link}}
{# the page url (beginning with "?") #}
{{ page.url }}
{# the page path #}
{{ page.path }}
{# the page number #}
{{ page.number }}
{# a string representing the page (commonly the page number) #}
{{ page.label }}
{# check if the page is the current one #}
{{ page.is_current }}
{# check if the page is the first one #}
{{ page.is_first }}
{# check if the page is the last one #}
{{ page.is_last }}
{% endfor %}
You can change the variable name, e.g.:
.. code-block:: html+django
{% get_pages as page_links %}
Must be called after ``{% paginate objects %}``.
"""
# Validate args.
try:
tag_name, args = token.contents.split(None, 1)
except ValueError:
var_name = 'pages'
else:
args = args.split()
if len(args) == 2 and args[0] == 'as':
var_name = args[1]
else:
msg = f'Invalid arguments for {tag_name!r} tag'
raise template.TemplateSyntaxError(msg)
# Call the node.
return GetPagesNode(var_name)
class GetPagesNode(template.Node):
"""Add the page list to context."""
def __init__(self, var_name):
self.var_name = var_name
def render(self, context):
# This template tag could raise a PaginationError: you have to call
# *paginate* or *lazy_paginate* before including the getpages template.
data = utils.get_data_from_context(context)
# Add the PageList instance to the context.
context[self.var_name] = models.PageList(
context['request'],
data['page'],
data['querystring_key'],
context=context,
default_number=data['default_number'],
override_path=data['override_path'],
)
return ''
@register.tag
def show_pages(parser, token):
"""Show page links.
Usage:
.. code-block:: html+django
{% show_pages %}
It is just a shortcut for:
.. code-block:: html+django
{% get_pages %}
{{ pages.get_rendered }}
You can set ``ENDLESS_PAGINATION_PAGE_LIST_CALLABLE`` in your *settings.py*
to a callable, or to a dotted path representing a callable, used to
customize the pages that are displayed.
See the *__unicode__* method of ``endless_pagination.models.PageList`` for
a detailed explanation of how the callable can be used.
Must be called after ``{% paginate objects %}``.
"""
# Validate args.
if len(token.contents.split()) != 1:
tag = token.contents.split()[0]
raise template.TemplateSyntaxError(f'{tag!r} tag takes no arguments')
# Call the node.
return ShowPagesNode()
class ShowPagesNode(template.Node):
"""Show the pagination."""
def render(self, context):
# This template tag could raise a PaginationError: you have to call
# *paginate* or *lazy_paginate* before including the getpages template.
data = utils.get_data_from_context(context)
# Return the string representation of the sequence of pages.
pages = models.PageList(
context['request'],
data['page'],
data['querystring_key'],
default_number=data['default_number'],
override_path=data['override_path'],
context=context,
)
return pages.get_rendered()
@register.tag
def show_current_number(parser, token):
"""Show the current page number, or insert it in the context.
This tag can for example be useful to change the page title according to
the current page number.
To just show current page number:
.. code-block:: html+django
{% show_current_number %}
If you use multiple paginations in the same page, you can get the page
number for a specific pagination using the querystring key, e.g.:
.. code-block:: html+django
{% show_current_number using mykey %}
The default page when no querystring is specified is 1. If you changed it
in the `paginate`_ template tag, you have to call ``show_current_number``
according to your choice, e.g.:
.. code-block:: html+django
{% show_current_number starting from page 3 %}
This can be also achieved using a template variable you passed to the
context, e.g.:
.. code-block:: html+django
{% show_current_number starting from page page_number %}
You can of course mix it all (the order of arguments is important):
.. code-block:: html+django
{% show_current_number starting from page 3 using mykey %}
If you want to insert the current page number in the context, without
actually displaying it in the template, use the *as* argument, i.e.:
.. code-block:: html+django
{% show_current_number as page_number %}
{% show_current_number
starting from page 3 using mykey as page_number %}
"""
# Validate args.
try:
tag_name, args = token.contents.split(None, 1)
except ValueError:
key = None
number = None
tag_name = token.contents[0]
var_name = None
else:
# Use a regexp to catch args.
match = SHOW_CURRENT_NUMBER_EXPRESSION.match(args)
if match is None:
msg = f'Invalid arguments for {tag_name!r} tag'
raise template.TemplateSyntaxError(msg)
# Retrieve objects.
groupdict = match.groupdict()
key = groupdict['key']
number = groupdict['number']
var_name = groupdict['var_name']
# Call the node.
return ShowCurrentNumberNode(number, key, var_name)
class ShowCurrentNumberNode(template.Node):
"""Show the page number taken from context."""
def __init__(self, number, key, var_name):
# Retrieve the page number.
self.page_number_variable = None
if number is None:
self.page_number = 1
elif number.isdigit():
self.page_number = int(number)
else:
self.page_number_variable = template.Variable(number)
# Get the queystring key.
self.querystring_key_variable = None
if key is None:
self.querystring_key = settings.PAGE_LABEL
elif key[0] in ('"', "'") and key[-1] == key[0]:
self.querystring_key = key[1:-1]
else:
self.querystring_key_variable = template.Variable(key)
# Get the template variable name.
self.var_name = var_name
def render(self, context):
# Get the page number to use if it is not specified in querystring.
if self.page_number_variable is None:
default_number = self.page_number
else:
default_number = int(self.page_number_variable.resolve(context))
# User can override the querystring key to use in the template.
# The default value is defined in the settings file.
if self.querystring_key_variable is None:
querystring_key = self.querystring_key
else:
querystring_key = self.querystring_key_variable.resolve(context)
# The request object is used to retrieve the current page number.
page_number = utils.get_page_number_from_request(
context['request'], querystring_key, default=default_number
)
if self.var_name is None:
return force_str(page_number)
context[self.var_name] = page_number
return ''
================================================
FILE: el_pagination/tests/__init__.py
================================================
"""Test model definitions."""
from django.core.management import call_command
call_command('makemigrations', verbosity=0)
call_command('migrate', verbosity=0)
================================================
FILE: el_pagination/tests/integration/__init__.py
================================================
"""Integration tests base objects definitions."""
import os
import unittest
from contextlib import contextmanager
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from django.http import QueryDict
from django.urls import reverse
from selenium.common import exceptions
# from selenium.webdriver import Firefox
from selenium.webdriver.firefox.options import Options
from selenium.webdriver.firefox.webdriver import WebDriver
from selenium.webdriver.support import ui
# Disable selenium as default. Difficult to setup for local tests. Must be enabled
# in CI environment.
USE_SELENIUM = os.getenv('USE_SELENIUM', 0) in (1, True, '1')
def setup_package():
"""Set up the Selenium driver once for all tests."""
# Just skipping *setup_package* and *teardown_package* generates an
# uncaught exception under Python 2.6.
if USE_SELENIUM:
# Create a Selenium browser instance.
options = Options()
options.add_argument('-headless')
selenium = SeleniumTestCase.selenium = WebDriver(options=options)
selenium.maximize_window()
SeleniumTestCase.wait = ui.WebDriverWait(selenium, 5)
SeleniumTestCase.selenium.implicitly_wait(3)
def teardown_package():
"""Quit the Selenium driver."""
if USE_SELENIUM:
SeleniumTestCase.selenium.quit()
@unittest.skipIf(
not USE_SELENIUM,
'excluding integration tests: environment variable USE_SELENIUM is not set.')
class SeleniumTestCase(StaticLiveServerTestCase):
"""Base test class for integration tests."""
PREVIOUS = '<'
NEXT = '>'
MORE = 'More results'
selector = 'div.{0} > h4'
def setUp(self):
self.url = self.live_server_url + reverse(self.view_name)
# Give the browser a little time; Firefox sometimes throws
# random errors if you hit it too soon
# time.sleep(1)
# @classmethod
# def setUpClass(cls):
# if not SHOW_BROWSER:
#
gitextract_8g56kzu8/ ├── .coveragerc ├── .github/ │ └── workflows/ │ └── tox.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .pylintrc ├── .readthedocs.yaml ├── .vscode/ │ ├── launch.json │ └── settings.json ├── AUTHORS ├── HACKING ├── INSTALL ├── LICENSE ├── MANIFEST.in ├── Makefile ├── PKG-INFO ├── README.rst ├── doc/ │ ├── Makefile │ ├── _static/ │ │ └── TRACKME │ ├── changelog.rst │ ├── conf.py │ ├── contacts.rst │ ├── contributing.rst │ ├── current_page_number.rst │ ├── customization.rst │ ├── different_first_page.rst │ ├── digg_pagination.rst │ ├── generic_views.rst │ ├── index.rst │ ├── javascript.rst │ ├── lazy_pagination.rst │ ├── multiple_pagination.rst │ ├── requirements.txt │ ├── start.rst │ ├── templatetags_reference.rst │ ├── thanks.rst │ └── twitter_pagination.rst ├── el_pagination/ │ ├── __init__.py │ ├── decorators.py │ ├── exceptions.py │ ├── loaders.py │ ├── locale/ │ │ ├── de/ │ │ │ └── LC_MESSAGES/ │ │ │ ├── django.mo │ │ │ └── django.po │ │ ├── es/ │ │ │ └── LC_MESSAGES/ │ │ │ ├── django.mo │ │ │ └── django.po │ │ ├── fr/ │ │ │ └── LC_MESSAGES/ │ │ │ ├── django.mo │ │ │ └── django.po │ │ ├── it/ │ │ │ └── LC_MESSAGES/ │ │ │ ├── django.mo │ │ │ └── django.po │ │ ├── pt_BR/ │ │ │ └── LC_MESSAGES/ │ │ │ ├── django.mo │ │ │ └── django.po │ │ └── zh_CN/ │ │ └── LC_MESSAGES/ │ │ ├── django.mo │ │ └── django.po │ ├── models.py │ ├── paginators.py │ ├── settings.py │ ├── static/ │ │ └── el-pagination/ │ │ └── js/ │ │ └── el-pagination.js │ ├── templates/ │ │ └── el_pagination/ │ │ ├── current_link.html │ │ ├── next_link.html │ │ ├── page_link.html │ │ ├── previous_link.html │ │ ├── show_more.html │ │ ├── show_more_table.html │ │ └── show_pages.html │ ├── templatetags/ │ │ ├── __init__.py │ │ └── el_pagination_tags.py │ ├── tests/ │ │ ├── __init__.py │ │ ├── integration/ │ │ │ ├── __init__.py │ │ │ ├── test_callbacks.py │ │ │ ├── test_chunks.py │ │ │ ├── test_digg.py │ │ │ ├── test_feed_wrapper.py │ │ │ ├── test_multiple.py │ │ │ ├── test_onscroll.py │ │ │ └── test_twitter.py │ │ ├── templatetags/ │ │ │ ├── __init__.py │ │ │ └── test_el_pagination_tags.py │ │ ├── test_decorators.py │ │ ├── test_loaders.py │ │ ├── test_models.py │ │ ├── test_paginators.py │ │ ├── test_utils.py │ │ └── test_views.py │ ├── utils.py │ └── views.py ├── pyproject.toml ├── release-requirements.txt ├── setup.cfg ├── setup.py ├── tests/ │ ├── __init__.py │ ├── develop.py │ ├── manage.py │ ├── project/ │ │ ├── __init__.py │ │ ├── context_processors.py │ │ ├── models.py │ │ ├── settings.py │ │ ├── static/ │ │ │ └── pagination.css │ │ ├── templates/ │ │ │ ├── 404.html │ │ │ ├── 500.html │ │ │ ├── base.html │ │ │ ├── callbacks/ │ │ │ │ ├── index.html │ │ │ │ └── page.html │ │ │ ├── chunks/ │ │ │ │ ├── index.html │ │ │ │ ├── items_page.html │ │ │ │ └── objects_page.html │ │ │ ├── complete/ │ │ │ │ ├── articles_page.html │ │ │ │ ├── entries_page.html │ │ │ │ ├── index.html │ │ │ │ ├── items_page.html │ │ │ │ ├── objects_page.html │ │ │ │ └── objects_simple_page.html │ │ │ ├── digg/ │ │ │ │ ├── index.html │ │ │ │ ├── page.html │ │ │ │ └── table/ │ │ │ │ ├── index.html │ │ │ │ └── page.html │ │ │ ├── feed_wrapper/ │ │ │ │ ├── index.html │ │ │ │ └── page.html │ │ │ ├── home.html │ │ │ ├── multiple/ │ │ │ │ ├── entries_page.html │ │ │ │ ├── index.html │ │ │ │ ├── items_page.html │ │ │ │ └── objects_page.html │ │ │ ├── onscroll/ │ │ │ │ ├── index.html │ │ │ │ ├── page.html │ │ │ │ └── table/ │ │ │ │ ├── index.html │ │ │ │ └── page.html │ │ │ └── twitter/ │ │ │ ├── index.html │ │ │ ├── page.html │ │ │ └── table/ │ │ │ ├── index.html │ │ │ └── page.html │ │ ├── urls.py │ │ └── views.py │ ├── requirements.pip │ └── with_venv.sh └── tox.ini
SYMBOL INDEX (390 symbols across 29 files)
FILE: el_pagination/__init__.py
function get_version (line 10) | def get_version():
FILE: el_pagination/decorators.py
function page_template (line 10) | def page_template(template, key=PAGE_LABEL):
function _get_template (line 45) | def _get_template(querystring_key, mapping):
function page_templates (line 61) | def page_templates(mapping):
FILE: el_pagination/exceptions.py
class PaginationError (line 4) | class PaginationError(Exception):
FILE: el_pagination/loaders.py
function load_object (line 8) | def load_object(path):
FILE: el_pagination/models.py
class ELPage (line 12) | class ELPage:
method __init__ (line 28) | def __init__(
method render_link (line 60) | def render_link(self):
class PageList (line 83) | class PageList:
method __init__ (line 86) | def __init__(
method _endless_page (line 107) | def _endless_page(self, number, label=None):
method __getitem__ (line 124) | def __getitem__(self, value):
method __len__ (line 137) | def __len__(self):
method __iter__ (line 141) | def __iter__(self):
method __str__ (line 146) | def __str__(self):
method get_pages_list (line 173) | def get_pages_list(self):
method get_rendered (line 200) | def get_rendered(self):
method current (line 207) | def current(self):
method current_start_index (line 211) | def current_start_index(self):
method current_end_index (line 215) | def current_end_index(self):
method total_count (line 219) | def total_count(self):
method first (line 223) | def first(self, label=None):
method last (line 227) | def last(self, label=None):
method first_as_arrow (line 231) | def first_as_arrow(self):
method last_as_arrow (line 238) | def last_as_arrow(self):
method previous (line 245) | def previous(self):
method next (line 257) | def next(self):
method paginated (line 269) | def paginated(self):
method per_page_number (line 273) | def per_page_number(self):
FILE: el_pagination/paginators.py
class CustomPage (line 8) | class CustomPage(Page):
method start_index (line 11) | def start_index(self):
method end_index (line 21) | def end_index(self):
class BasePaginator (line 30) | class BasePaginator(Paginator):
method __init__ (line 36) | def __init__(self, object_list, per_page, **kwargs):
method get_current_per_page (line 44) | def get_current_per_page(self, number):
class DefaultPaginator (line 48) | class DefaultPaginator(BasePaginator):
method page (line 51) | def page(self, number):
method _get_num_pages (line 62) | def _get_num_pages(self):
class LazyPaginatorCustomPage (line 77) | class LazyPaginatorCustomPage(Page):
method start_index (line 80) | def start_index(self):
method end_index (line 87) | def end_index(self):
class LazyPaginator (line 93) | class LazyPaginator(BasePaginator):
method validate_number (line 96) | def validate_number(self, number):
method page (line 105) | def page(self, number):
method _get_count (line 128) | def _get_count(self):
method _get_num_pages (line 133) | def _get_num_pages(self):
method _get_page_range (line 138) | def _get_page_range(self):
FILE: el_pagination/templatetags/el_pagination_tags.py
function paginate (line 43) | def paginate(parser, token, paginator_class=None):
function lazy_paginate (line 188) | def lazy_paginate(parser, token):
class PaginateNode (line 199) | class PaginateNode(template.Node):
method __init__ (line 205) | def __init__(
method render (line 272) | def render(self, context):
function show_more (line 341) | def show_more(context, label=None, loading=settings.LOADING, class_name=...
function show_more_table (line 389) | def show_more_table(context, label=None, loading=settings.LOADING):
function get_pages (line 406) | def get_pages(parser, token):
class GetPagesNode (line 531) | class GetPagesNode(template.Node):
method __init__ (line 534) | def __init__(self, var_name):
method render (line 537) | def render(self, context):
function show_pages (line 554) | def show_pages(parser, token):
class ShowPagesNode (line 587) | class ShowPagesNode(template.Node):
method render (line 590) | def render(self, context):
function show_current_number (line 607) | def show_current_number(parser, token):
class ShowCurrentNumberNode (line 680) | class ShowCurrentNumberNode(template.Node):
method __init__ (line 683) | def __init__(self, number, key, var_name):
method render (line 705) | def render(self, context):
FILE: el_pagination/tests/integration/__init__.py
function setup_package (line 24) | def setup_package():
function teardown_package (line 38) | def teardown_package():
class SeleniumTestCase (line 47) | class SeleniumTestCase(StaticLiveServerTestCase):
method setUp (line 55) | def setUp(self):
method get (line 101) | def get(self, url=None, data=None, **kwargs):
method wait_ajax (line 127) | def wait_ajax(self):
method click_link (line 142) | def click_link(self, text, index=0):
method scroll_down (line 148) | def scroll_down(self):
method get_current_elements (line 153) | def get_current_elements(self, class_name, driver=None):
method asserLinksEqual (line 163) | def asserLinksEqual(self, count, text):
method assertElements (line 171) | def assertElements(self, class_name, elements):
method assertNewElements (line 188) | def assertNewElements(self, class_name, new_elements):
method assertSameURL (line 200) | def assertSameURL(self):
FILE: el_pagination/tests/integration/test_callbacks.py
class CallbacksTest (line 8) | class CallbacksTest(SeleniumTestCase):
method notifications_loaded (line 12) | def notifications_loaded(self, driver):
method assertNotificationsEqual (line 15) | def assertNotificationsEqual(self, notifications):
method test_can_navigate_site (line 22) | def test_can_navigate_site(self):
method test_on_click (line 27) | def test_on_click(self):
method test_on_completed (line 38) | def test_on_completed(self):
FILE: el_pagination/tests/integration/test_chunks.py
class ChunksPaginationTest (line 8) | class ChunksPaginationTest(SeleniumTestCase):
method test_new_elements_loaded (line 12) | def test_new_elements_loaded(self):
method test_url_not_changed (line 19) | def test_url_not_changed(self):
method test_direct_link (line 25) | def test_direct_link(self):
method test_subsequent_page (line 34) | def test_subsequent_page(self):
method test_chunks (line 41) | def test_chunks(self):
FILE: el_pagination/tests/integration/test_digg.py
class DiggPaginationTest (line 8) | class DiggPaginationTest(SeleniumTestCase):
method test_new_elements_loaded (line 12) | def test_new_elements_loaded(self):
method test_url_not_changed (line 18) | def test_url_not_changed(self):
method test_direct_link (line 24) | def test_direct_link(self):
method test_next (line 30) | def test_next(self):
method test_previous (line 37) | def test_previous(self):
method test_no_previous_link_in_first_page (line 44) | def test_no_previous_link_in_first_page(self):
method test_no_next_link_in_last_page (line 49) | def test_no_next_link_in_last_page(self):
class DiggPaginationTableTest (line 55) | class DiggPaginationTableTest(DiggPaginationTest):
FILE: el_pagination/tests/integration/test_feed_wrapper.py
class FeedWrapperPaginationTest (line 9) | class FeedWrapperPaginationTest(SeleniumTestCase):
method test_new_elements_loaded (line 14) | def test_new_elements_loaded(self):
method test_url_not_changed (line 20) | def test_url_not_changed(self):
method test_direct_link (line 26) | def test_direct_link(self):
method test_subsequent_page (line 32) | def test_subsequent_page(self):
method test_feed_wrapper__test_multiple_show_more_through_all_pages (line 38) | def test_feed_wrapper__test_multiple_show_more_through_all_pages(self):
method test_no_more_link_in_last_page_opened_directly (line 61) | def test_no_more_link_in_last_page_opened_directly(self):
FILE: el_pagination/tests/integration/test_multiple.py
class MultiplePaginationTest (line 8) | class MultiplePaginationTest(SeleniumTestCase):
method test_new_elements_loaded (line 12) | def test_new_elements_loaded(self):
method test_url_not_changed (line 22) | def test_url_not_changed(self):
method test_direct_link (line 30) | def test_direct_link(self):
method test_subsequent_pages (line 40) | def test_subsequent_pages(self):
method test_no_more_link_in_last_page (line 50) | def test_no_more_link_in_last_page(self):
FILE: el_pagination/tests/integration/test_onscroll.py
class OnScrollPaginationTest (line 8) | class OnScrollPaginationTest(SeleniumTestCase):
method test_new_elements_loaded (line 12) | def test_new_elements_loaded(self):
method test_url_not_changed (line 18) | def test_url_not_changed(self):
method test_direct_link (line 24) | def test_direct_link(self):
method test_subsequent_page (line 30) | def test_subsequent_page(self):
method test_multiple_show_more (line 36) | def test_multiple_show_more(self):
method test_scrolling_last_page (line 45) | def test_scrolling_last_page(self):
class OnScrollPaginationTableTest (line 52) | class OnScrollPaginationTableTest(OnScrollPaginationTest):
FILE: el_pagination/tests/integration/test_twitter.py
class TwitterPaginationTest (line 8) | class TwitterPaginationTest(SeleniumTestCase):
method test_new_elements_loaded (line 12) | def test_new_elements_loaded(self):
method test_url_not_changed (line 18) | def test_url_not_changed(self):
method test_direct_link (line 24) | def test_direct_link(self):
method test_subsequent_page (line 30) | def test_subsequent_page(self):
method test_multiple_show_more (line 36) | def test_multiple_show_more(self):
method test_no_more_link_in_last_page (line 45) | def test_no_more_link_in_last_page(self):
class TwitterPaginationTableTest (line 51) | class TwitterPaginationTableTest(TwitterPaginationTest):
FILE: el_pagination/tests/templatetags/test_el_pagination_tags.py
class TemplateTagsTestMixin (line 25) | class TemplateTagsTestMixin(object):
method setUp (line 28) | def setUp(self):
method render (line 31) | def render(self, request, contents, **kwargs):
method request (line 48) | def request(self, url='/', page=None, data=None, **kwargs):
class EtreeTemplateTagsTestMixin (line 57) | class EtreeTemplateTagsTestMixin(TemplateTagsTestMixin):
method render (line 60) | def render(self, request, contents, **kwargs):
class PaginateTestMixin (line 71) | class PaginateTestMixin(TemplateTagsTestMixin):
method assertPaginationNumQueries (line 77) | def assertPaginationNumQueries(self, num_queries, template, queryset=N...
method assertRangeEqual (line 94) | def assertRangeEqual(self, expected, actual):
method render (line 98) | def render(self, request, contents, **kwargs):
method test_object_list (line 102) | def test_object_list(self):
method test_per_page_argument (line 109) | def test_per_page_argument(self):
method test_per_page_argument_as_variable (line 115) | def test_per_page_argument_as_variable(self):
method test_first_page_argument (line 123) | def test_first_page_argument(self):
method test_first_page_argument_as_variable (line 132) | def test_first_page_argument_as_variable(self):
method test_starting_from_page_argument (line 148) | def test_starting_from_page_argument(self):
method test_starting_from_page_argument_as_variable (line 154) | def test_starting_from_page_argument_as_variable(self):
method test_using_argument (line 162) | def test_using_argument(self):
method test_using_argument_as_variable (line 169) | def test_using_argument_as_variable(self):
method test_with_argument (line 177) | def test_with_argument(self):
method test_with_argument_as_variable (line 183) | def test_with_argument_as_variable(self):
method test_as_argument (line 192) | def test_as_argument(self):
method test_complete_argument_list (line 200) | def test_complete_argument_list(self):
method test_invalid_arguments (line 215) | def test_invalid_arguments(self):
method test_invalid_page (line 227) | def test_invalid_page(self):
method test_invalid_page_with_raise_404_enabled (line 234) | def test_invalid_page_with_raise_404_enabled(self):
method test_nested_context_variable (line 242) | def test_nested_context_variable(self):
method test_failing_nested_context_variable (line 249) | def test_failing_nested_context_variable(self):
method test_multiple_pagination (line 258) | def test_multiple_pagination(self):
class PaginateTest (line 276) | class PaginateTest(PaginateTestMixin, TestCase):
method test_starting_from_last_page_argument (line 280) | def test_starting_from_last_page_argument(self):
method test_starting_from_negative_page_argument (line 287) | def test_starting_from_negative_page_argument(self):
method test_starting_from_negative_page_argument_as_variable (line 294) | def test_starting_from_negative_page_argument_as_variable(self):
method test_starting_from_negative_page_out_of_range (line 304) | def test_starting_from_negative_page_out_of_range(self):
method test_num_queries (line 311) | def test_num_queries(self):
method test_num_queries_starting_from_another_page (line 318) | def test_num_queries_starting_from_another_page(self):
method test_num_queries_starting_from_last_page (line 324) | def test_num_queries_starting_from_last_page(self):
class LazyPaginateTest (line 331) | class LazyPaginateTest(PaginateTestMixin, TestCase):
method test_starting_from_negative_page_raises_error (line 335) | def test_starting_from_negative_page_raises_error(self):
method test_num_queries (line 342) | def test_num_queries(self):
method test_num_queries_starting_from_another_page (line 350) | def test_num_queries_starting_from_another_page(self):
class ShowMoreTest (line 358) | class ShowMoreTest(EtreeTemplateTagsTestMixin, TestCase):
method render (line 362) | def render(self, request, contents, **kwargs):
method test_first_page_next_url (line 366) | def test_first_page_next_url(self):
method test_page_next_url (line 375) | def test_page_next_url(self):
method test_last_page (line 383) | def test_last_page(self):
method test_customized_label (line 389) | def test_customized_label(self):
method test_customized_loading (line 396) | def test_customized_loading(self):
class ShowMoreTableTest (line 405) | class ShowMoreTableTest(ShowMoreTest):
class GetPagesTest (line 410) | class GetPagesTest(TemplateTagsTestMixin, TestCase):
method test_page_list (line 412) | def test_page_list(self):
method test_different_varname (line 420) | def test_different_varname(self):
method test_page_numbers (line 428) | def test_page_numbers(self):
method test_without_paginate_tag (line 437) | def test_without_paginate_tag(self):
method test_invalid_arguments (line 443) | def test_invalid_arguments(self):
method test_starting_from_negative_page_in_another_page (line 450) | def test_starting_from_negative_page_in_another_page(self):
method test_pages_length (line 462) | def test_pages_length(self):
class ShowPagesTest (line 470) | class ShowPagesTest(EtreeTemplateTagsTestMixin, TestCase):
method test_current_page (line 472) | def test_current_page(self):
method test_links (line 482) | def test_links(self):
method test_without_paginate_tag (line 491) | def test_without_paginate_tag(self):
method test_invalid_arguments (line 497) | def test_invalid_arguments(self):
class ShowCurrentNumberTest (line 505) | class ShowCurrentNumberTest(TemplateTagsTestMixin, TestCase):
method test_current_number (line 507) | def test_current_number(self):
method test_starting_from_page_argument (line 514) | def test_starting_from_page_argument(self):
method test_starting_from_page_argument_as_variable (line 520) | def test_starting_from_page_argument_as_variable(self):
method test_using_argument (line 528) | def test_using_argument(self):
method test_using_argument_as_variable (line 535) | def test_using_argument_as_variable(self):
method test_as_argument (line 543) | def test_as_argument(self):
method test_complete_argument_list (line 551) | def test_complete_argument_list(self):
method test_invalid_arguments (line 563) | def test_invalid_arguments(self):
FILE: el_pagination/tests/test_decorators.py
class DecoratorsTestMixin (line 11) | class DecoratorsTestMixin(object):
method setUp (line 18) | def setUp(self):
method get_decorator (line 27) | def get_decorator(self):
method assertTemplatesEqual (line 31) | def assertTemplatesEqual(self, expected_active, expected_page, templat...
method decorate (line 35) | def decorate(self, *args, **kwargs):
method test_decorated (line 48) | def test_decorated(self):
method test_request_with_querystring_key (line 54) | def test_request_with_querystring_key(self):
method test_ajax_request (line 61) | def test_ajax_request(self):
method test_ajax_request_with_querystring_key (line 67) | def test_ajax_request_with_querystring_key(self):
method test_unexistent_page (line 74) | def test_unexistent_page(self):
class PageTemplateTest (line 82) | class PageTemplateTest(DecoratorsTestMixin, TestCase):
method get_decorator (line 86) | def get_decorator(self):
method test_request_with_querystring_key_to_mypage (line 89) | def test_request_with_querystring_key_to_mypage(self):
method test_ajax_request_with_querystring_key_to_mypage (line 96) | def test_ajax_request_with_querystring_key_to_mypage(self):
method test_ajax_request_to_mypage (line 104) | def test_ajax_request_to_mypage(self):
class PageTemplatesTest (line 113) | class PageTemplatesTest(DecoratorsTestMixin, TestCase):
method get_decorator (line 117) | def get_decorator(self):
method test_request_with_querystring_key_to_mypage (line 120) | def test_request_with_querystring_key_to_mypage(self):
method test_ajax_request_with_querystring_key_to_mypage (line 127) | def test_ajax_request_with_querystring_key_to_mypage(self):
class PageTemplatesWithTupleTest (line 136) | class PageTemplatesWithTupleTest(PageTemplatesTest):
FILE: el_pagination/tests/test_loaders.py
class ImproperlyConfiguredTestMixin (line 15) | class ImproperlyConfiguredTestMixin(object):
method assertImproperlyConfigured (line 19) | def assertImproperlyConfigured(self, message):
class AssertImproperlyConfiguredTest (line 33) | class AssertImproperlyConfiguredTest(ImproperlyConfiguredTestMixin, Test...
method test_assertion (line 35) | def test_assertion(self):
method test_case_insensitive (line 41) | def test_case_insensitive(self):
method test_assertion_fails_different_message (line 46) | def test_assertion_fails_different_message(self):
method test_assertion_fails_no_exception (line 53) | def test_assertion_fails_no_exception(self):
method test_assertion_fails_different_exception (line 60) | def test_assertion_fails_different_exception(self):
class LoadObjectTest (line 67) | class LoadObjectTest(ImproperlyConfiguredTestMixin, TestCase):
method setUp (line 69) | def setUp(self):
method test_valid_path (line 72) | def test_valid_path(self):
method test_module_not_found (line 77) | def test_module_not_found(self):
method test_invalid_module (line 82) | def test_invalid_module(self):
method test_object_not_found (line 87) | def test_object_not_found(self):
FILE: el_pagination/tests/test_models.py
function local_settings (line 18) | def local_settings(**kwargs):
class LocalSettingsTest (line 35) | class LocalSettingsTest(TestCase):
method setUp (line 37) | def setUp(self):
method tearDown (line 40) | def tearDown(self):
method test_settings_changed (line 43) | def test_settings_changed(self):
method test_settings_restored (line 48) | def test_settings_restored(self):
method test_restored_after_exception (line 54) | def test_restored_after_exception(self):
function page_list_callable_arrows (line 62) | def page_list_callable_arrows(number, num_pages):
class PageListTest (line 73) | class PageListTest(TestCase):
method setUp (line 75) | def setUp(self):
method get_url_for_page (line 86) | def get_url_for_page(self, number):
method get_path_for_page (line 90) | def get_path_for_page(self, number):
method check_page (line 94) | def check_page(
method check_page_list_callable (line 105) | def check_page_list_callable(self, callable_or_path):
method test_length (line 112) | def test_length(self):
method test_paginated (line 116) | def test_paginated(self):
method test_first_page (line 125) | def test_first_page(self):
method test_last_page (line 132) | def test_last_page(self):
method test_first_page_as_arrow (line 137) | def test_first_page_as_arrow(self):
method test_last_page_as_arrow (line 146) | def test_last_page_as_arrow(self):
method test_current_page (line 154) | def test_current_page(self):
method test_path (line 159) | def test_path(self):
method test_url (line 165) | def test_url(self):
method test_current_indexes (line 171) | def test_current_indexes(self):
method test_total_count (line 177) | def test_total_count(self):
method test_page_render (line 181) | def test_page_render(self):
method test_current_page_render (line 188) | def test_current_page_render(self):
method test_page_list_render (line 195) | def test_page_list_render(self):
method test_page_list_render_using_arrows (line 202) | def test_page_list_render_using_arrows(self):
method test_page_list_render_just_one_page (line 213) | def test_page_list_render_just_one_page(self):
method test_different_default_number (line 220) | def test_different_default_number(self):
method test_index_error (line 228) | def test_index_error(self):
method test_previous (line 233) | def test_previous(self):
method test_previous_attrs (line 238) | def test_previous_attrs(self):
method test_next (line 246) | def test_next(self):
method test_next_attrs (line 251) | def test_next_attrs(self):
method test_no_previous (line 259) | def test_no_previous(self):
method test_no_next (line 266) | def test_no_next(self):
method test_customized_page_list_callable (line 274) | def test_customized_page_list_callable(self):
method test_customized_page_list_dotted_path (line 278) | def test_customized_page_list_dotted_path(self):
method test_whitespace_in_path (line 284) | def test_whitespace_in_path(self):
method test_lookup (line 293) | def test_lookup(self):
method test_invalid_lookup (line 298) | def test_invalid_lookup(self):
FILE: el_pagination/tests/test_paginators.py
class PaginatorTestMixin (line 10) | class PaginatorTestMixin(object):
method setUp (line 16) | def setUp(self):
method test_object_list (line 22) | def test_object_list(self):
method test_orphans (line 29) | def test_orphans(self):
method test_no_orphans (line 34) | def test_no_orphans(self):
method test_empty_page (line 40) | def test_empty_page(self):
method test_invalid_page (line 45) | def test_invalid_page(self):
class DifferentFirstPagePaginatorTestMixin (line 53) | class DifferentFirstPagePaginatorTestMixin(PaginatorTestMixin):
method setUp (line 61) | def setUp(self):
method test_no_orphans (line 67) | def test_no_orphans(self):
class DefaultPaginatorTest (line 74) | class DefaultPaginatorTest(PaginatorTestMixin, TestCase):
method test_indexes (line 78) | def test_indexes(self):
method test_items_count (line 84) | def test_items_count(self):
method test_num_pages (line 88) | def test_num_pages(self):
method test_page_range (line 92) | def test_page_range(self):
method test_no_items (line 96) | def test_no_items(self):
method test_single_page_indexes (line 103) | def test_single_page_indexes(self):
class LazyPaginatorTest (line 111) | class LazyPaginatorTest(PaginatorTestMixin, TestCase):
method test_items_count (line 115) | def test_items_count(self):
method test_num_pages (line 120) | def test_num_pages(self):
method test_page_range (line 125) | def test_page_range(self):
class DifferentFirstPageDefaultPaginatorTest (line 131) | class DifferentFirstPageDefaultPaginatorTest(
class DifferentFirstPageLazyPaginatorTest (line 137) | class DifferentFirstPageLazyPaginatorTest(
FILE: el_pagination/tests/test_utils.py
class GetDataFromContextTest (line 13) | class GetDataFromContextTest(TestCase):
method test_valid_context (line 15) | def test_valid_context(self):
method test_invalid_context (line 20) | def test_invalid_context(self):
class GetPageNumberFromRequestTest (line 26) | class GetPageNumberFromRequestTest(TestCase):
method setUp (line 28) | def setUp(self):
method test_no_querystring_key (line 31) | def test_no_querystring_key(self):
method test_default_querystring_key (line 37) | def test_default_querystring_key(self):
method test_default (line 43) | def test_default(self):
method test_custom_querystring_key (line 50) | def test_custom_querystring_key(self):
method test_post_data (line 57) | def test_post_data(self):
class GetPageNumbersTest (line 63) | class GetPageNumbersTest(TestCase):
method test_defaults (line 65) | def test_defaults(self):
method test_first_page (line 73) | def test_first_page(self):
method test_last_page (line 79) | def test_last_page(self):
method test_no_extremes (line 85) | def test_no_extremes(self):
method test_no_arounds (line 91) | def test_no_arounds(self):
method test_no_extremes_arounds (line 97) | def test_no_extremes_arounds(self):
method test_one_page (line 103) | def test_one_page(self):
method test_arrows (line 109) | def test_arrows(self):
method test_arrows_first_page (line 116) | def test_arrows_first_page(self):
method test_arrows_last_page (line 123) | def test_arrows_last_page(self):
class IterFactorsTest (line 131) | class IterFactorsTest(TestCase):
method _run_tests (line 133) | def _run_tests(self, test_data):
method test__iter_factors (line 139) | def test__iter_factors(self):
class MakeElasticRangeTest (line 150) | class MakeElasticRangeTest(TestCase):
method _run_tests (line 152) | def _run_tests(self, test_data):
method test___make_elastic_range_units (line 157) | def test___make_elastic_range_units(self):
method test___make_elastic_range_tens (line 218) | def test___make_elastic_range_tens(self):
method test___make_elastic_range_more (line 235) | def test___make_elastic_range_more(self):
class GetElasticPageNumbersTest (line 252) | class GetElasticPageNumbersTest(TestCase):
method _run_tests (line 254) | def _run_tests(self, test_data):
method test_get_elastic_page_numbers_units (line 259) | def test_get_elastic_page_numbers_units(self):
method test_get_elastic_page_numbers_tens (line 285) | def test_get_elastic_page_numbers_tens(self):
method test_get_elastic_page_numbers_more (line 347) | def test_get_elastic_page_numbers_more(self):
class GetQuerystringForPageTest (line 380) | class GetQuerystringForPageTest(TestCase):
method setUp (line 382) | def setUp(self):
method test_querystring (line 385) | def test_querystring(self):
method test_default_page (line 391) | def test_default_page(self):
method test_composition (line 398) | def test_composition(self):
method test_querystring_key (line 405) | def test_querystring_key(self):
class NormalizePageNumberTest (line 412) | class NormalizePageNumberTest(TestCase):
method test_in_range (line 416) | def test_in_range(self):
method test_out_of_range (line 425) | def test_out_of_range(self):
FILE: el_pagination/tests/test_views.py
class AjaxListViewTest (line 14) | class AjaxListViewTest(TestCase):
method setUp (line 22) | def setUp(self):
method check_response (line 28) | def check_response(self, response, template_name, object_list):
method make_view (line 39) | def make_view(self, *args, **kwargs):
method test_list (line 43) | def test_list(self):
method test_list_ajax (line 53) | def test_list_ajax(self):
method test_queryset (line 63) | def test_queryset(self):
method test_queryset_ajax (line 70) | def test_queryset_ajax(self):
method test_model (line 77) | def test_model(self):
method test_model_ajax (line 84) | def test_model_ajax(self):
method test_missing_queryset_or_model (line 91) | def test_missing_queryset_or_model(self):
method test_missing_page_template (line 99) | def test_missing_page_template(self):
method test_do_not_allow_empty (line 106) | def test_do_not_allow_empty(self):
method test_view_in_context (line 114) | def test_view_in_context(self):
FILE: el_pagination/utils.py
function get_data_from_context (line 12) | def get_data_from_context(context):
function get_page_number_from_request (line 26) | def get_page_number_from_request(request, querystring_key=PAGE_LABEL, de...
function get_page_numbers (line 38) | def get_page_numbers(
function _iter_factors (line 93) | def _iter_factors(starting_factor=1):
function _make_elastic_range (line 104) | def _make_elastic_range(begin, end):
function get_elastic_page_numbers (line 129) | def get_elastic_page_numbers(current_page, num_pages):
function get_querystring_for_page (line 149) | def get_querystring_for_page(request, page_number, querystring_key, defa...
function normalize_page_number (line 163) | def normalize_page_number(page_number, page_range):
FILE: el_pagination/views.py
class MultipleObjectMixin (line 13) | class MultipleObjectMixin:
method get_queryset (line 19) | def get_queryset(self):
method get_allow_empty (line 40) | def get_allow_empty(self):
method get_context_object_name (line 49) | def get_context_object_name(self, object_list):
method get_context_data (line 61) | def get_context_data(self, **kwargs):
class BaseListView (line 93) | class BaseListView(MultipleObjectMixin, View):
method get (line 96) | def get(self, request, *args, **kwargs):
class InvalidPaginationListView (line 109) | class InvalidPaginationListView:
method get (line 110) | def get(self, request, *args, **kwargs):
class AjaxMultipleObjectTemplateResponseMixin (line 126) | class AjaxMultipleObjectTemplateResponseMixin(MultipleObjectTemplateResp...
method get_page_template (line 132) | def get_page_template(self, **kwargs):
method get_template_names (line 146) | def get_template_names(self):
class AjaxListView (line 159) | class AjaxListView(AjaxMultipleObjectTemplateResponseMixin, BaseListView):
FILE: tests/develop.py
function call (line 12) | def call(*args):
function pip_install (line 18) | def pip_install(*args):
FILE: tests/manage.py
function main (line 7) | def main():
FILE: tests/project/context_processors.py
function navbar (line 26) | def navbar(request):
function versions (line 42) | def versions(request):
FILE: tests/project/models.py
function make_model_instances (line 4) | def make_model_instances(number):
class TestModel (line 11) | class TestModel(models.Model):
class Meta (line 14) | class Meta:
method __str__ (line 17) | def __str__(self):
FILE: tests/project/views.py
function _make (line 15) | def _make(title, number):
function generic (line 23) | def generic(request, extra_context=None, template=None, number=50):
class SearchListView (line 35) | class SearchListView(ListView):
Condensed preview — 134 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (346K chars).
[
{
"path": ".coveragerc",
"chars": 78,
"preview": "[run]\nsource = el_pagination\nbranch = 1\n\n[report]\nomit = *tests*,*migrations*\n"
},
{
"path": ".github/workflows/tox.yml",
"chars": 3111,
"preview": "name: Tox\n\non: [push, pull_request]\n\njobs:\n tox-lint:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/check"
},
{
"path": ".gitignore",
"chars": 362,
"preview": "# Python\n*.py[cod]\n__pycache__/\n*.so\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist"
},
{
"path": ".pre-commit-config.yaml",
"chars": 487,
"preview": "repos:\n - repo: https://github.com/pycqa/isort\n rev: 5.13.2\n hooks:\n - id: isort\n\n - repo: local\n hooks:"
},
{
"path": ".pylintrc",
"chars": 20626,
"preview": "[MAIN]\n\n# Analyse import fallback blocks. This can be used to support both Python 2 and\n# 3 compatible code, which means"
},
{
"path": ".readthedocs.yaml",
"chars": 874,
"preview": "# .readthedocs.yaml\n# Read the Docs configuration file\n# See https://docs.readthedocs.io/en/stable/config-file/v2.html f"
},
{
"path": ".vscode/launch.json",
"chars": 1139,
"preview": "{\n // Use IntelliSense to learn about possible attributes.\n // Hover to view descriptions of existing attributes.\n"
},
{
"path": ".vscode/settings.json",
"chars": 1514,
"preview": "{\n \"editor.trimAutoWhitespace\": true,\n \"editor.useTabStops\": true,\n \"files.trimTrailingWhitespace\": true,\n \""
},
{
"path": "AUTHORS",
"chars": 112,
"preview": "Oleksandr Shtalinberg <O.Shtalinberg@gmail.com>\nFrancesco Banconi <francesco.banconi@gmail.com>\nChristian Clauss"
},
{
"path": "HACKING",
"chars": 2122,
"preview": "Hacking Django EL(Endless) Pagination\n=================================\n\nHere are the steps needed to set up a developme"
},
{
"path": "INSTALL",
"chars": 201,
"preview": "To install django-el-pagination, run the following command\ninside this directory:\n\n make install\n\nOr if you'd prefer "
},
{
"path": "LICENSE",
"chars": 1112,
"preview": "Copyright (c) 2009-2013 Francesco Banconi\nCopyright (c) 2015-2024 Oleksandr Shtalinberg\n\nPermission is hereby granted, f"
},
{
"path": "MANIFEST.in",
"chars": 659,
"preview": "include AUTHORS\ninclude HACKING\ninclude INSTALL\ninclude LICENSE\ninclude Makefile\ninclude MANIFEST.in\ninclude README.rst\n"
},
{
"path": "Makefile",
"chars": 3709,
"preview": "# Django Endless Pagination Makefile.\n\n# Define these variables based on the system Python versions.\nPYTHON ?= python3\n\n"
},
{
"path": "PKG-INFO",
"chars": 1593,
"preview": "Metadata-Version: 1.2\nName: django-el-pagination\nVersion: 4.2.0\nSummary: Django pagination tools supporting Ajax, multip"
},
{
"path": "README.rst",
"chars": 2725,
"preview": "=============================\nDjango EL(Endless) Pagination\n=============================\n\n| |pypi-pkg-version| |python"
},
{
"path": "doc/Makefile",
"chars": 4658,
"preview": "# Makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line.\nSPHINXOPTS =\nSPHINXBUILD "
},
{
"path": "doc/_static/TRACKME",
"chars": 51,
"preview": "Placeholder file to let Hg include this directory.\n"
},
{
"path": "doc/changelog.rst",
"chars": 13964,
"preview": "Changelog\n=========\n\nUnreleased\n~~~~~~~~~~\n\n**New feature**: Django 5.2.x support.\n Django EL(Endless) Pagination now"
},
{
"path": "doc/conf.py",
"chars": 2919,
"preview": "\"\"\"Django EL(Endless) Pagination documentation build configuration file.\"\"\"\n\nimport os\nimport sys\n\nsys.path.insert(0, os"
},
{
"path": "doc/contacts.rst",
"chars": 461,
"preview": "Source code and contacts\n========================\n\nRepository and bugs\n~~~~~~~~~~~~~~~~~~~\n\nThe **source code** for this"
},
{
"path": "doc/contributing.rst",
"chars": 2020,
"preview": "Contributing\n============\n\n\nHere are the steps needed to set up a development and testing environment.\n\n**WARNING**\n\nThi"
},
{
"path": "doc/current_page_number.rst",
"chars": 1319,
"preview": "Getting the current page number\n===============================\n\nIn the template\n~~~~~~~~~~~~~~~\n\nYou can get and displa"
},
{
"path": "doc/customization.rst",
"chars": 7768,
"preview": "Customization\n=============\n\nSettings\n~~~~~~~~\n\nYou can customize the application using ``settings.py``.\n\n=============="
},
{
"path": "doc/different_first_page.rst",
"chars": 965,
"preview": "Different number of items on the first page\n===========================================\n\nSometimes you might want to sho"
},
{
"path": "doc/digg_pagination.rst",
"chars": 5403,
"preview": "Digg-style pagination\n=====================\n\nDigg-style pagination of queryset objects is really easy to implement. If A"
},
{
"path": "doc/generic_views.rst",
"chars": 3416,
"preview": "Generic views\n=============\n\nThis application provides a customized class-based view, similar to\n*django.views.generic.L"
},
{
"path": "doc/index.rst",
"chars": 707,
"preview": "=============================\nDjango EL(Endless) Pagination\n=============================\n\nThis application provides Twi"
},
{
"path": "doc/javascript.rst",
"chars": 16118,
"preview": "JavaScript reference\n====================\n\nFor each type of pagination it is possible to enable Ajax so that the request"
},
{
"path": "doc/lazy_pagination.rst",
"chars": 1014,
"preview": "Lazy pagination\n===============\n\nUsually pagination requires hitting the database to get the total number of\nitems to di"
},
{
"path": "doc/multiple_pagination.rst",
"chars": 6485,
"preview": "Multiple paginations in the same page\n=====================================\n\nSometimes it is necessary to show different"
},
{
"path": "doc/requirements.txt",
"chars": 297,
"preview": "# Documentation dependencies\nsphinx>=5.0.0,<6.0.0\nsphinx-rtd-theme>=1.0.0\nsphinxcontrib-applehelp>=1.0.4\nsphinxcontrib-d"
},
{
"path": "doc/start.rst",
"chars": 2133,
"preview": "Getting started\n===============\n\nRequirements\n~~~~~~~~~~~~\n\n====== ====================\nPython >= 3.8\nDjango >= 3.2\nj"
},
{
"path": "doc/templatetags_reference.rst",
"chars": 11232,
"preview": "Templatetags reference\n======================\n\n.. _templatetags-paginate:\n\npaginate\n~~~~~~~~\n\nUsage:\n\n.. code-block:: ht"
},
{
"path": "doc/thanks.rst",
"chars": 505,
"preview": "Thanks\n======\n\nThis application was initially inspired by the excellent tool\n*django-pagination* (see https://github.com"
},
{
"path": "doc/twitter_pagination.rst",
"chars": 10972,
"preview": "Twitter-style Pagination\n========================\n\nAssuming the developer wants Twitter-style pagination of\nentries of a"
},
{
"path": "el_pagination/__init__.py",
"chars": 290,
"preview": "\"\"\"Django pagination tools supporting Ajax, multiple and lazy pagination,\nTwitter-style and Digg-style pagination.\n\"\"\"\n\n"
},
{
"path": "el_pagination/decorators.py",
"chars": 3361,
"preview": "\"\"\"View decorators for Ajax powered pagination.\"\"\"\n\nfrom functools import wraps\n\nfrom el_pagination.settings import PAGE"
},
{
"path": "el_pagination/exceptions.py",
"chars": 108,
"preview": "\"\"\"Pagination exceptions.\"\"\"\n\n\nclass PaginationError(Exception):\n \"\"\"Error in the pagination process.\"\"\"\n"
},
{
"path": "el_pagination/loaders.py",
"chars": 840,
"preview": "\"\"\"Django EL Pagination object loaders.\"\"\"\n\nfrom importlib import import_module\n\nfrom django.core.exceptions import Impr"
},
{
"path": "el_pagination/locale/de/LC_MESSAGES/django.po",
"chars": 724,
"preview": "# Django Endless Pagination Locale\n# Copyright (C) 2009-2013 Francesco Banconi\n# This file is distributed under the same"
},
{
"path": "el_pagination/locale/es/LC_MESSAGES/django.po",
"chars": 762,
"preview": "# Django Endless Pagination Locale\n# Copyright (C) 2009-2013 Francesco Banconi\n# This file is distributed under the same"
},
{
"path": "el_pagination/locale/fr/LC_MESSAGES/django.po",
"chars": 737,
"preview": "# Django Endless Pagination Locale\n# Copyright (C) 2009-2013 Francesco Banconi\n# This file is distributed under the same"
},
{
"path": "el_pagination/locale/it/LC_MESSAGES/django.po",
"chars": 699,
"preview": "# Django Endless Pagination Locale\n# Copyright (C) 2009-2013 Francesco Banconi\n# This file is distributed under the same"
},
{
"path": "el_pagination/locale/pt_BR/LC_MESSAGES/django.po",
"chars": 878,
"preview": "# Django Endless Pagination Locale\n# Copyright (C) 2009-2013 Francesco Banconi\n# This file is distributed under the same"
},
{
"path": "el_pagination/locale/zh_CN/LC_MESSAGES/django.po",
"chars": 678,
"preview": "# Django Endless Pagination Locale\n# Copyright (C) 2009-2013 Francesco Banconi\n# This file is distributed under the same"
},
{
"path": "el_pagination/models.py",
"chars": 9728,
"preview": "\"\"\"Ephemeral models used to represent a page and a list of pages.\"\"\"\n\nfrom django.template import loader\nfrom django.uti"
},
{
"path": "el_pagination/paginators.py",
"chars": 4895,
"preview": "\"\"\"Customized Django paginators.\"\"\"\n\nfrom math import ceil\n\nfrom django.core.paginator import EmptyPage, Page, PageNotAn"
},
{
"path": "el_pagination/settings.py",
"chars": 2422,
"preview": "# \"\"\"Django Endless Pagination settings file.\"\"\"\n\nfrom django.conf import settings\n\n# How many objects are normally disp"
},
{
"path": "el_pagination/static/el-pagination/js/el-pagination.js",
"chars": 5818,
"preview": "'use strict';\n\n(function ($) {\n\n $.fn.endlessPaginate = function(options) {\n var defaults = {\n // T"
},
{
"path": "el_pagination/templates/el_pagination/current_link.html",
"chars": 87,
"preview": "<span class=\"endless_page_current\">\n <strong>{{ page.label|safe }}</strong>\n</span>\n"
},
{
"path": "el_pagination/templates/el_pagination/next_link.html",
"chars": 187,
"preview": "<a href=\"{{ page.path }}\"\n rel=\"next{% if add_nofollow %} nofollow{% endif %}\"\n data-el-querystring-key=\"{{ querystr"
},
{
"path": "el_pagination/templates/el_pagination/page_link.html",
"chars": 185,
"preview": "<a href=\"{{ page.path }}\"\n {% if add_nofollow %}rel=\"nofollow\"{% endif %}\n data-el-querystring-key=\"{{ querystring"
},
{
"path": "el_pagination/templates/el_pagination/previous_link.html",
"chars": 187,
"preview": "<a href=\"{{ page.path }}\"\n rel=\"prev{% if add_nofollow %} nofollow{% endif %}\"\n data-el-querystring-key=\"{{ querystr"
},
{
"path": "el_pagination/templates/el_pagination/show_more.html",
"chars": 429,
"preview": "{% load i18n %}\n{% if querystring %}\n <div class=\"endless_container\">\n <a class=\"endless_more{% if class_name "
},
{
"path": "el_pagination/templates/el_pagination/show_more_table.html",
"chars": 455,
"preview": "{% load i18n %}\n{% if querystring %}\n<tr class=\"endless_container\">\n <td colspan=\"100%\">\n <a class=\"endless_mo"
},
{
"path": "el_pagination/templates/el_pagination/show_pages.html",
"chars": 117,
"preview": "{% for page in pages %}\n {{ page.render_link|default:'<span class=\"endless_separator\">...</span>' }}\n{% endfor %}\n"
},
{
"path": "el_pagination/templatetags/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "el_pagination/templatetags/el_pagination_tags.py",
"chars": 23268,
"preview": "\"\"\"Django EL(Endless) Pagination template tags.\"\"\"\n\nimport re\n\nfrom django import template\nfrom django.http import Http4"
},
{
"path": "el_pagination/tests/__init__.py",
"chars": 162,
"preview": "\"\"\"Test model definitions.\"\"\"\n\n\nfrom django.core.management import call_command\n\ncall_command('makemigrations', verbosit"
},
{
"path": "el_pagination/tests/integration/__init__.py",
"chars": 7649,
"preview": "\"\"\"Integration tests base objects definitions.\"\"\"\n\n\nimport os\nimport unittest\nfrom contextlib import contextmanager\n\nfro"
},
{
"path": "el_pagination/tests/integration/test_callbacks.py",
"chars": 1566,
"preview": "\"\"\"Javascript callbacks integration tests.\"\"\"\n\n\n\nfrom el_pagination.tests.integration import SeleniumTestCase\n\n\nclass Ca"
},
{
"path": "el_pagination/tests/integration/test_chunks.py",
"chars": 1698,
"preview": "\"\"\"On scroll chunks integration tests.\"\"\"\n\n\n\nfrom el_pagination.tests.integration import SeleniumTestCase\n\n\nclass Chunks"
},
{
"path": "el_pagination/tests/integration/test_digg.py",
"chars": 1748,
"preview": "\"\"\"Digg-style pagination integration tests.\"\"\"\n\n\n\nfrom el_pagination.tests.integration import SeleniumTestCase\n\n\nclass D"
},
{
"path": "el_pagination/tests/integration/test_feed_wrapper.py",
"chars": 2272,
"preview": "\"\"\"Twitter-style pagination feeding an specific content wrapper integration tests.\"\"\"\n\n\n\nimport el_pagination.settings\nf"
},
{
"path": "el_pagination/tests/integration/test_multiple.py",
"chars": 2243,
"preview": "\"\"\"Multiple pagination integration tests.\"\"\"\n\n\n\nfrom el_pagination.tests.integration import SeleniumTestCase\n\n\nclass Mul"
},
{
"path": "el_pagination/tests/integration/test_onscroll.py",
"chars": 1709,
"preview": "\"\"\"On scroll pagination integration tests.\"\"\"\n\n\n\nfrom el_pagination.tests.integration import SeleniumTestCase\n\n\nclass On"
},
{
"path": "el_pagination/tests/integration/test_twitter.py",
"chars": 1695,
"preview": "\"\"\"Twitter-style pagination integration tests.\"\"\"\n\n\n\nfrom el_pagination.tests.integration import SeleniumTestCase\n\n\nclas"
},
{
"path": "el_pagination/tests/templatetags/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "el_pagination/tests/templatetags/test_el_pagination_tags.py",
"chars": 24819,
"preview": "\"\"\"Endless template tags tests.\"\"\"\n\n\n\nimport string\nimport sys\nimport unittest\nimport xml.etree.ElementTree as etree\n\nfr"
},
{
"path": "el_pagination/tests/test_decorators.py",
"chars": 5622,
"preview": "\"\"\"Decorator tests.\"\"\"\n\n\n\nfrom django.test import TestCase\nfrom django.test.client import RequestFactory\n\nfrom el_pagina"
},
{
"path": "el_pagination/tests/test_loaders.py",
"chars": 3314,
"preview": "\"\"\"Loader tests.\"\"\"\n\n\n\nfrom contextlib import contextmanager\n\nfrom django.core.exceptions import ImproperlyConfigured\nfr"
},
{
"path": "el_pagination/tests/test_models.py",
"chars": 12030,
"preview": "\"\"\"Model tests.\"\"\"\n\n\n\nfrom contextlib import contextmanager\n\nfrom django.template import Context\nfrom django.test import"
},
{
"path": "el_pagination/tests/test_paginators.py",
"chars": 4841,
"preview": "\"\"\"Paginator tests.\"\"\"\n\n\n\nfrom django.test import TestCase\n\nfrom el_pagination import paginators\n\n\nclass PaginatorTestMi"
},
{
"path": "el_pagination/tests/test_utils.py",
"chars": 17314,
"preview": "\"\"\"Utilities tests.\"\"\"\n\n\n\nfrom django.test import TestCase\nfrom django.test.client import RequestFactory\n\nfrom el_pagina"
},
{
"path": "el_pagination/tests/test_views.py",
"chars": 4767,
"preview": "\"\"\"View tests.\"\"\"\n\n\n\nfrom django.core.exceptions import ImproperlyConfigured\nfrom django.http import Http404\nfrom django"
},
{
"path": "el_pagination/utils.py",
"chars": 5138,
"preview": "\"\"\"Django EL Pagination utility functions.\"\"\"\n\nfrom el_pagination import exceptions\nfrom el_pagination.settings import ("
},
{
"path": "el_pagination/views.py",
"chars": 6929,
"preview": "\"\"\"Django EL Pagination class-based views.\"\"\"\n\nfrom django.core.exceptions import ImproperlyConfigured\nfrom django.http "
},
{
"path": "pyproject.toml",
"chars": 2834,
"preview": "[build-system]\nrequires = [\"setuptools>=45\", \"wheel\"]\nbuild-backend = \"setuptools.build_meta\"\n\n[project]\nname = \"django-"
},
{
"path": "release-requirements.txt",
"chars": 25,
"preview": "build>=1.0.0\ntwine>=4.0.0"
},
{
"path": "setup.cfg",
"chars": 347,
"preview": "[bdist_wheel]\nuniversal = 0\n\n[flake8]\nexclude = docs/*,.tox,.git,build,dist\nignore = E123,E128,E402,W503,E731,W601\nmax-l"
},
{
"path": "setup.py",
"chars": 69,
"preview": "from setuptools import setup\n\nif __name__ == \"__main__\":\n setup()\n"
},
{
"path": "tests/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/develop.py",
"chars": 723,
"preview": "\"\"\"Create a development and testing environment using a virtualenv.\"\"\"\nimport os\nimport subprocess\nimport sys\n\nTESTS = o"
},
{
"path": "tests/manage.py",
"chars": 663,
"preview": "#!/usr/bin/env python\n\"\"\"Django's command-line utility for administrative tasks.\"\"\"\nimport os\nimport sys\n\n\ndef main():\n "
},
{
"path": "tests/project/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/project/context_processors.py",
"chars": 1257,
"preview": "\"\"\"Navigation bar context processor.\"\"\"\n\nimport platform\n\nimport django\nfrom django.urls import reverse\n\nimport el_pagin"
},
{
"path": "tests/project/models.py",
"chars": 435,
"preview": "from django.db import models\n\n\ndef make_model_instances(number):\n \"\"\"Make a ``number`` of test model instances and re"
},
{
"path": "tests/project/settings.py",
"chars": 2207,
"preview": "import os\nimport sys\n\n\"\"\"Settings file for the Django project used for tests.\"\"\"\n\nDEBUG = True\nALLOWED_HOSTS = ['*']\n# D"
},
{
"path": "tests/project/static/pagination.css",
"chars": 1279,
"preview": "/* Customized css for the pagination elements. */\n\n.pagination div,\n.pagination td,\n.endless_container {\n display: inli"
},
{
"path": "tests/project/templates/404.html",
"chars": 0,
"preview": ""
},
{
"path": "tests/project/templates/500.html",
"chars": 0,
"preview": ""
},
{
"path": "tests/project/templates/base.html",
"chars": 1654,
"preview": "<!DOCTYPE html>{% load static %}\n<html>\n <head>\n <meta content='text/html; charset=utf-8' http-equiv='Content-Type' "
},
{
"path": "tests/project/templates/callbacks/index.html",
"chars": 1704,
"preview": "{% extends \"base.html\" %}\n\n{% block content %}\n <div id=\"endless\" class=\"span8\">\n {% include page_template %}\n </di"
},
{
"path": "tests/project/templates/callbacks/page.html",
"chars": 475,
"preview": "{% load el_pagination_tags %}\n\n{% paginate 3 objects %}\n{% for object in objects %}\n <div class=\"well object\">\n <h4>"
},
{
"path": "tests/project/templates/chunks/index.html",
"chars": 704,
"preview": "{% extends \"base.html\" %}\n\n{% block content %}\n <div class=\"objects span5\">\n {% include \"chunks/objects_page.html\" %"
},
{
"path": "tests/project/templates/chunks/items_page.html",
"chars": 230,
"preview": "{% load el_pagination_tags %}\n\n{% paginate 5 items using \"items-page\" %}\n{% for item in items %}\n <div class=\"well item"
},
{
"path": "tests/project/templates/chunks/objects_page.html",
"chars": 224,
"preview": "{% load el_pagination_tags %}\n\n{% paginate 5 objects %}\n{% for object in objects %}\n <div class=\"well object\">\n <h4>"
},
{
"path": "tests/project/templates/complete/articles_page.html",
"chars": 256,
"preview": "{% load el_pagination_tags %}\n\n{% lazy_paginate 3 articles using \"articles-page\" %}\n{% for article in articles %}\n <div"
},
{
"path": "tests/project/templates/complete/entries_page.html",
"chars": 232,
"preview": "{% load el_pagination_tags %}\n\n{% lazy_paginate 1,3 entries using \"entries-page\" %}\n{% for entry in entries %}\n <div cl"
},
{
"path": "tests/project/templates/complete/index.html",
"chars": 1564,
"preview": "{% extends \"base.html\" %}\n\n{% block content %}\n <div class=\"span5\">\n {% include \"complete/objects_page.html\" %}\n "
},
{
"path": "tests/project/templates/complete/items_page.html",
"chars": 281,
"preview": "{% load el_pagination_tags %}\n\n{% paginate 3 items using \"items-page\" %}\n{% for item in items %}\n <div class=\"well item"
},
{
"path": "tests/project/templates/complete/objects_page.html",
"chars": 317,
"preview": "{% load el_pagination_tags %}\n\n{% paginate 3 objects starting from page -1 using \"objects-page\" %}\n{% for object in obje"
},
{
"path": "tests/project/templates/complete/objects_simple_page.html",
"chars": 274,
"preview": "{% load el_pagination_tags %}\n\n{% paginate 3 objects %}\n{% for object in objects %}\n <div class=\"well object\">\n <h4>"
},
{
"path": "tests/project/templates/digg/index.html",
"chars": 247,
"preview": "{% extends \"base.html\" %}\n\n{% block content %}\n <div class=\"endless_page_template span12\">\n {% include page_template"
},
{
"path": "tests/project/templates/digg/page.html",
"chars": 274,
"preview": "{% load el_pagination_tags %}\n\n{% paginate 5 objects %}\n{% for object in objects %}\n <div class=\"well object\">\n <h4>"
},
{
"path": "tests/project/templates/digg/table/index.html",
"chars": 251,
"preview": "{% extends \"base.html\" %}\n\n{% block content %}\n <table class=\"endless_page_template span12\">\n {% include page_templa"
},
{
"path": "tests/project/templates/digg/table/page.html",
"chars": 291,
"preview": "{% load el_pagination_tags %}\n\n{% paginate 5 objects %}\n{% for object in objects %}\n <tr class=\"well object\">\n <td>\n"
},
{
"path": "tests/project/templates/feed_wrapper/index.html",
"chars": 470,
"preview": "{% extends \"base.html\" %}\n{% load el_pagination_tags %}\n\n{% block content %}\n<table class=\"table\">\n <thead>\n <tr>\n "
},
{
"path": "tests/project/templates/feed_wrapper/page.html",
"chars": 182,
"preview": "{% load el_pagination_tags %}\n{% if request.is_ajax %}\n {% lazy_paginate 10 objects %}\n{% endif %}\n{% for object in obj"
},
{
"path": "tests/project/templates/home.html",
"chars": 1133,
"preview": "{% extends \"base.html\" %}\n\n{% block content %}\n <div class=\"span12\">\n <p class=\"lead\">\n This project is intende"
},
{
"path": "tests/project/templates/multiple/entries_page.html",
"chars": 247,
"preview": "{% load el_pagination_tags %}\n\n{% lazy_paginate 1,3 entries using \"entries-page\" %}\n{% for entry in entries %}\n <div cl"
},
{
"path": "tests/project/templates/multiple/index.html",
"chars": 439,
"preview": "{% extends \"base.html\" %}\n\n{% block content %}\n <div class=\"endless_page_template span6\">\n {% include \"multiple/obje"
},
{
"path": "tests/project/templates/multiple/items_page.html",
"chars": 281,
"preview": "{% load el_pagination_tags %}\n\n{% paginate 3 items using \"items-page\" %}\n{% for item in items %}\n <div class=\"well item"
},
{
"path": "tests/project/templates/multiple/objects_page.html",
"chars": 338,
"preview": "{% load el_pagination_tags %}\n\n{% paginate 3 objects using \"objects-page\" %}\n{% for object in objects %}\n <div class=\"w"
},
{
"path": "tests/project/templates/onscroll/index.html",
"chars": 249,
"preview": "{% extends \"base.html\" %}\n\n{% block content %}\n <div class=\"span12\">\n {% include page_template %}\n </div>\n{% endblo"
},
{
"path": "tests/project/templates/onscroll/page.html",
"chars": 224,
"preview": "{% load el_pagination_tags %}\n\n{% paginate 10 objects %}\n{% for object in objects %}\n <div class=\"well object\">\n <h4"
},
{
"path": "tests/project/templates/onscroll/table/index.html",
"chars": 253,
"preview": "{% extends \"base.html\" %}\n\n{% block content %}\n <table class=\"span12\">\n {% include page_template %}\n </table>\n{% en"
},
{
"path": "tests/project/templates/onscroll/table/page.html",
"chars": 252,
"preview": "{% load el_pagination_tags %}\n\n{% paginate 10 objects %}\n{% for object in objects %}\n <tr class=\"well object\">\n <td>"
},
{
"path": "tests/project/templates/twitter/index.html",
"chars": 225,
"preview": "{% extends \"base.html\" %}\n\n{% block content %}\n <div class=\"span12\">\n {% include page_template %}\n </div>\n{% endblo"
},
{
"path": "tests/project/templates/twitter/page.html",
"chars": 223,
"preview": "{% load el_pagination_tags %}\n\n{% paginate 5 objects %}\n{% for object in objects %}\n <div class=\"well object\">\n <h4>"
},
{
"path": "tests/project/templates/twitter/table/index.html",
"chars": 229,
"preview": "{% extends \"base.html\" %}\n\n{% block content %}\n <table class=\"span12\">\n {% include page_template %}\n </table>\n{% en"
},
{
"path": "tests/project/templates/twitter/table/page.html",
"chars": 246,
"preview": "{% load el_pagination_tags %}\n\n{% paginate 5 objects %}\n{% for object in objects %}\n <tr class=\"well object\">\n <td>\n"
},
{
"path": "tests/project/urls.py",
"chars": 3211,
"preview": "\"\"\"Test project URL patterns.\"\"\"\n\nfrom django.conf import settings\nfrom django.contrib.staticfiles.urls import staticfil"
},
{
"path": "tests/project/views.py",
"chars": 977,
"preview": "\"\"\"Test project views.\"\"\"\n\n\n\nfrom django.shortcuts import render\nfrom django.views.generic import ListView\n\nLOREM = \"\"\"L"
},
{
"path": "tests/requirements.pip",
"chars": 405,
"preview": "# Django Endless Pagination test requirements.\n# Dependencies are installed by the ``make`` command.\ndjango>=3.2.0\ncodec"
},
{
"path": "tests/with_venv.sh",
"chars": 350,
"preview": "#!/bin/bash\n\nexport DJANGO_LIVE_TEST_SERVER_ADDRESS=\"localhost:8000-8010,8080,9200-9300\"\nexport DJANGO_TEST_PROCESSES=\"1"
},
{
"path": "tox.ini",
"chars": 2053,
"preview": "[tox]\nenvlist = py3{8,9,10,11,12}-django42\n py3{12}-django50\n py3{10,11,12,13}-django51\n py3{10,11,"
}
]
// ... and 6 more files (download for full content)
About this extraction
This page contains the full source code of the shtalinberg/django-el-pagination GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 134 files (318.4 KB), approximately 80.8k tokens, and a symbol index with 390 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.