[
  {
    "path": ".coveragerc",
    "content": "[run]\nsource = el_pagination\nbranch = 1\n\n[report]\nomit = *tests*,*migrations*\n"
  },
  {
    "path": ".github/workflows/tox.yml",
    "content": "name: Tox\n\non: [push, pull_request]\n\njobs:\n  tox-lint:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-python@v5\n        with:\n          python-version: 3.x\n      - run: pip install --upgrade pip\n      - run: pip install tox\n      - run: tox -e lint || true  # Fix error and remove \"|| true\"!\n\n  tox-docs:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-python@v5\n        with:\n          python-version: 3.x\n      - run: pip install --upgrade pip\n      - run: pip install tox\n      - run: tox -e docs || true  # Fix error and remove \"|| true\"!\n\n  tox-docs-linkcheck:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-python@v5\n        with:\n          python-version: 3.x\n      - run: pip install --upgrade pip\n      - run: pip install tox\n      - run: tox -e docs-linkcheck || true  # Fix error and remove \"|| true\"!\n\n  build:\n    strategy:\n      fail-fast: false\n      max-parallel: 5\n      matrix:\n        os: [ubuntu-latest]  # [macos-latest, ubuntu-latest, windows-latest]\n        python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13']\n        django-version: [\"==3.2.*\", \"==4.1.*\", \"==4.2.*\", \"==5.0.*\", \"==5.1.*\", \"==5.2.*\"]\n        exclude:\n        # https://docs.djangoproject.com/en/stable/faq/install/#what-python-version-can-i-use-with-django\n          - python-version: 3.11\n            django-version: \"==3.2.*\"\n          - python-version: 3.12\n            django-version: \"==3.2.*\"\n          - python-version: 3.12\n            django-version: \"==4.1.*\"\n          - python-version: 3.8\n            django-version: \"==5.0.*\"\n          - python-version: 3.8\n            django-version: \"==5.1.*\"\n          - python-version: 3.8\n            django-version: \"==5.2.*\"\n          - python-version: 3.9\n            django-version: \"==5.0.*\"\n          - python-version: 3.9\n            django-version: \"==5.1.*\"\n          - python-version: 3.9\n            django-version: \"==5.2.*\"\n        # # Django 4.0 no longer supports python 3.7\n        #   - python-version: 3.7\n        #     django-version: \"==4.0.*\"\n\n    runs-on: ${{ matrix.os }}\n\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-python@v5\n        with:\n          python-version: ${{ matrix.python }}\n\n      - name: Get pip cache dir\n        id: pip-cache\n        run: |\n          echo \"dir=$(pip cache dir)\" >> $GITHUB_OUTPUT\n\n      - name: Cache\n        uses: actions/cache@v3\n        with:\n          path: ${{ steps.pip-cache.outputs.dir }}\n          key:\n            -${{ matrix.python-version }}-v1-${{ hashFiles('**/setup.py') }}\n          restore-keys: |\n            -${{ matrix.python-version }}-v1-\n\n      - name: Install dependencies\n        run: |\n          python -m pip install --upgrade pip\n          python -m pip install --upgrade tox tox-gh-actions\n\n      - name: Tox tests\n        run: |\n          tox -v\n\n      - name: Upload coverage\n        uses: codecov/codecov-action@v3\n        with:\n          name: Python ${{ matrix.python-version }}\n"
  },
  {
    "path": ".gitignore",
    "content": "# Python\n*.py[cod]\n__pycache__/\n*.so\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\n*.egg-info/\n.installed.cfg\n*.egg\n\n# Sphinx documentation\n_static/TRACKME\n.doctrees/\ndoc/_build\ndoc/.doctrees\n\n*.sublime-*\n.idea*\n/help-man\n~$\n.venv/\n.coverage\n.ropeproject\n.DS_Store\nMANIFEST\ntests/settings_local.py\n*.log\n.tox/\n"
  },
  {
    "path": ".pre-commit-config.yaml",
    "content": "repos:\n  - repo: https://github.com/pycqa/isort\n    rev: 5.13.2\n    hooks:\n      - id: isort\n\n  - repo: local\n    hooks:\n      - id: black\n        name: black\n        language: system\n        entry: sh -c 'make black_diff'\n        types: [python]\n        exclude: (migrations/|\\.venv|\\.git)\n\n  - repo: local\n    hooks:\n      - id: pylint\n        name: pylint\n        language: system\n        entry: sh -c 'make pylint'\n        types: [python]\n        exclude: (migrations/|\\.venv|\\.git)\n"
  },
  {
    "path": ".pylintrc",
    "content": "[MAIN]\n\n# Analyse import fallback blocks. This can be used to support both Python 2 and\n# 3 compatible code, which means that the block might have code that exists\n# only in one or another interpreter, leading to false positives when analysed.\nanalyse-fallback-blocks=no\n\n# Load and enable all available extensions. Use --list-extensions to see a list\n# all available extensions.\n#enable-all-extensions=\n\n# In error mode, messages with a category besides ERROR or FATAL are\n# suppressed, and no reports are done by default. Error mode is compatible with\n# disabling specific errors.\n#errors-only=\n\n# Always return a 0 (non-error) status code, even if lint errors are found.\n# This is primarily useful in continuous integration scripts.\n#exit-zero=\n\n# A comma-separated list of package or module names from where C extensions may\n# be loaded. Extensions are loading into the active Python interpreter and may\n# run arbitrary code.\nextension-pkg-allow-list=\n\n# A comma-separated list of package or module names from where C extensions may\n# be loaded. Extensions are loading into the active Python interpreter and may\n# run arbitrary code. (This is an alternative name to extension-pkg-allow-list\n# for backward compatibility.)\nextension-pkg-whitelist=\n\n# Return non-zero exit code if any of these messages/categories are detected,\n# even if score is above --fail-under value. Syntax same as enable. Messages\n# specified are enabled, while categories only check already-enabled messages.\nfail-on=\n\n# Specify a score threshold to be exceeded before program exits with error.\nfail-under=10\n\n# Interpret the stdin as a python script, whose filename needs to be passed as\n# the module_or_package argument.\n#from-stdin=\n\n# Files or directories to be skipped. They should be base names, not paths.\nignore=tests\n\n# Add files or directories matching the regex patterns to the ignore-list. The\n# regex matches against paths and can be in Posix or Windows format.\nignore-paths=logs,\n      docs,\n      help-man\n\n\n# Files or directories matching the regex patterns are skipped. The regex\n# matches against base names, not paths. The default value ignores Emacs file\n# locks\nignore-patterns=conftest.py,tests.py,test_*, test*\n\n# List of module names for which member attributes should not be checked\n# (useful for modules/projects where namespaces are manipulated during runtime\n# and thus existing member attributes cannot be deduced by static analysis). It\n# supports qualified module names, as well as Unix pattern matching.\nignored-modules=\n\n# Python code to execute, usually for sys.path manipulation such as\n# pygtk.require().\n#init-hook='import sys; sys.path = [\"src\"] + sys.path'\ninit-hook='import os, sys; sys.path.append(\"/el_pagination\"); sys.path.append(\"/tests\"); sys.path.append(\".\")'\n\n# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the\n# number of processors available to use, and will cap the count on Windows to\n# avoid hangs.\njobs=1\n\n# Control the amount of potential inferred values when inferring a single\n# object. This can help the performance when dealing with large functions or\n# complex, nested conditions.\nlimit-inference-results=100\n\n# List of plugins (as comma separated values of python module names) to load,\n# usually to register additional checkers.\nload-plugins=pylint_django\n\n# Pickle collected data for later comparisons.\npersistent=no\n\n# Minimum Python version to use for version dependent checks. Will default to\n# the version used to run pylint.\npy-version=3.10\n\n# Discover python modules and packages in the file system subtree.\nrecursive=no\n\n# When enabled, pylint would attempt to guess common misconfiguration and emit\n# user-friendly hints instead of false-positive error messages.\nsuggestion-mode=yes\n\n# Allow loading of arbitrary C extensions. Extensions are imported into the\n# active Python interpreter and may run arbitrary code.\nunsafe-load-any-extension=no\n\n# In verbose mode, extra non-checker-related info will be displayed.\n#verbose=\n\n\n[REPORTS]\n\n# Python expression which should return a score less than or equal to 10. You\n# have access to the variables 'fatal', 'error', 'warning', 'refactor',\n# 'convention', and 'info' which contain the number of messages in each\n# category, as well as 'statement' which is the total number of statements\n# analyzed. This score is used by the global evaluation report (RP0004).\nevaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)\n\n# Template used to display messages. This is a python new-style format string\n# used to format the message information. See doc for all details.\nmsg-template=\n\n# Set the output format. Available formats are text, parseable, colorized, json\n# and msvs (visual studio). You can also give a reporter class, e.g.\n# mypackage.mymodule.MyReporterClass.\noutput-format=colorized\n\n# Tells whether to display a full report or only the messages.\nreports=no\n\n# Activate the evaluation score.\nscore=yes\n\n\n[MESSAGES CONTROL]\n\n# Only show warnings with the listed confidence levels. Leave empty to show\n# all. Valid levels: HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE,\n# UNDEFINED.\nconfidence=HIGH,\n           CONTROL_FLOW,\n           INFERENCE,\n           INFERENCE_FAILURE,\n           UNDEFINED\n\n# Disable the message, report, category or checker with the given id(s). You\n# can either give multiple identifiers separated by comma (,) or put this\n# option multiple times (only on the command line, not in the configuration\n# file where it should appear only once). You can also use \"--disable=all\" to\n# disable everything first and then re-enable specific checks. For example, if\n# you want to run only the similarities checker, you can use \"--disable=all\n# --enable=similarities\". If you want to run only the classes checker, but have\n# no Warning level messages displayed, use \"--disable=all --enable=classes\n# --disable=W\".\ndisable=django-settings-module-not-found,\n        missing-module-docstring,\n        missing-class-docstring,\n        missing-function-docstring,\n        too-few-public-methods,\n        unused-argument,\n\n\n\n# Enable the message, report, category or checker with the given id(s). You can\n# either give multiple identifier separated by comma (,) or put this option\n# multiple time (only on the command line, not in the configuration file where\n# it should appear only once). See also the \"--disable\" option for examples.\nenable=c-extension-no-member\n\n\n[SIMILARITIES]\n\n# Comments are removed from the similarity computation\nignore-comments=yes\n\n# Docstrings are removed from the similarity computation\nignore-docstrings=yes\n\n# Imports are removed from the similarity computation\nignore-imports=yes\n\n# Signatures are removed from the similarity computation\nignore-signatures=yes\n\n# Minimum lines number of a similarity.\nmin-similarity-lines=4\n\n\n[IMPORTS]\n\n# List of modules that can be imported at any level, not just the top level\n# one.\nallow-any-import-level=\n\n# Allow wildcard imports from modules that define __all__.\nallow-wildcard-with-all=no\n\n# Deprecated modules which should not be used, separated by a comma.\ndeprecated-modules=optparse,tkinter.tix\n\n# Output a graph (.gv or any supported image format) of external dependencies\n# to the given file (report RP0402 must not be disabled).\next-import-graph=\n\n# Output a graph (.gv or any supported image format) of all (i.e. internal and\n# external) dependencies to the given file (report RP0402 must not be\n# disabled).\nimport-graph=\n\n# Output a graph (.gv or any supported image format) of internal dependencies\n# to the given file (report RP0402 must not be disabled).\nint-import-graph=\n\n# Force import order to recognize a module as part of the standard\n# compatibility libraries.\nknown-standard-library=\n\n# Force import order to recognize a module as part of a third party library.\nknown-third-party=enchant\n\n# Couples of modules and preferred modules, separated by a comma.\npreferred-modules=\n\n\n[TYPECHECK]\n\n# List of decorators that produce context managers, such as\n# contextlib.contextmanager. Add to this list to register other decorators that\n# produce valid context managers.\ncontextmanager-decorators=contextlib.contextmanager\n\n# List of members which are set dynamically and missed by pylint inference\n# system, and so shouldn't trigger E1101 when accessed. Python regular\n# expressions are accepted.\ngenerated-members=cv2.*\n\n# Tells whether to warn about missing members when the owner of the attribute\n# is inferred to be None.\nignore-none=yes\n\n# This flag controls whether pylint should warn about no-member and similar\n# checks whenever an opaque object is returned when inferring. The inference\n# can return multiple potential results while evaluating a Python object, but\n# some branches might not be evaluated, which results in partial inference. In\n# that case, it might be useful to still emit no-member and other checks for\n# the rest of the inferred objects.\nignore-on-opaque-inference=yes\n\n# List of symbolic message names to ignore for Mixin members.\nignored-checks-for-mixins=no-member,\n                          not-async-context-manager,\n                          not-context-manager,\n                          attribute-defined-outside-init\n\n# List of class names for which member attributes should not be checked (useful\n# for classes with dynamically set attributes). This supports the use of\n# qualified names.\nignored-classes=optparse.Values,thread._local,_thread._local\n\n# Show a hint with possible names when a member name was not found. The aspect\n# of finding the hint is based on edit distance.\nmissing-member-hint=yes\n\n# The minimum edit distance a name should have in order to be considered a\n# similar match for a missing member name.\nmissing-member-hint-distance=1\n\n# The total number of similar names that should be taken in consideration when\n# showing a hint for a missing member.\nmissing-member-max-choices=1\n\n# Regex pattern to define which classes are considered mixins.\nmixin-class-rgx=.*[Mm]ixin\n\n# List of decorators that change the signature of a decorated function.\nsignature-mutators=\n\n\n[LOGGING]\n\n# The type of string formatting that logging methods do. `old` means using %\n# formatting, `new` is for `{}` formatting.\nlogging-format-style=old\n\n# Logging modules to check that the string format arguments are in logging\n# function parameter format.\nlogging-modules=logging\n\n\n[VARIABLES]\n\n# List of additional names supposed to be defined in builtins. Remember that\n# you should avoid defining new builtins when possible.\nadditional-builtins=\n\n# Tells whether unused global variables should be treated as a violation.\nallow-global-unused-variables=yes\n\n# List of names allowed to shadow builtins\nallowed-redefined-builtins=\n\n# List of strings which can identify a callback function by name. A callback\n# name must start or end with one of those strings.\ncallbacks=cb_,\n          _cb\n\n# A regular expression matching the name of dummy variables (i.e. expected to\n# not be used).\ndummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_\n\n# Argument names that match this expression will be ignored. Default to name\n# with leading underscore.\nignored-argument-names=_.*|^ignored_|^unused_\n\n# Tells whether we should check for unused import in __init__ files.\ninit-import=no\n\n# List of qualified module names which can have objects that can redefine\n# builtins.\nredefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io\n\n\n[STRING]\n\n# This flag controls whether inconsistent-quotes generates a warning when the\n# character used as a quote delimiter is used inconsistently within a module.\ncheck-quote-consistency=no\n\n# This flag controls whether the implicit-str-concat should generate a warning\n# on implicit string concatenation in sequences defined over several lines.\ncheck-str-concat-over-line-jumps=no\n\n\n[DESIGN]\n\n# List of regular expressions of class ancestor names to ignore when counting\n# public methods (see R0903)\nexclude-too-few-public-methods=\n\n# List of qualified class names to ignore when counting class parents (see\n# R0901)\nignored-parents=\n\n# Maximum number of arguments for function / method.\nmax-args=10\n\n# Maximum number of attributes for a class (see R0902).\nmax-attributes=13\n\n# Maximum number of boolean expressions in an if statement (see R0916).\nmax-bool-expr=5\n\n# Maximum number of branch for function / method body.\nmax-branches=15\n\n# Maximum number of locals for function / method body.\nmax-locals=20\n\n# Maximum number of parents for a class (see R0901).\nmax-parents=10\n\n# Maximum number of positional arguments for function / method.\nmax-positional-arguments=10\n\n# Maximum number of public methods for a class (see R0904).\nmax-public-methods=20\n\n# Maximum number of return / yield for function / method body.\nmax-returns=6\n\n# Maximum number of statements in function / method body.\nmax-statements=50\n\n# Minimum number of public methods for a class (see R0903).\nmin-public-methods=2\n\n\n[REFACTORING]\n\n# Maximum number of nested blocks for function / method body\nmax-nested-blocks=5\n\n# Complete name of functions that never returns. When checking for\n# inconsistent-return-statements if a never returning function is called then\n# it will be considered as an explicit return statement and no message will be\n# printed.\nnever-returning-functions=sys.exit\n\n\n[BASIC]\n\n# Naming style matching correct argument names.\nargument-naming-style=snake_case\n\n# Regular expression matching correct argument names. Overrides argument-\n# naming-style. If left empty, argument names will be checked with the set\n# naming style.\n#argument-rgx=\n\n# Naming style matching correct attribute names.\nattr-naming-style=snake_case\n\n# Regular expression matching correct attribute names. Overrides attr-naming-\n# style. If left empty, attribute names will be checked with the set naming\n# style.\n#attr-rgx=\n\n# Bad variable names which should always be refused, separated by a comma.\nbad-names=foo,\n          bar,\n          baz,\n          toto,\n          tutu,\n          tata\n\n# Bad variable names regexes, separated by a comma. If names match any regex,\n# they will always be refused\nbad-names-rgxs=\n\n# Naming style matching correct class attribute names.\nclass-attribute-naming-style=any\n\n# Regular expression matching correct class attribute names. Overrides class-\n# attribute-naming-style. If left empty, class attribute names will be checked\n# with the set naming style.\n#class-attribute-rgx=\n\n# Naming style matching correct class constant names.\nclass-const-naming-style=UPPER_CASE\n\n# Regular expression matching correct class constant names. Overrides class-\n# const-naming-style. If left empty, class constant names will be checked with\n# the set naming style.\n#class-const-rgx=\n\n# Naming style matching correct class names.\nclass-naming-style=PascalCase\n\n# Regular expression matching correct class names. Overrides class-naming-\n# style. If left empty, class names will be checked with the set naming style.\n#class-rgx=\n\n# Naming style matching correct constant names.\nconst-naming-style=UPPER_CASE\n\n# Regular expression matching correct constant names. Overrides const-naming-\n# style. If left empty, constant names will be checked with the set naming\n# style.\n#const-rgx=\n\n# Minimum line length for functions/classes that require docstrings, shorter\n# ones are exempt.\ndocstring-min-length=-1\n\n# Naming style matching correct function names.\nfunction-naming-style=snake_case\n\n# Regular expression matching correct function names. Overrides function-\n# naming-style. If left empty, function names will be checked with the set\n# naming style.\n#function-rgx=\n\n# Good variable names which should always be accepted, separated by a comma.\ngood-names=i,\n           j,\n           k,\n           ex,\n           Run,\n           _\n\n# Good variable names regexes, separated by a comma. If names match any regex,\n# they will always be accepted\ngood-names-rgxs=\n\n# Include a hint for the correct naming format with invalid-name.\ninclude-naming-hint=no\n\n# Naming style matching correct inline iteration names.\ninlinevar-naming-style=any\n\n# Regular expression matching correct inline iteration names. Overrides\n# inlinevar-naming-style. If left empty, inline iteration names will be checked\n# with the set naming style.\n#inlinevar-rgx=\n\n# Naming style matching correct method names.\nmethod-naming-style=snake_case\n\n# Regular expression matching correct method names. Overrides method-naming-\n# style. If left empty, method names will be checked with the set naming style.\n#method-rgx=\n\n# Naming style matching correct module names.\nmodule-naming-style=snake_case\n\n# Regular expression matching correct module names. Overrides module-naming-\n# style. If left empty, module names will be checked with the set naming style.\n#module-rgx=\n\n# Colon-delimited sets of names that determine each other's naming style when\n# the name regexes allow several styles.\nname-group=\n\n# Regular expression which should only match function or class names that do\n# not require a docstring.\nno-docstring-rgx=^_\n\n# List of decorators that produce properties, such as abc.abstractproperty. Add\n# to this list to register other decorators that produce valid properties.\n# These decorators are taken in consideration only for invalid-name.\nproperty-classes=abc.abstractproperty\n\n# Regular expression matching correct type variable names. If left empty, type\n# variable names will be checked with the set naming style.\n#typevar-rgx=\n\n# Naming style matching correct variable names.\nvariable-naming-style=snake_case\n\n# Regular expression matching correct variable names. Overrides variable-\n# naming-style. If left empty, variable names will be checked with the set\n# naming style.\nvariable-rgx=(([a-z_][a-z0-9_]+)|(_[a-z0-9_]*)|(__[a-z][a-z0-9_]+__))$\n\n\n[MISCELLANEOUS]\n\n# List of note tags to take in consideration, separated by a comma.\nnotes=FIXME,\n      XXX,\n      TODO\n\n# Regular expression of note tags to take in consideration.\nnotes-rgx=\n\n\n[FORMAT]\n\n# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.\nexpected-line-ending-format=\n\n# Regexp for a line that is allowed to be longer than the limit.\nignore-long-lines=^\\s*(# )?<?https?://\\S+>?$\n\n# Number of spaces of indent required inside a hanging or continued line.\nindent-after-paren=4\n\n# String used as indentation unit. This is usually \"    \" (4 spaces) or \"\\t\" (1\n# tab).\nindent-string='    '\n\n# Maximum number of characters on a single line.\nmax-line-length=119\n\n# Maximum number of lines in a module.\nmax-module-lines=1200\n\n# Allow the body of a class to be on the same line as the declaration if body\n# contains single statement.\nsingle-line-class-stmt=no\n\n# Allow the body of an if to be on the same line as the test if there is no\n# else.\nsingle-line-if-stmt=no\n\n\n[SPELLING]\n\n# Limits count of emitted suggestions for spelling mistakes.\nmax-spelling-suggestions=4\n\n# Spelling dictionary name. Available dictionaries: none. To make it work,\n# install the 'python-enchant' package.\nspelling-dict=\n\n# List of comma separated words that should be considered directives if they\n# appear at the beginning of a comment and should not be checked.\nspelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy:\n\n# List of comma separated words that should not be checked.\nspelling-ignore-words=\n\n# A path to a file that contains the private dictionary; one word per line.\nspelling-private-dict-file=\n\n# Tells whether to store unknown words to the private dictionary (see the\n# --spelling-private-dict-file option) instead of raising a message.\nspelling-store-unknown-words=no\n\n\n[CLASSES]\n\n# Warn about protected attribute access inside special methods\ncheck-protected-access-in-special-methods=no\n\n# List of method names used to declare (i.e. assign) instance attributes.\ndefining-attr-methods=__init__,\n                      __new__,\n                      setUp\n\n# List of member names, which should be excluded from the protected access\n# warning.\nexclude-protected=_asdict,\n                  _fields,\n                  _replace,\n                  _source,\n                  _make\n\n# List of valid names for the first argument in a class method.\nvalid-classmethod-first-arg=cls\n\n# List of valid names for the first argument in a metaclass class method.\nvalid-metaclass-classmethod-first-arg=cls\n\n\n[EXCEPTIONS]\n\n# Exceptions that will emit a warning when caught.\novergeneral-exceptions=builtins.BaseException,\n                       builtins.Exception\n\n\n[DJANGO FOREIGN KEYS REFERENCED BY STRINGS]\n\n# A module containing Django settings to be used while linting.\ndjango-settings-module=project.settings\n"
  },
  {
    "path": ".readthedocs.yaml",
    "content": "# .readthedocs.yaml\n# Read the Docs configuration file\n# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details\n\n# Required\nversion: 2\n\n# Set the OS, Python version and other tools you might need\nbuild:\n  os: ubuntu-22.04\n  tools:\n    python: \"3.10\"\n\n# Build documentation in the docs/ directory with Sphinx\nsphinx:\n  configuration: doc/conf.py\n  fail_on_warning: true\n\n# Optionally build your docs in additional formats such as PDF and ePub\nformats:\n  - pdf\n\n# Optionally declare the Python requirements required to build your docs\npython:\n  install:\n    - method: pip\n      path: .\n      extra_requirements:\n        - doc\n    - requirements: doc/requirements.txt\n\n# Don't install the package in editable mode\n# This ensures we're testing the actual installation\npython:\n  install:\n    - method: pip\n      path: .\n    - requirements: doc/requirements.txt"
  },
  {
    "path": ".vscode/launch.json",
    "content": "{\n    // Use IntelliSense to learn about possible attributes.\n    // Hover to view descriptions of existing attributes.\n    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387\n    \"version\": \"0.2.0\",\n    \"configurations\": [\n        {\n            \"name\": \"Python Demo: Django\",\n            \"type\": \"debugpy\",\n            \"request\": \"launch\",\n            \"program\": \"${workspaceFolder}/tests/manage.py\",\n            \"console\": \"internalConsole\",\n            \"args\": [\n                \"runserver\",\n                \"127.0.0.1:8000\",\n                \"--noreload\",\n                \"--settings=project.settings\"\n            ],\n            \"django\": true,\n            \"autoStartBrowser\": false\n        },\n        {\n            \"type\": \"chrome\",\n            \"request\": \"attach\",\n            \"name\": \"Attach to Chrome\",\n            \"port\": 9222,\n            \"urlFilter\": \"http://127.0.0.1:8000/*\",\n            \"webRoot\": \"${workspaceFolder}\"\n        },\n    ],\n    \"compounds\": [\n        {\n            \"name\": \"Django\",\n            \"configurations\": [\n                \"Python Demo: Django\",\n            ]\n        },\n    ]\n}"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n    \"editor.trimAutoWhitespace\": true,\n    \"editor.useTabStops\": true,\n    \"files.trimTrailingWhitespace\": true,\n    \"html.format.enable\": false,\n    \"html.format.templating\": true,\n    \"javascript.format.insertSpaceAfterFunctionKeywordForAnonymousFunctions\": false,\n    \"javascript.format.insertSpaceAfterKeywordsInControlFlowStatements\": false,\n    \"javascript.format.insertSpaceAfterCommaDelimiter\": false,\n    \"javascript.format.insertSpaceAfterOpeningAndBeforeClosingEmptyBraces\": false,\n    \"javascript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces\": false,\n    \"javascript.format.insertSpaceBeforeAndAfterBinaryOperators\": false,\n    \"javascript.format.insertSpaceAfterSemicolonInForStatements\": false,\n    \"[django-html]\": {\n        \"editor.defaultFormatter\": \"monosans.djlint\"\n    },\n    \"emmet.includeLanguages\": {\n        \"django-html\": \"html\",\n    },\n    \"files.associations\": {\n        \"**/templates{/**,*}.html\": \"django-html\",\n        \"**/templates{/**,*}.htm\": \"django-html\",\n        \"**/templates{/**,*}.txt\": \"django-txt\",\n        \"**/requirements{/**,*}.{txt,pip}\": \"pip-requirements\",\n        \"**/*.html\": \"html\"\n    },\n    \"[python]\": {\n        \"editor.formatOnType\": true,\n        \"editor.defaultFormatter\": \"ms-python.black-formatter\"\n    },\n    \"python.analysis.extraPaths\": [\n        \"./src/apps\",\n        \"./src/compat\"\n    ],\n    \"python.testing.pytestArgs\": [\n        \"src\"\n    ],\n    \"python.testing.unittestEnabled\": false,\n    \"python.testing.pytestEnabled\": true,\n}"
  },
  {
    "path": "AUTHORS",
    "content": "Oleksandr Shtalinberg <O.Shtalinberg@gmail.com>\nFrancesco Banconi <francesco.banconi@gmail.com>\nChristian Clauss"
  },
  {
    "path": "HACKING",
    "content": "Hacking Django EL(Endless) Pagination\n=================================\n\nHere are the steps needed to set up a development and testing environment.\n\nCreating a development environment\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThe development environment is created in a venv. The environment\ncreation requires the *make* program to be installed.\n\nTo install *make* under Debian/Ubuntu::\n\n    $ sudo apt-get install build-essential\n\nUnder Mac OS/X, *make* is available as part of XCode.\n\nAt this point, from the root of this branch, run the command::\n\n    $ make\n\nThis command will create a ``.venv`` directory in the branch root, ignored\nby DVCSes, containing the development virtual environment with all the\ndependencies.\n\nTesting the application\n~~~~~~~~~~~~~~~~~~~~~~~\n\nRun the tests::\n\n    $ make test\n\nThe command above also runs all tests except the available integration. They use\nSelenium and require Firefox to be installed. To include executing integration\ntests, define the environment variable USE_SELENIUM, e.g.::\n\n    $ make test USE_SELENIUM=1\n\nIntegration tests are excluded by default when using Python 3. The test suite\nrequires Python >= 3.8.0.\n\nRun the tests and lint/pep8 checks::\n\n    $ make check\n\nAgain, to exclude integration tests::\n\n    $ make check USE_SELENIUM=1\n\nDebugging\n~~~~~~~~~\n\nRun the Django shell (Python interpreter)::\n\n    $ make shell\n\nRun the Django development server for manual testing::\n\n    $ make server\n\nAfter executing the command above, it is possible to navigate the testing\nproject going to <http://localhost:8000>.\n\nSee all the available make targets, including info on how to create a Python 3\ndevelopment environment::\n\n    $ make help\n\n\nThanks for contributing, and have fun!\n\n\nPipy testing before bump new vervion\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nBefore register our package with PyPI, it is important to test if the package actually works.\n\npip install -e git+https://shtalinberg@github.com/shtalinberg/django-el-pagination#egg=django-el-pagination\npip install -e git+https://shtalinberg@github.com/shtalinberg/django-el-pagination@develop#egg=django-el-pagination\n"
  },
  {
    "path": "INSTALL",
    "content": "To install django-el-pagination, run the following command\ninside this directory:\n\n    make install\n\nOr if you'd prefer you can simply place the included ``el_pagination``\npackage on your Python path.\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2009-2013 Francesco Banconi\nCopyright (c) 2015-2024 Oleksandr Shtalinberg\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies\nof the Software, and to permit persons to whom the Software is furnished to do\nso, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "MANIFEST.in",
    "content": "include AUTHORS\ninclude HACKING\ninclude INSTALL\ninclude LICENSE\ninclude Makefile\ninclude MANIFEST.in\ninclude README.rst\n\n# Documentation\nrecursive-include doc *\n\n# Test files\nrecursive-include tests *\n\n# Package resources\nrecursive-include el_pagination/static *\nrecursive-include el_pagination/locale *\nrecursive-include el_pagination/templates *\nrecursive-include el_pagination/templatetags *.py\n\n# Exclude cache files and compiled python files\nrecursive-exclude * __pycache__\nrecursive-exclude * *.py[cod]\nrecursive-exclude * *.so\nrecursive-exclude * .*.swp\nrecursive-exclude * .DS_Store\n\n# Exclude version control\nprune .git\nprune .github\nprune .gitignore"
  },
  {
    "path": "Makefile",
    "content": "# Django Endless Pagination Makefile.\n\n# Define these variables based on the system Python versions.\nPYTHON ?= python3\n\nVENV = .venv\nWITH_VENV = ./tests/with_venv.sh $(VENV)\nMANAGE = $(PYTHON) ./tests/manage.py\nLINTER = flake8 --show-source el_pagination/ tests/\nDOC_INDEX = doc/_build/html/index.html\n\n.PHONY: all clean cleanall check develop help install lint doc opendoc release server shell source test\n\nall: develop\n\n# Virtual environment\n$(VENV)/bin/activate: tests/develop.py tests/requirements.pip\n\t@$(PYTHON) tests/develop.py\n\t@touch $(VENV)/bin/activate\n\t$(VENV)/bin/pip install --upgrade pip setuptools wheel\n\t$(VENV)/bin/pip install -r tests/requirements.pip\n\ndevelop: $(VENV)/bin/activate\n\t$(VENV)/bin/pip install -e .\n\n# Documentation\n$(DOC_INDEX): $(wildcard doc/*.rst)\n\t@$(WITH_VENV) make -C doc html\n\ndoc: develop $(DOC_INDEX)\n\nclean:\n\tpip uninstall django-el-pagination -y || true\n\trm -rf .coverage build/ dist/ doc/_build MANIFEST *.egg-info\n\tfind . -name '*.pyc' -delete\n\tfind . -name '__pycache__' -type d -delete\n\ncleanall: clean\n\trm -rf $(VENV)\n\ncheck: test lint\n\ninstall:\n\tpip install --force-reinstall -e .\n\nlint: develop\n\t@$(WITH_VENV) $(LINTER)\n\nblack: develop\n\t@echo \"***  Black - Reformat pycode ***\"\n\t@echo \"\"\n\t@$(WITH_VENV) black el_pagination/ tests/\n\nblack_diff: develop\n\t@echo \"*** Black - Show pycode diff ***\"\n\t@echo \"\"\n\tblack --diff --check --color el_pagination/ tests/\n\npylint: develop\n\t@echo \"Running pylint\"\n\tpylint --rcfile=.pylintrc el_pagination/ tests/\n\t@echo \"Finish pylint\"\n\nopendoc: doc\n\t@firefox $(DOC_INDEX)\n\nserver: develop\n\t@$(WITH_VENV) $(MANAGE) runserver 0.0.0.0:8000\n\nshell: develop\n\t@$(WITH_VENV) $(MANAGE) shell\n\nsource:\n\t$(PYTHON) setup.py sdist\n\ntest: develop\n\t@$(WITH_VENV) $(MANAGE) test\n\nbuild-dist: clean develop\n\t@echo \"Installing build dependencies...\"\n\t$(VENV)/bin/pip install build twine\n\t@echo \"Building distribution...\"\n\t$(VENV)/bin/python -m build\n\ncheck-dist: build-dist\n\t@echo \"Checking distribution...\"\n\t$(VENV)/bin/twine check dist/*\n\nupload-dist: check-dist\n\t@echo \"Uploading to PyPI...\"\n\t$(VENV)/bin/twine upload dist/*\n\nrelease: clean develop\n\t@echo \"Starting release process...\"\n\t@if [ -z \"$$SKIP_CONFIRMATION\" ]; then \\\n\t\tread -p \"Are you sure you want to release to PyPI? [y/N] \" confirm; \\\n\t\tif [ \"$$confirm\" != \"y\" ]; then \\\n\t\t\techo \"Release cancelled.\"; \\\n\t\t\texit 1; \\\n\t\tfi \\\n\tfi\n\t$(MAKE) build-dist\n\t$(MAKE) check-dist\n\t@echo \"Ready to upload to PyPI...\"\n\t@if [ -z \"$$SKIP_CONFIRMATION\" ]; then \\\n\t\tread -p \"Proceed with upload? [y/N] \" confirm; \\\n\t\tif [ \"$$confirm\" != \"y\" ]; then \\\n\t\t\techo \"Upload cancelled.\"; \\\n\t\t\texit 1; \\\n\t\tfi \\\n\tfi\n\t$(MAKE) upload-dist\n\t@echo \"Release completed successfully!\"\n\n\nhelp:\n\t@echo 'Django Endless Pagination - Available commands:'\n\t@echo\n\t@echo 'Development:'\n\t@echo '  make          - Set up development environment'\n\t@echo '  make install  - Install package locally'\n\t@echo '  make server   - Run development server'\n\t@echo '  make shell    - Enter Django shell'\n\t@echo\n\t@echo 'Testing:'\n\t@echo '  make test     - Run tests'\n\t@echo '  make lint     - Run code linting'\n\t@echo '  make check    - Run tests and linting'\n\t@echo\n\t@echo 'Documentation:'\n\t@echo '  make doc      - Build documentation'\n\t@echo '  make opendoc  - Build and open documentation'\n\t@echo\n\t@echo 'Cleaning:'\n\t@echo '  make clean    - Remove build artifacts'\n\t@echo '  make cleanall - Remove all generated files including venv'\n\t@echo\n\t@echo 'Distribution:'\n\t@echo '  make source   - Create source package'\n\t@echo '  make release  - Upload to PyPI'\n\t@echo\n\t@echo 'Environment Variables:'\n\t@echo '  USE_SELENIUM=1    - Include integration tests'\n\t@echo '  SHOW_BROWSER=1    - Show browser during Selenium tests'\n\t@echo\n"
  },
  {
    "path": "PKG-INFO",
    "content": "Metadata-Version: 1.2\nName: django-el-pagination\nVersion: 4.2.0\nSummary: Django pagination tools supporting Ajax, multiple and lazy pagination, Twitter-style and Digg-style pagination.\nHome-page: https://github.com/shtalinberg/django-el-pagination\nAuthor: Oleksandr Shtalinberg\nAuthor-email: O.Shtalinberg@gmail.com\nLicense: MIT\nDescription: Django EL(Endless) Pagination can be used to provide Twitter-style or\n        Digg-style pagination, with optional Ajax support and other features\n        like multiple or lazy pagination.\n\n        The initial idea, which has guided the development of this application,\n        is to allow pagination of web contents in `very few steps\n        <http://django-el-pagination.readthedocs.org/en/latest/start.html>`_.\n\n        **Documentation** is `avaliable online\n        <http://django-el-pagination.readthedocs.org/>`_, or in the docs\n        directory of the project.\n\n        To file **bugs and requests**, please use\n        https://github.com/shtalinberg/django-el-pagination/issues.\n\n        The **source code** for this app is hosted at\n        https://github.com/shtalinberg/django-el-pagination.\n\nKeywords: django pagination ajax\nPlatform: UNKNOWN\nClassifier: Development Status :: 5 - Production/Stable\nClassifier: Environment :: Web Environment\nClassifier: Framework :: Django\nClassifier: Intended Audience :: Developers\nClassifier: License :: OSI Approved :: MIT License\nClassifier: Operating System :: OS Independent\nClassifier: Programming Language :: Python\nClassifier: Programming Language :: Python :: 3\nClassifier: Topic :: Utilities\n"
  },
  {
    "path": "README.rst",
    "content": "=============================\nDjango EL(Endless) Pagination\n=============================\n\n|  |pypi-pkg-version| |python-versions| |django-versions|  |pypi-status| |docs|\n|  |build-ci-status| |tox-ci-status| |codecov|\n\nDjango EL(Endless) Pagination can be used to provide Twitter-style or\nDigg-style pagination, with optional Ajax support and other features\nlike multiple or lazy pagination.\n\nThis app **django-el-pagination** forked from django-endless-pagination==2.0 (https://github.com/frankban/django-endless-pagination)\n\nFrom version 4.0.0 drop support Django<3.2. For support Django<3.2 use django-endless-pagination<4.0.x\n\nFrom version 4.1.2 added support Django 5.0 and python 3.12\n\nThe initial idea, which has guided the development of this application,\nis to allow pagination of web contents in `very few steps\n<http://django-el-pagination.readthedocs.org/en/latest/start.html>`_.\n\n**Documentation** is `available online\n<http://django-el-pagination.readthedocs.org/>`_, or in the doc\ndirectory of the project.\n\nTo file **bugs and requests**, please use\nhttps://github.com/shtalinberg/django-el-pagination/issues.\n\nThe **source code** for this app is hosted at\nhttps://github.com/shtalinberg/django-el-pagination.\n\nPull requests are welcome. See `Contributing Guide\n<http://django-el-pagination.readthedocs.io/en/latest/contributing.html>`_.\n\n.. |build-ci-status| image:: https://github.com/shtalinberg/django-el-pagination/actions/workflows/tox.yml/badge.svg?branch=master\n   :target: https://github.com/shtalinberg/django-el-pagination/actions/workflows/tox.yml\n   :alt: Build release status\n.. |docs| image:: https://readthedocs.org/projects/django-el-pagination/badge/?version=latest\n    :target: https://django-el-pagination.readthedocs.io/en/latest/?badge=latest\n    :alt: Documentation Status\n.. |pypi-pkg-version| image:: https://img.shields.io/pypi/v/django-el-pagination.svg\n   :target:  https://pypi.python.org/pypi/django-el-pagination/\n.. |pypi-status| image:: https://img.shields.io/pypi/status/coverage.svg\n    :target: https://pypi.python.org/pypi/django-el-pagination/\n.. |python-versions| image:: https://img.shields.io/pypi/pyversions/django-el-pagination.svg\n.. |django-versions| image:: https://img.shields.io/pypi/djversions/django-el-pagination.svg\n.. |codecov| image:: https://codecov.io/gh/shtalinberg/django-el-pagination/branch/master/graph/badge.svg\n  :target: https://codecov.io/gh/shtalinberg/django-el-pagination\n  :alt: Code coverage\n.. |tox-ci-status| image:: https://github.com/shtalinberg/django-el-pagination/actions/workflows/tox.yml/badge.svg?branch=develop\n   :target: https://github.com/shtalinberg/django-el-pagination/actions/workflows/tox.yml\n   :alt: Tox develop status\n"
  },
  {
    "path": "doc/Makefile",
    "content": "# Makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line.\nSPHINXOPTS    =\nSPHINXBUILD   = sphinx-build\nPAPER         =\nBUILDDIR      = _build\n\n# Internal variables.\nPAPEROPT_a4     = -D latex_paper_size=a4\nPAPEROPT_letter = -D latex_paper_size=letter\nALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .\n\n.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest\n\nhelp:\n\t@echo \"Please use \\`make <target>' where <target> is one of\"\n\t@echo \"  html       to make standalone HTML files\"\n\t@echo \"  dirhtml    to make HTML files named index.html in directories\"\n\t@echo \"  singlehtml to make a single large HTML file\"\n\t@echo \"  pickle     to make pickle files\"\n\t@echo \"  json       to make JSON files\"\n\t@echo \"  htmlhelp   to make HTML files and a HTML help project\"\n\t@echo \"  qthelp     to make HTML files and a qthelp project\"\n\t@echo \"  devhelp    to make HTML files and a Devhelp project\"\n\t@echo \"  epub       to make an epub\"\n\t@echo \"  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter\"\n\t@echo \"  latexpdf   to make LaTeX files and run them through pdflatex\"\n\t@echo \"  text       to make text files\"\n\t@echo \"  man        to make manual pages\"\n\t@echo \"  changes    to make an overview of all changed/added/deprecated items\"\n\t@echo \"  linkcheck  to check all external links for integrity\"\n\t@echo \"  doctest    to run all doctests embedded in the documentation (if enabled)\"\n\nclean:\n\t-rm -rf $(BUILDDIR)/*\n\nhtml:\n\t$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html\n\t@echo\n\t@echo \"Build finished. The HTML pages are in $(BUILDDIR)/html.\"\n\ndirhtml:\n\t$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml\n\t@echo\n\t@echo \"Build finished. The HTML pages are in $(BUILDDIR)/dirhtml.\"\n\nsinglehtml:\n\t$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml\n\t@echo\n\t@echo \"Build finished. The HTML page is in $(BUILDDIR)/singlehtml.\"\n\npickle:\n\t$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle\n\t@echo\n\t@echo \"Build finished; now you can process the pickle files.\"\n\njson:\n\t$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json\n\t@echo\n\t@echo \"Build finished; now you can process the JSON files.\"\n\nhtmlhelp:\n\t$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp\n\t@echo\n\t@echo \"Build finished; now you can run HTML Help Workshop with the\" \\\n\t      \".hhp project file in $(BUILDDIR)/htmlhelp.\"\n\nqthelp:\n\t$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp\n\t@echo\n\t@echo \"Build finished; now you can run \"qcollectiongenerator\" with the\" \\\n\t      \".qhcp project file in $(BUILDDIR)/qthelp, like this:\"\n\t@echo \"# qcollectiongenerator $(BUILDDIR)/qthelp/DjangoEndlessPagination.qhcp\"\n\t@echo \"To view the help file:\"\n\t@echo \"# assistant -collectionFile $(BUILDDIR)/qthelp/DjangoEndlessPagination.qhc\"\n\ndevhelp:\n\t$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp\n\t@echo\n\t@echo \"Build finished.\"\n\t@echo \"To view the help file:\"\n\t@echo \"# mkdir -p $$HOME/.local/share/devhelp/DjangoEndlessPagination\"\n\t@echo \"# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/DjangoEndlessPagination\"\n\t@echo \"# devhelp\"\n\nepub:\n\t$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub\n\t@echo\n\t@echo \"Build finished. The epub file is in $(BUILDDIR)/epub.\"\n\nlatex:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo\n\t@echo \"Build finished; the LaTeX files are in $(BUILDDIR)/latex.\"\n\t@echo \"Run \\`make' in that directory to run these through (pdf)latex\" \\\n\t      \"(use \\`make latexpdf' here to do that automatically).\"\n\nlatexpdf:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo \"Running LaTeX files through pdflatex...\"\n\tmake -C $(BUILDDIR)/latex all-pdf\n\t@echo \"pdflatex finished; the PDF files are in $(BUILDDIR)/latex.\"\n\ntext:\n\t$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text\n\t@echo\n\t@echo \"Build finished. The text files are in $(BUILDDIR)/text.\"\n\nman:\n\t$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man\n\t@echo\n\t@echo \"Build finished. The manual pages are in $(BUILDDIR)/man.\"\n\nchanges:\n\t$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes\n\t@echo\n\t@echo \"The overview file is in $(BUILDDIR)/changes.\"\n\nlinkcheck:\n\t$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck\n\t@echo\n\t@echo \"Link check complete; look for any errors in the above output \" \\\n\t      \"or in $(BUILDDIR)/linkcheck/output.txt.\"\n\ndoctest:\n\t$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest\n\t@echo \"Testing of doctests in the sources finished, look at the \" \\\n\t      \"results in $(BUILDDIR)/doctest/output.txt.\"\n"
  },
  {
    "path": "doc/_static/TRACKME",
    "content": "Placeholder file to let Hg include this directory.\n"
  },
  {
    "path": "doc/changelog.rst",
    "content": "Changelog\n=========\n\nUnreleased\n~~~~~~~~~~\n\n**New feature**: Django 5.2.x support.\n    Django EL(Endless) Pagination now supports Django from 4.2.x to 5.2.x\n\nVersion 4.2.0\n~~~~~~~~~~~~~\n\n**New feature**: Django 5.1.x support.\n    Django EL(Endless) Pagination now supports Django from 4.2.x to 5.1.x\n    supports Python 3.8, 3.9, 3.10, 3.11, 3.12, 3.13 (only 5.1.x)\n\n**Source code validation**:\n    add black formatting rules\n    add pylint checks\n\n\nVersion 4.1.2\n~~~~~~~~~~~~~\n\n**Fix**:  in 4.1.1 problem with loading template_tag\n\n\nVersion 4.1.1\n~~~~~~~~~~~~~\n\n**Fix**:  fixed readthedocs documentation\n\n\nVersion 4.1.0\n~~~~~~~~~~~~~\n\n**New feature**: Django 4.2.x, 5.x support.\n    Django EL(Endless) Pagination now supports Django 3.2.x, 4.2.x and 5.0\n    supports Python\n    Django Python versions\n    3.2\t3.8, 3.9, 3.10 (added in 3.2.9)\n    4.2\t3.8, 3.9, 3.10, 3.11, 3.12 (added in 4.2.8)\n    5.0\t3.10, 3.11, 3.12\n\n\nVersion 4.0.0\n~~~~~~~~~~~~~\n\n**New feature**: Django 4.1.x support.\n    Django EL(Endless) Pagination now supports Django from 3.2.x to 4.1.x\n    supports Python 3.8, 3.9, 3.10\n\n\nVersion 3.3.0\n~~~~~~~~~~~~~\n\n**New feature**: Django 3.0.x support.\n    Django EL(Endless) Pagination now supports Django from 1.11.x to 3.0.x\n    Dropped support for Python 2.x\n\n\nVersion 3.2.4\n~~~~~~~~~~~~~\n\n**Fix**:  compatible with jQuery 3.x\n\n\nVersion 3.2.3\n~~~~~~~~~~~~~\nBug-fix release\n\n**Fix**: cycle in show_pages with django 2.0\nfix tests for PageList.get_rendered()\n\n\nVersion 3.2.2\n~~~~~~~~~~~~~\nBug-fix release\n\n**Fix**: fix UnicodeEncodeError with translate in templates\n\n\nVersion 3.2.0\n~~~~~~~~~~~~~\n**New feature**: Django 2.0.x support.\n    Django EL(Endless) Pagination now supports Django from 1.8.x to 2.0.x\n\n\n**New feature**: settings.USE_NEXT_PREVIOUS_LINKS: default=False\n    if True:\n    Add `is_previous` & `is_next` flags for `previous` and `next` pages\n    Add `next_link.html` & `previous_link.html` templates\n\n\n**New feature**:  `__unicode__` is removed from class ELPage\n    It's Fix Causes Fatal Python error with django-debug-toolbar\n    In templates:\n    - {{ page }} now use as {{ page.render_link }}\n    - {{ pages }} now use as {{ pages.get_rendered }}\n\n\n**Template changes**:\n    show_pages.html:\n    `page|default_if_none` replaced `page.render_link|default`\n\n----\n\n**Cleanup**:\nutils.UnicodeMixin\nutils.text\n\n\nVersion 3.1.0\n~~~~~~~~~~~~~\n**Template changes**:\n    link attribute rel=\"{{ querystring_key }}\"  replaced by data-el-querystring-key=\"{{ querystring_key }}\"\n\n**New feature**: Django 1.11 support.\n\n**New feature**:\n    added view for maintaining original functionality on page index out of range, but setting response code to 404\n    ``PAGE_OUT_OF_RANGE_404`` default *False* If True on page out of range, throw a 404 exception, otherwise display the first page\n\n**Documentation**: render_to_response deprecated in django 1.10\n    replaced to ``return render(request, template, context)``\n\n\nVersion 3.0.0\n~~~~~~~~~~~~~\n\n**New feature**: Django 1.10 support.\nNew app Django EL(Endless) Pagination now supports Django from 1.8.x to 1.10\n\n----\n\n**New feature**: Travic CI support\nadd tox and Travic CI config\n\n----\n\n**Documentation**: general clean up.\n\n\nVersion 2.1.1\n~~~~~~~~~~~~~\n\nBug-fix release\n\n**Fix**: page_template decorator doesn't change template of ajax call\n\n----\n\n**Fix**: Fix syntax error in declaring variable in javascript\n\n\nVersion 2.1.0\n~~~~~~~~~~~~~\n\nNew name app: django-el-pagination\n\n**New feature**: Django 1.8 and 1.9 support.\nNew app Django EL(Endless) Pagination now supports Django from 1.4.x to 1.9\n\nnew jQuery plugin that can be found in\n``static/el-pagination/js/el-pagination.js``.\n\nSupport get the numbers of objects are normally display in per page\n\nUsage:\n\n.. code-block:: html+django\n\n\t{{ pages.per_page_number }}\n\nadd a class on chunk complete\n\nEach time a chunk size is complete, the class ``endless_chunk_complete`` is added to the *show more* link,\n\n\nVersion 2.0\n~~~~~~~~~~~\n\n**New feature**: Python 3 support.\n\nDjango Endless Pagination now supports both Python 2 and **Python 3**. Dropped\nsupport for Python 2.5. See :doc:`start` for the new list of requirements.\n\n----\n\n**New feature**: the **JavaScript refactoring**.\n\nThis version introduces a re-designed Ajax support for pagination. Ajax can\nnow be enabled using a brand new jQuery plugin that can be found in\n``static/el-pagination/js/el-pagination.js``.\n\nUsage:\n\n.. code-block:: html+django\n\n    {% block js %}\n        {{ block.super }}\n        <script src=\"http://code.jquery.com/jquery-latest.js\"></script>\n        <script src=\"{{ STATIC_URL }}el-pagination/js/el-pagination.js\"></script>\n        <script>$.endlessPaginate();</script>\n    {% endblock %}\n\nThe last line in the block above enables Ajax requests to retrieve new\npages for each pagination in the page. That's basically the same as the old\napproach of loading the file ``endless.js``. The new approach, however,\nis more jQuery-idiomatic, increases the flexibility of how objects can be\npaginated, implements some :doc:`new features </javascript>` and also contains\nsome bug fixes.\n\nFor backward compatibility, the application still includes the two JavaScript\n``endless.js`` and ``endless_on_scroll.js`` files. However, please consider\n:ref:`migrating<javascript-migrate>` as soon as possible: the old JavaScript\nfiles are deprecated, are no longer maintained, and don't provide the new\nJavaScript features. Also note that the old Javascript files will not work if\njQuery >= 1.9 is used.\n\nNew features include ability to **paginate different objects with different\noptions**, precisely **selecting what to bind**, ability to **register\ncallbacks**, support for **pagination in chunks** and much more.\n\nPlease refer to the :doc:`javascript` for a detailed overview of the new\nfeatures and for instructions on :ref:`how to migrate<javascript-migrate>` from\nthe old JavaScript files to the new one.\n\n----\n\n**New feature**: the :ref:`page_templates<multiple-page-templates>` decorator\nalso accepts a sequence of ``(template, key)`` pairs, functioning as a dict\nmapping templates and keys (still present), e.g.::\n\n    from endless_pagination.decorators import page_templates\n\n    @page_templates((\n        ('myapp/entries_page.html', None),\n        ('myapp/other_entries_page.html', 'other_entries_page'),\n    ))\n    def entry_index():\n        ...\n\nThis also supports serving different paginated objects with the same template.\n\n----\n\n**New feature**: ability to provide nested context variables in the\n:ref:`templatetags-paginate` and :ref:`templatetags-lazy-paginate` template\ntags, e.g.:\n\n.. code-block:: html+django\n\n    {% paginate entries.all as myentries %}\n\nThe code above is basically equivalent to:\n\n.. code-block:: html+django\n\n    {% with entries.all as myentries %}\n        {% paginate myentries %}\n    {% endwith %}\n\nIn this case, and only in this case, the `as` argument is mandatory, and a\n*TemplateSyntaxError* will be raised if the variable name is missing.\n\n----\n\n**New feature**: the page list object returned by the\n:ref:`templatetags-get-pages` template tag has been improved adding the\nfollowing new methods:\n\n.. code-block:: html+django\n\n    {# whether the page list contains more than one page #}\n    {{ pages.paginated }}\n\n    {# the 1-based index of the first item on the current page #}\n    {{ pages.current_start_index }}\n\n    {# the 1-based index of the last item on the current page #}\n    {{ pages.current_end_index }}\n\n    {# the total number of objects, across all pages #}\n    {{ pages.total_count }}\n\n    {# the first page represented as an arrow #}\n    {{ pages.first_as_arrow }}\n\n    {# the last page represented as an arrow #}\n    {{ pages.last_as_arrow }}\n\nIn the *arrow* representation, the page label defaults to ``<<`` for the first\npage and to ``>>`` for the last one. As a consequence, the labels of the\nprevious and next pages are now single brackets, respectively ``<`` and ``>``.\nFirst and last pages' labels can be customized using\n``settings.ENDLESS_PAGINATION_FIRST_LABEL`` and\n``settings.ENDLESS_PAGINATION_LAST_LABEL``: see :doc:`customization`.\n\n----\n\n**New feature**: The sequence returned by the callable\n``settings.ENDLESS_PAGINATION_PAGE_LIST_CALLABLE`` can now contain two new\nvalues:\n\n- *'first'*: will display the first page as an arrow;\n- *'last'*: will display the last page as an arrow.\n\nThe :ref:`templatetags-show-pages` template tag documentation describes how to\ncustomize Digg-style pagination defining your own page list callable.\n\nWhen using the default Digg-style pagination (i.e. when\n``settings.ENDLESS_PAGINATION_PAGE_LIST_CALLABLE`` is set to *None*), it is\npossible to enable first / last page arrows by setting the new flag\n``settings.ENDLESS_PAGINATION_DEFAULT_CALLABLE_ARROWS`` to *True*.\n\n----\n\n**New feature**: ``settings.ENDLESS_PAGINATION_PAGE_LIST_CALLABLE`` can now be\neither a callable or a **dotted path** to a callable, e.g.::\n\n    ENDLESS_PAGINATION_PAGE_LIST_CALLABLE = 'path.to.callable'\n\nIn addition to the default, ``endless_pagination.utils.get_page_numbers``, an\nalternative implementation is now available:\n``endless_pagination.utils.get_elastic_page_numbers``. It adapts its output\nto the number of pages, making it arguably more usable when there are many\nof them. To enable it, add the following line to your ``settings.py``::\n\n    ENDLESS_PAGINATION_PAGE_LIST_CALLABLE = (\n        'endless_pagination.utils.get_elastic_page_numbers')\n\n----\n\n**New feature**: ability to create a development and testing environment\n(see :doc:`contributing`).\n\n----\n\n**New feature**: in addition to the ability to provide a customized pagination\nURL as a context variable, the :ref:`templatetags-paginate` and\n:ref:`templatetags-lazy-paginate` tags now support hardcoded pagination URL\nendpoints, e.g.:\n\n.. code-block:: html+django\n\n    {% paginate 20 entries with \"/mypage/\" %}\n\n----\n\n**New feature**: ability to specify negative indexes as values for the\n``starting from page`` argument of the :ref:`templatetags-paginate` template\ntag.\n\nWhen changing the default page, it is now possible to reference the last page\n(or the second last page, and so on) by using negative indexes, e.g:\n\n.. code-block:: html+django\n\n    {% paginate entries starting from page -1 %}\n\nSee :doc:`templatetags_reference`.\n\n----\n\n**Documentation**: general clean up.\n\n----\n\n**Documentation**: added a :doc:`contributing` page. Have a look!\n\n----\n\n**Documentation**: included a comprehensive :doc:`javascript`.\n\n----\n\n**Fix**: ``endless_pagination.views.AjaxListView`` no longer subclasses\n``django.views.generic.list.ListView``. Instead, the base objects and\nmixins composing the final view are now defined by this app.\n\nThis change eliminates the ambiguity of having two separate pagination\nmachineries in place: the Django Endless Pagination one and the built-in\nDjango ``ListView`` one.\n\n----\n\n**Fix**: the *using* argument of :ref:`templatetags-paginate` and\n:ref:`templatetags-lazy-paginate` template tags now correctly handles\nquerystring keys containing dashes, e.g.:\n\n.. code-block:: html+django\n\n    {% lazy_paginate entries using \"entries-page\" %}\n\n----\n\n**Fix**: replaced namespace ``endless_pagination.paginator`` with\n``endless_pagination.paginators``: the module contains more than one\npaginator classes.\n\n----\n\n**Fix**: in some corner cases, loading ``endless_pagination.models`` raised\nan *ImproperlyConfigured* error while trying to pre-load the templates.\n\n----\n\n**Fix**: replaced doctests with proper unittests. Improved the code coverage\nas a consequence. Also introduced integration tests exercising JavaScript,\nbased on Selenium.\n\n----\n\n**Fix**: overall code lint and clean up.\n\n\nVersion 1.1\n~~~~~~~~~~~\n\n**New feature**: now it is possible to set the bottom margin used for\npagination on scroll (default is 1 pixel).\n\nFor example, if you want the pagination on scroll to be activated when\n20 pixels remain until the end of the page:\n\n.. code-block:: html+django\n\n    <script src=\"http://code.jquery.com/jquery-latest.js\"></script>\n    <script src=\"{{ STATIC_URL }}endless_pagination/js/endless.js\"></script>\n    <script src=\"{{ STATIC_URL }}endless_pagination/js/endless_on_scroll.js\"></script>\n\n    {# add the lines below #}\n    <script type=\"text/javascript\" charset=\"utf-8\">\n        var endless_on_scroll_margin = 20;\n    </script>\n\n----\n\n**New feature**: added ability to avoid Ajax requests when multiple pagination\nis used.\n\nA template for multiple pagination with Ajax support may look like this\n(see :doc:`multiple_pagination`):\n\n.. code-block:: html+django\n\n    {% block js %}\n        {{ block.super }}\n        <script src=\"http://code.jquery.com/jquery-latest.js\"></script>\n        <script src=\"{{ STATIC_URL }}endless_pagination/js/endless.js\"></script>\n    {% endblock %}\n\n    <h2>Entries:</h2>\n    <div class=\"endless_page_template\">\n        {% include \"myapp/entries_page.html\" %}\n    </div>\n\n    <h2>Other entries:</h2>\n    <div class=\"endless_page_template\">\n        {% include \"myapp/other_entries_page.html\" %}\n    </div>\n\nBut what if you need Ajax pagination for *entries* but not for *other entries*?\nYou will only have to add a class named ``endless_page_skip`` to the\npage container element, e.g.:\n\n.. code-block:: html+django\n\n    <h2>Other entries:</h2>\n    <div class=\"endless_page_template endless_page_skip\">\n        {% include \"myapp/other_entries_page.html\" %}\n    </div>\n\n----\n\n**New feature**: implemented a class-based generic view allowing\nAjax pagination of a list of objects (usually a queryset).\n\nIntended as a substitution of *django.views.generic.ListView*, it recreates\nthe behaviour of the *page_template* decorator.\n\nFor a complete explanation, see :doc:`generic_views`.\n\n----\n\n**Fix**: the ``page_template`` and ``page_templates`` decorators no longer\nhide the original view name and docstring (*update_wrapper*).\n\n----\n\n**Fix**: pagination on scroll now works on Firefox >= 4.\n\n----\n\n**Fix**: tests are now compatible with Django 1.3.\n"
  },
  {
    "path": "doc/conf.py",
    "content": "\"\"\"Django EL(Endless) Pagination documentation build configuration file.\"\"\"\n\nimport os\nimport sys\n\nsys.path.insert(0, os.path.abspath('..'))\n\n\nAUTHOR = 'Francesco Banconi, Oleksandr Shtalinberg'\nAPP = 'Django EL(Endless) Pagination'\nTITLE = APP + ' Documentation'\nVERSION = '4.2.0'\n\n# General information about the project.\nproject = APP\ncopyright = '2009-2024, ' + AUTHOR\nauthor = 'Oleksandr Shtalinberg'\n\n\n# Add any Sphinx extension module names here, as strings. They can be\n# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.\nextensions = [\n    'sphinx.ext.autodoc',\n    'sphinx.ext.intersphinx',\n    'sphinx.ext.viewcode',\n]\n\n# Add any paths that contain templates here, relative to this directory.\ntemplates_path = ['_templates']\nexclude_patterns = [\n    '_build',\n    'Thumbs.db',\n    '.DS_Store',\n    '_static/TRACKME',  # Exclude the TRACKME file from processing\n]\n\n# The suffix of source filenames.\nsource_suffix = '.rst'\n\n# The master toctree document.\nmaster_doc = 'index'\n\n# The short X.Y version.\nversion = release = VERSION\n\n# List of patterns, relative to source directory, that match files and\n# directories to ignore when looking for source files.\nexclude_patterns = ['_build']\n\n# The name of the Pygments (syntax highlighting) style to use.\npygments_style = 'sphinx'\n\n# The theme to use for HTML and HTML Help pages.  See the documentation for\n# a list of builtin themes.\nhtml_theme = 'sphinx_rtd_theme'\nhtml_theme_options = {\n    'navigation_depth': 4,\n    'collapse_navigation': False,\n    'sticky_navigation': True,\n    'includehidden': True,\n    'titles_only': False,\n}\n\n# Add any paths that contain custom static files (such as style sheets) here,\n# relative to this directory. They are copied after the builtin static files,\n# so a file named \"default.css\" will overwrite the builtin \"default.css\".\nhtml_static_path = ['_static']\nepub_exclude_files = [\n    '_static/TRACKME',\n    'search.html',\n    '_static/websupport.js',\n]\n# -- Epub options ---------------------------------------------------------\nepub_show_urls = 'footnote'\nepub_tocdepth = 3\nepub_tocdup = True\nepub_guide = (('toc', 'index.html', 'Table of Contents'),)\n\n# Output file base name for HTML help builder.\nhtmlhelp_basename = 'DjangoELPaginationdoc'\n\n# Grouping the document tree into LaTeX files. List of tuples (source start\n# file, target name, title, author, documentclass [howto/manual]).\nlatex_documents = [(\n    'index', 'DjangoELPagination.tex', TITLE, AUTHOR, 'manual')]\n\n# One entry per manual page. List of tuples\n# (source start file, name, description, authors, manual section).\nman_pages = [('index', 'djangoelpagination', TITLE, [AUTHOR], 1)]\n\n# Intersphinx configuration\nintersphinx_mapping = {\n    'python': ('https://docs.python.org/3', None),\n    'django': (\n        'https://docs.djangoproject.com/en/stable/',\n        'https://docs.djangoproject.com/en/stable/_objects/',\n    ),\n}\n"
  },
  {
    "path": "doc/contacts.rst",
    "content": "Source code and contacts\n========================\n\nRepository and bugs\n~~~~~~~~~~~~~~~~~~~\n\nThe **source code** for this app is hosted on\nhttps://github.com/shtalinberg/django-el-pagination.\n\nTo file **bugs and requests**, please use\nhttps://github.com/shtalinberg/django-el-pagination/issues.\n\nContacts\n~~~~~~~~\n\nOleksandr Shtalinberg\n\n- Email: ``o.shtalinberg at gmail.com``\n\nFrancesco Banconi\n\n- Email: ``frankban at gmail.com``\n- IRC: ``frankban@freenode``\n"
  },
  {
    "path": "doc/contributing.rst",
    "content": "Contributing\n============\n\n\nHere are the steps needed to set up a development and testing environment.\n\n**WARNING**\n\nThis app use *git flow* for branching strategy and release management.\n\nPlease, change code and submit all pull requests into branch `develop`\n\nCreating a development environment\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThe development environment is created in a venv. The environment\ncreation requires the *make* program to be installed.\n\nTo install *make* under Debian/Ubuntu::\n\n    $ sudo apt-get install build-essential\n\nUnder Mac OS/X, *make* is available as part of XCode.\n\nAt this point, from the root of this branch, run the command::\n\n    $ make\n\nThis command will create a ``.venv`` directory in the branch root, ignored\nby DVCSes, containing the development virtual environment with all the\ndependencies.\n\nTesting the application\n~~~~~~~~~~~~~~~~~~~~~~~\n\nTo install *xvfb* (for integration tests) under Debian/Ubuntu::\n\n    $ sudo apt-get install xvfb\n\nIf you are on CentOS and using yum, it's::\n\n\t$ yum install xorg-X11-server-Xvfb\n\nRun the tests::\n\n    $ make test\n\nThe command above also runs all tests except the available integration. They use\nSelenium and require Firefox to be installed. To include executing integration\ntests, define the environment variable USE_SELENIUM, e.g.::\n\n    $ make test USE_SELENIUM=1\n\nIntegration tests are excluded by default when using Python 3. The test suite\nrequires Python >= 3.8.x.\n\nRun the tests and lint/pep8 checks::\n\n    $ make check\n\nAgain, to exclude integration tests::\n\n    $ make check USE_SELENIUM=1\n\nDebugging\n~~~~~~~~~\n\nRun the Django shell (Python interpreter)::\n\n    $ make shell\n\nRun the Django development server for manual testing::\n\n    $ make server\n\nAfter executing the command above, it is possible to navigate the testing\nproject going to <http://localhost:8000>.\n\nSee all the available make targets, including info on how to create a Python 3\ndevelopment environment::\n\n    $ make help\n\n\n\nThanks for contributing, and have fun!\n\n\n"
  },
  {
    "path": "doc/current_page_number.rst",
    "content": "Getting the current page number\n===============================\n\nIn the template\n~~~~~~~~~~~~~~~\n\nYou can get and display the current page number in the template using\nthe :ref:`templatetags-show-current-number` template tag, e.g.:\n\n.. code-block:: html+django\n\n    {% show_current_number %}\n\nThis call will display the current page number, but you can also\ninsert the value in the context as a template variable:\n\n.. code-block:: html+django\n\n\n    {% show_current_number as page_number %}\n    {{ page_number }}\n\nSee the :ref:`templatetags-show-current-number` refrence for more information\non accepted arguments.\n\nIn the view\n~~~~~~~~~~~\n\nIf you need to get the current page number in the view, you can use an utility\nfunction called ``get_page_number_from_request``, e.g.::\n\n    from el_pagination import utils\n\n    page = utils.get_page_number_from_request(request)\n\nIf you are using :doc:`multiple pagination<multiple_pagination>`, or you have\nchanged the default querystring for pagination, you can pass the querystring\nkey as an optional argument::\n\n    page = utils.get_page_number_from_request(request, querystring_key=mykey)\n\nIf the page number is not present in the request, by default *1* is returned.\nYou can change this behaviour using::\n\n    page = utils.get_page_number_from_request(request, default=3)\n\n"
  },
  {
    "path": "doc/customization.rst",
    "content": "Customization\n=============\n\nSettings\n~~~~~~~~\n\nYou can customize the application using ``settings.py``.\n\n================================================= =========== ==============================================\nName                                              Default     Description\n================================================= =========== ==============================================\n``EL_PAGINATION_PER_PAGE``                        10          How many objects are normally displayed\n                                                              in a page (overwriteable by templatetag).\n------------------------------------------------- ----------- ----------------------------------------------\n``EL_PAGINATION_PAGE_LABEL``                      'page'      The querystring key of the page number\n                                                              (e.g. ``http://example.com?page=2``).\n------------------------------------------------- ----------- ----------------------------------------------\n``EL_PAGINATION_ORPHANS``                         0           See Django *Paginator* definition of orphans.\n------------------------------------------------- ----------- ----------------------------------------------\n``EL_PAGINATION_LOADING``                         'loading'   If you use the default ``show_more`` template,\n                                                              here you can customize the content of the\n                                                              loader hidden element. HTML is safe here,\n                                                              e.g. you can show your pretty animated GIF\n                                                              ``EL_PAGINATION_LOADING = \"\"\"<img src=\"/static/img/loader .gif\" alt=\"loading\" />\"\"\"``.\n------------------------------------------------- ----------- ----------------------------------------------\n``EL_PAGINATION_PREVIOUS_LABEL``                  '<'         Default label for the *previous* page link.\n------------------------------------------------- ----------- ----------------------------------------------\n``EL_PAGINATION_NEXT_LABEL``                      '>'         Default label for the *next* page link.\n------------------------------------------------- ----------- ----------------------------------------------\n``EL_PAGINATION_FIRST_LABEL``                     '<<'        Default label for the *first* page link.\n------------------------------------------------- ----------- ----------------------------------------------\n``EL_PAGINATION_LAST_LABEL``                      '>>'        Default label for the *last* page link.\n------------------------------------------------- ----------- ----------------------------------------------\n``EL_PAGINATION_ADD_NOFOLLOW``                    *False*     Set to *True* if your SEO alchemist\n                                                              wants search engines not to follow\n                                                              pagination links.\n------------------------------------------------- ----------- ----------------------------------------------\n``EL_PAGINATION_PAGE_LIST_CALLABLE``              *None*      Callable (or dotted path to a callable) that\n                                                              returns pages to be displayed.\n                                                              If *None*, a default callable is used;\n                                                              that produces :doc:`digg_pagination`.\n                                                              The applicationt provides also a callable\n                                                              producing elastic pagination:\n                                                              ``EL_pagination.utils.get_elastic_page_numbers``.\n                                                              It adapts its output to the number of pages,\n                                                              making it arguably more usable when there are\n                                                              many of them.\n                                                              See :doc:`templatetags_reference` for\n                                                              information about writing custom callables.\n------------------------------------------------- ----------- ----------------------------------------------\n``EL_PAGINATION_DEFAULT_CALLABLE_EXTREMES``       3           Default number of *extremes* displayed when\n                                                              :doc:`digg_pagination` is used with the\n                                                              default callable.\n------------------------------------------------- ----------- ----------------------------------------------\n``EL_PAGINATION_DEFAULT_CALLABLE_AROUNDS``        2           Default number of *arounds* displayed when\n                                                              :doc:`digg_pagination` is used with the\n                                                              default callable.\n------------------------------------------------- ----------- ----------------------------------------------\n``EL_PAGINATION_DEFAULT_CALLABLE_ARROWS``         *False*     Whether or not the first and last pages arrows\n                                                              are displayed when :doc:`digg_pagination` is\n                                                              used with the default callable.\n------------------------------------------------- ----------- ----------------------------------------------\n``EL_PAGINATION_TEMPLATE_VARNAME``                'template'  Template variable name used by the\n                                                              ``page_template`` decorator. You can change\n                                                              this value if you are going to decorate\n                                                              generic views using a different variable name\n                                                              for the template (e.g. ``template_name``).\n------------------------------------------------- ----------- ----------------------------------------------\n``EL_PAGINATION_PAGE_OUT_OF_RANGE_404``           *False*     If True on page out of range, throw a 404\n                                                              exception, otherwise display the first page.\n                                                              There is a view that maintains the original\n                                                              functionality but sets the 404 status code\n                                                              found in el_pagination\\\\views.py\n------------------------------------------------- ----------- ----------------------------------------------\n``EL_PAGINATION_USE_NEXT_PREVIOUS_LINKS``         *False*     Add `is_previous` & `is_next` flags\n                                                              for `previous` and `next` pages\n================================================= =========== ==============================================\n\nTemplates and CSS\n~~~~~~~~~~~~~~~~~\n\nYou can override the default template for ``show_more`` templatetag following\nsome rules:\n\n- *more* link is shown only if the variable ``querystring`` is not False;\n- the container (most external html element) class is *endless_container*;\n- the *more* link and the loader hidden element live inside the container;\n- the *more* link class is *endless_more*;\n- the *more* link data-el-querystring-key attribute is ``{{ querystring_key }}``;\n- the loader hidden element class is *endless_loading*.\n"
  },
  {
    "path": "doc/different_first_page.rst",
    "content": "Different number of items on the first page\n===========================================\n\nSometimes you might want to show on the first page a different number of\nitems than on subsequent pages (e.g. in a movie detail page you want to show\n4 images of the movie as a reminder, making the user click to see the next 20\nimages). To achieve this, use the :ref:`templatetags-paginate` or\n:ref:`templatetags-lazy-paginate` tags with comma separated *first page* and\n*per page* arguments, e.g.:\n\n.. code-block:: html+django\n\n    {% load el_pagination_tags %}\n\n    {% lazy_paginate 4,20 entries %}\n    {% for entry in entries %}\n        {# your code to show the entry #}\n    {% endfor %}\n    {% show_more %}\n\nThis code will display 4 entries on the first page and 20 entries on the other\npages.\n\nOf course the *first page* and *per page* arguments can be passed\nas template variables, e.g.:\n\n.. code-block:: html+django\n\n    {% lazy_paginate first_page,per_page entries %}\n"
  },
  {
    "path": "doc/digg_pagination.rst",
    "content": "Digg-style pagination\n=====================\n\nDigg-style pagination of queryset objects is really easy to implement. If Ajax\npagination is not needed, all you have to do is modifying the template, e.g.:\n\n.. code-block:: html+django\n\n    {% load el_pagination_tags %}\n\n    {% paginate entries %}\n    {% for entry in entries %}\n        {# your code to show the entry #}\n    {% endfor %}\n    {% show_pages %}\n\nThat's it! As seen, the :ref:`templatetags-paginate` template tag takes care of\ncustomizing the given queryset and the current template context. The\n:ref:`templatetags-show-pages` one displays the page links allowing for\nnavigation to other pages.\n\nPage by page\n~~~~~~~~~~~~\n\nIf you only want to display previous and next links (in a page-by-page\npagination) you have to use the lower level :ref:`templatetags-get-pages`\ntemplate tag, e.g.:\n\n.. code-block:: html+django\n\n    {% load el_pagination_tags %}\n\n    {% paginate entries %}\n    {% for entry in entries %}\n        {# your code to show the entry #}\n    {% endfor %}\n    {% get_pages %}\n    {{ pages.previous }} {{ pages.next }}\n\n:doc:`customization` explains how to customize the arrows that go to previous\nand next pages.\n\nShowing indexes\n~~~~~~~~~~~~~~~\n\nThe :ref:`templatetags-get-pages` template tag adds to the current template\ncontext a ``pages`` variable containing several methods that can be used to\nfully customize how the page links are displayed. For example, assume you want\nto show the indexes of the entries in the current page, followed by the total\nnumber of entries:\n\n.. code-block:: html+django\n\n    {% load el_pagination_tags %}\n\n    {% paginate entries %}{% get_pages %}\n    {% for entry in entries %}\n        {# your code to show the entry #}\n    {% endfor %}\n\n    Showing entries\n    {{ pages.current_start_index }}-{{ pages.current_end_index }} of\n    {{ pages.total_count }}.\n    {# Just print pages to render the Digg-style pagination. #}\n    {{ pages.get_rendered }}\n\nNumber of pages\n~~~~~~~~~~~~~~~\n\nYou can use ``{{ pages|length }}`` to retrieve and display the pages count.\nA common use case is to change the layout or display additional info based\non whether the page list contains more than one page. This can be achieved\nchecking ``{% if pages|length > 1 %}``, or, in a more convenient way, using\n``{{ pages.paginated }}``. For example, assume you want to change the layout,\nor display some info, only if the page list contains more than one page, i.e.\nthe results are actually paginated:\n\n.. code-block:: html+django\n\n    {% load el_pagination_tags %}\n\n    {% paginate entries %}\n    {% for entry in entries %}\n        {# your code to show the entry #}\n    {% endfor %}\n    {% get_pages %}\n    {% if pages.paginated %}\n        Some info/layout to display only if the available\n        objects span multiple pages...\n        {{ pages.get_rendered }}\n    {% endif %}\n\nAgain, for a full overview of the :ref:`templatetags-get-pages` and all the\nother template tags, see the :doc:`templatetags_reference`.\n\n.. _digg-ajax:\n\nAdding Ajax\n~~~~~~~~~~~\n\nThe view is exactly the same as the one used in\n:ref:`Twitter-style Pagination<twitter-page-template>`::\n\n    from el_pagination.decorators import page_template\n\n    @page_template('myapp/entry_index_page.html')  # just add this decorator\n    def entry_index(\n            request, template='myapp/entry_index.html', extra_context=None):\n        context = {\n            'entries': Entry.objects.all(),\n        }\n        if extra_context is not None:\n            context.update(extra_context)\n        return render(request, template, context)\n\nAs seen before in :doc:`twitter_pagination`, you have to\n:ref:`split the templates<twitter-split-template>`, separating the main one from\nthe fragment representing the single page. However, this time a container for\nthe page template is also required and, by default, must be an element having a\nclass named *endless_page_template*.\n\n*myapp/entry_index.html* becomes:\n\n.. code-block:: html+django\n\n    <h2>Entries:</h2>\n    <div class=\"endless_page_template\">\n        {% include page_template %}\n    </div>\n\n    {% block js %}\n        {{ block.super }}\n        <script src=\"http://code.jquery.com/jquery-latest.js\"></script>\n        <script src=\"{{ STATIC_URL }}el-pagination/js/el-pagination.js\"></script>\n        <script>$.endlessPaginate();</script>\n    {% endblock %}\n\n*myapp/entry_index_page.html* becomes:\n\n.. code-block:: html+django\n\n    {% load el_pagination_tags %}\n\n    {% paginate entries %}\n    {% for entry in entries %}\n        {# your code to show the entry #}\n    {% endfor %}\n    {% show_pages %}\n\nDone.\n\nIt is possible to manually\n:ref:`override the container selector<javascript-selectors>` used by\n*$.endlessPaginate()* to update the page contents. This can be easily achieved\nby customizing the *pageSelector* option of *$.endlessPaginate()*, e.g.:\n\n.. code-block:: html+django\n\n    <h2>Entries:</h2>\n    <div id=\"entries\">\n        {% include page_template %}\n    </div>\n\n    {% block js %}\n        {{ block.super }}\n        <script src=\"http://code.jquery.com/jquery-latest.js\"></script>\n        <script src=\"{{ STATIC_URL }}el-pagination/js/el-pagination.js\"></script>\n        <script>$.endlessPaginate({pageSelector: 'div#entries'});</script>\n    {% endblock %}\n\nSee the :doc:`javascript` for a detailed explanation of how to integrate\nJavaScript and Ajax features in Django Endless Pagination.\n"
  },
  {
    "path": "doc/generic_views.rst",
    "content": "Generic views\n=============\n\nThis application provides a customized class-based view, similar to\n*django.views.generic.ListView*, that allows Ajax pagination of a\nlist of objects (usually a queryset).\n\nAjaxListView reference\n~~~~~~~~~~~~~~~~~~~~~~\n\n.. py:module:: el_pagination.views\n\n.. py:class:: AjaxListView(django.views.generic.ListView)\n\n    A class based view, similar to *django.views.generic.ListView*,\n    that allows Ajax pagination of a list of objects.\n\n    You can use this class based view in place of *ListView* in order to\n    recreate the behaviour of the *page_template* decorator.\n\n    For instance, assume you have this code (taken from Django docs)::\n\n        from django.conf.urls import url\n        from django.views.generic import ListView\n        from books.models import Publisher\n\n        urlpatterns = [\n            url(r'^publishers/$', ListView.as_view(model=Publisher)),\n        ]\n\n\n    You want to Ajax paginate publishers, so, as seen, you need to switch\n    the template if the request is Ajax and put the page template\n    into the context as a variable named *page_template*.\n\n    This is straightforward, you only need to replace the view class, e.g.::\n\n        from django.conf.urls import *\n        from books.models import Publisher\n\n        from el_pagination.views import AjaxListView\n\n        urlpatterns = [\n            url(r'^publishers/$', AjaxListView.as_view(model=Publisher)),\n        ]\n\n\n    .. py:attribute:: key\n\n        the querystring key used for the current pagination\n        (default: *settings.EL_PAGINATION_PAGE_LABEL*)\n\n    .. py:attribute:: page_template\n\n        the template used for the paginated objects\n\n    .. py:attribute:: page_template_suffix\n\n        the template suffix used for autogenerated page_template name\n        (when not given, default='_page')\n\n\n    .. py:method:: get_context_data(self, **kwargs)\n\n        Adds the *page_template* variable in the context.\n\n        If the *page_template* is not given as a kwarg of the *as_view*\n        method then it is invented using app label, model name\n        (obviously if the list is a queryset), *self.template_name_suffix*\n        and *self.page_template_suffix*.\n\n        For instance, if the list is a queryset of *blog.Entry*,\n        the template will be *myapp/publisher_list_page.html*.\n\n    .. py:method:: get_template_names(self)\n\n        Switch the templates for Ajax requests.\n\n    .. py:method:: get_page_template(self, **kwargs)\n\n        Only called if *page_template* is not given as a kwarg of\n        *self.as_view*.\n\n\nGeneric view example\n~~~~~~~~~~~~~~~~~~~~\nIf the developer wants pagination of publishers, in *views.py* we have code class-based::\n\n    from django.views.generic import ListView\n\n    class EntryListView(ListView)\n        model = Publisher\n        template_name = \"myapp/publisher_list.html\"\n        context_object_name = \"publisher_list\"\n\nor function-based::\n\n    def entry_index(request, template='myapp/publisher_list.html'):\n        context = {\n            'publisher_list': Entry.objects.all(),\n        }\n        return render(request, template, context)\n\nIn *myapp/publisher_list.html*:\n\n.. code-block:: html+django\n\n\t<h2>Entries:</h2>\n\t{% for entry in publisher_list %}\n\t    {# your code to show the entry #}\n\t{% endfor %}\n\nThis is just a basic example. To continue exploring more AjaxListView examples,\nhave a look at :doc:`twitter_pagination`\n\n"
  },
  {
    "path": "doc/index.rst",
    "content": "=============================\nDjango EL(Endless) Pagination\n=============================\n\nThis application provides Twitter- and Digg-style pagination, with multiple\nand lazy pagination and optional Ajax support. It is devoted to implementing\nweb pagination in very few steps.\n\nThe **source code** for this app is hosted at\nhttps://github.com/shtalinberg/django-el-pagination.\n\n:doc:`start` is easy!\n\nContents:\n\n.. toctree::\n   :maxdepth: 2\n\n   changelog\n   start\n   twitter_pagination\n   digg_pagination\n   multiple_pagination\n   lazy_pagination\n   different_first_page\n   current_page_number\n   templatetags_reference\n   javascript\n   generic_views\n   customization\n   contributing\n   contacts\n   thanks\n"
  },
  {
    "path": "doc/javascript.rst",
    "content": "JavaScript reference\n====================\n\nFor each type of pagination it is possible to enable Ajax so that the requested\npage is loaded using an asynchronous request to the server. This is especially\nimportant for :doc:`twitter_pagination` and\n:ref:`endless pagination on scroll<javascript-pagination-on-scroll>`, but\n:doc:`digg_pagination` can also take advantage of this technique.\n\nActivating Ajax support\n~~~~~~~~~~~~~~~~~~~~~~~\n\nAjax support is activated linking jQuery and the ``el-pagination.js`` file\nincluded in this app. It is then possible to use the *$.endlessPaginate()*\njQuery plugin to enable Ajax pagination, e.g.:\n\n.. code-block:: html+django\n\n    <h2>Entries:</h2>\n    <div class=\"endless_page_template\">\n        {% include page_template %}\n    </div>\n\n    {% block js %}\n        {{ block.super }}\n        <script src=\"http://code.jquery.com/jquery-latest.js\"></script>\n        <script src=\"{{ STATIC_URL }}el-pagination/js/el-pagination.js\"></script>\n        <script>$.endlessPaginate();</script>\n    {% endblock %}\n\nThis example assumes that you\n:ref:`separated the fragment<twitter-split-template>` containing the single\npage (*page_tempate*) from the main template (the code snipper above). More on\nthis in :doc:`twitter_pagination` and :doc:`digg_pagination`.\n\nThe *$.endlessPaginate()* call activates Ajax for each pagination present in\nthe page.\n\n.. _javascript-pagination-on-scroll:\n\nPagination on scroll\n~~~~~~~~~~~~~~~~~~~~\n\nIf you want new items to load when the user scrolls down the browser page,\nyou can use the **pagination on scroll** feature: just set the\n*paginateOnScroll* option of *$.endlessPaginate()* to *true*, e.g.:\n\n.. code-block:: html+django\n\n    <h2>Entries:</h2>\n    <div class=\"endless_page_template\">\n        {% include page_template %}\n    </div>\n\n    {% block js %}\n        {{ block.super }}\n        <script src=\"http://code.jquery.com/jquery-latest.js\"></script>\n        <script src=\"{{ STATIC_URL }}el-pagination/js/el-pagination.js\"></script>\n        <script>$.endlessPaginate({paginateOnScroll: true});</script>\n    {% endblock %}\n\nThat's all. See the :doc:`templatetags_reference` page to improve usage of\nthe included templatetags.\n\nIt is possible to set the **bottom margin** used for pagination on scroll\n(default is 1 pixel). For example, if you want the pagination on scroll\nto be activated when 20 pixels remain to the end of the page:\n\n.. code-block:: html+django\n\n    <h2>Entries:</h2>\n    <div class=\"endless_page_template\">\n        {% include page_template %}\n    </div>\n\n    {% block js %}\n        {{ block.super }}\n        <script src=\"http://code.jquery.com/jquery-latest.js\"></script>\n        <script src=\"{{ STATIC_URL }}el-pagination/js/el-pagination.js\"></script>\n        <script>\n            $.endlessPaginate({\n                paginateOnScroll: true,\n                paginateOnScrollMargin: 200\n            });\n        </script>\n    {% endblock %}\n\nAttaching callbacks\n~~~~~~~~~~~~~~~~~~~\n\nIt is possible to customize the behavior of JavaScript pagination by attaching\ncallbacks to *$.endlessPaginate()*, called when the following events are fired:\n\n- *onClick*: the user clicks on a page link;\n- *onCompleted*: the new page is fully loaded and inserted in the DOM.\n\nThe context of both callbacks is the clicked link fragment: in other words,\ninside the callbacks, *this* will be the HTML fragment representing the clicked\nlink, e.g.:\n\n.. code-block:: html+django\n\n    <h2>Entries:</h2>\n    <div class=\"endless_page_template\">\n        {% include page_template %}\n    </div>\n\n    {% block js %}\n        {{ block.super }}\n        <script src=\"http://code.jquery.com/jquery-latest.js\"></script>\n        <script src=\"{{ STATIC_URL }}el-pagination/js/el-pagination.js\"></script>\n        <script>\n            $.endlessPaginate({\n                onClick: function() {\n                    console.log('Label:', $(this).text());\n                }\n            });\n        </script>\n    {% endblock %}\n\nBoth callbacks also receive a *context* argument containing information about\nthe requested page:\n\n- *context.url*: the requested URL;\n- *context.key*: the querystring key used to retrieve the requested contents.\n\nIf the *onClick* callback returns *false*, the pagination process is stopped,\nthe Ajax request is not performed and the *onCompleted* callback never called.\n\nThe *onCompleted* callbacks also receives a second argument: the data returned\nby the server. Basically this is the HTML fragment representing the new\nrequested page.\n\nTo wrap it up, here is an example showing the callbacks' signatures:\n\n.. code-block:: html+django\n\n    <h2>Entries:</h2>\n    <div class=\"endless_page_template\">\n        {% include page_template %}\n    </div>\n\n    {% block js %}\n        {{ block.super }}\n        <script src=\"http://code.jquery.com/jquery-latest.js\"></script>\n        <script src=\"{{ STATIC_URL }}el-pagination/js/el-pagination.js\"></script>\n        <script>\n            $.endlessPaginate({\n                onClick: function(context) {\n                    console.log('Label:', $(this).text());\n                    console.log('URL:', context.url);\n                    console.log('Querystring key:', context.key);\n                    if (forbidden) {  // to be defined...\n                        return false;\n                    }\n                },\n                onCompleted: function(context, fragment) {\n                    console.log('Label:', $(this).text());\n                    console.log('URL:', context.url);\n                    console.log('Querystring key:', context.key);\n                    console.log('Fragment:', fragment);\n                }\n            });\n        </script>\n    {% endblock %}\n\nManually selecting what to bind\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nAs seen above, *$.endlessPaginate()* enables Ajax support for each pagination\nin the page. But assuming you are using :doc:`multiple_pagination`, e.g.:\n\n.. code-block:: html+django\n\n    <h2>Entries:</h2>\n    <div id=\"entries\" class=\"endless_page_template\">\n        {% include \"myapp/entries_page.html\" %}\n    </div>\n\n    <h2>Other entries:</h2>\n    <div id=\"other-entries\" class=\"endless_page_template\">\n        {% include \"myapp/other_entries_page.html\" %}\n    </div>\n\n    {% block js %}\n        {{ block.super }}\n        <script src=\"http://code.jquery.com/jquery-latest.js\"></script>\n        <script src=\"{{ STATIC_URL }}el-pagination/js/el-pagination.js\"></script>\n        <script>$.endlessPaginate();</script>\n    {% endblock %}\n\nWhat if you need Ajax pagination only for *entries* and not for\n*other entries*? You can do this in a straightforward way using jQuery\nselectors, e.g.:\n\n.. code-block:: html+django\n\n    {% block js %}\n        {{ block.super }}\n        <script src=\"http://code.jquery.com/jquery-latest.js\"></script>\n        <script src=\"{{ STATIC_URL }}el-pagination/js/el-pagination.js\"></script>\n        <script>$('#entries').endlessPaginate();</script>\n    {% endblock %}\n\nThe call to *$('#entries').endlessPaginate()* applies Ajax pagination starting\nfrom the DOM node with id *entries* and to all sub-nodes. This means that\n*other entries* are left intact. Of course you can use any selector supported\nby jQuery.\n\nAt this point, you might have already guessed that *$.endlessPaginate()*\nis just an alias for *$('body').endlessPaginate()*.\n\nCustomize each pagination\n~~~~~~~~~~~~~~~~~~~~~~~~~\n\nYou can also call *$.endlessPaginate()* multiple times if you want to customize\nthe behavior of each pagination. E.g. if you need to register a callback for\n*entries* but not for *other entries*:\n\n.. code-block:: html+django\n\n    <h2>Entries:</h2>\n    <div id=\"entries\" class=\"endless_page_template\">\n        {% include \"myapp/entries_page.html\" %}\n    </div>\n\n    <h2>Other entries:</h2>\n    <div id=\"other-entries\" class=\"endless_page_template\">\n        {% include \"myapp/other_entries_page.html\" %}\n    </div>\n\n    {% block js %}\n        {{ block.super }}\n        <script src=\"http://code.jquery.com/jquery-latest.js\"></script>\n        <script src=\"{{ STATIC_URL }}el-pagination/js/el-pagination.js\"></script>\n        <script>\n            $('#entries').endlessPaginate({\n                onCompleted: function(data) {\n                    console.log('New entries loaded.');\n                }\n            });\n            $('#other-entries').endlessPaginate();\n        </script>\n    {% endblock %}\n\n.. _javascript-selectors:\n\nSelectors\n~~~~~~~~~\n\nEach time *$.endlessPaginate()* is used, several JavaScript selectors are used\nto select DOM nodes. Here is a list of them all:\n\n- containerSelector: '.endless_container'\n  (Twitter-style pagination container selector);\n- loadingSelector: '.endless_loading' -\n  (Twitter-style pagination loading selector);\n- moreSelector: 'a.endless_more' -\n  (Twitter-style pagination link selector);\n- contentSelector: null -\n  (Twitter-style pagination content wrapper);\n- pageSelector: '.endless_page_template'\n  (Digg-style pagination page template selector);\n- pagesSelector: 'a.endless_page_link'\n  (Digg-style pagination link selector).\n\nAn example can better explain the meaning of the selectors above. Assume you\nhave a Digg-style pagination like the following:\n\n.. code-block:: html+django\n\n    <h2>Entries:</h2>\n    <div id=\"entries\" class=\"endless_page_template\">\n        {% include \"myapp/entries_page.html\" %}\n    </div>\n\n    {% block js %}\n        {{ block.super }}\n        <script src=\"http://code.jquery.com/jquery-latest.js\"></script>\n        <script src=\"{{ STATIC_URL }}el-pagination/js/el-pagination.js\"></script>\n        <script>\n            $('#entries').endlessPaginate();\n        </script>\n    {% endblock %}\n\nHere the ``#entries`` node is selected and Digg-style pagination is applied.\nDigg-style needs to know which DOM node will be updated with new contents,\nand in this case it's the same node we selected, because we added the\n*endless_page_template* class to that node, and *.endless_page_template*\nis the selector used by default. However, the following example is equivalent\nand does not involve adding another class to the container:\n\n.. code-block:: html+django\n\n    <h2>Entries:</h2>\n    <div id=\"entries\">\n        {% include \"myapp/entries_page.html\" %}\n    </div>\n\n    {% block js %}\n        {{ block.super }}\n        <script src=\"http://code.jquery.com/jquery-latest.js\"></script>\n        <script src=\"{{ STATIC_URL }}el-pagination/js/el-pagination.js\"></script>\n        <script>\n            $('#entries').endlessPaginate({\n                pageSelector: '#entries'\n            });\n        </script>\n    {% endblock %}\n\n.. _javascript-chunks:\n\nOn scroll pagination using chunks\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nSometimes, when using on scroll pagination, you may want to still display\nthe *show more* link after each *N* pages. In Django Endless Pagination this is\ncalled *chunk size*. For instance, a chunk size of 5 means that a *show more*\nlink is displayed after page 5 is loaded, then after page 10, then after page\n15 and so on. Activating this functionality is straightforward, just use the\n*paginateOnScrollChunkSize* option:\n\n.. code-block:: html+django\n\n    {% block js %}\n        {{ block.super }}\n        <script src=\"http://code.jquery.com/jquery-latest.js\"></script>\n        <script src=\"{{ STATIC_URL }}el-pagination/js/el-pagination.js\"></script>\n        <script>\n            $.endlessPaginate({\n                paginateOnScroll: true,\n                paginateOnScrollChunkSize: 5\n            });\n        </script>\n    {% endblock %}\n\nEach time a chunk size is complete, the class ``endless_chunk_complete`` is added to the *show more* link,\nso you still have a way to distinguish between the implicit\nclick done by the scroll event and a real click on the button.\n\n\n.. _javascript-migrate:\n\nMigrate from version 1.1 to 2.1\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nDjango Endless Pagination v2.0 introduces changes in how Ajax pagination\nis handled by JavaScript. These changes are discussed in this document and in\nthe :doc:`changelog`.\n\nThe JavaScript code now lives in a file named ``el-pagination.js``.\nThe two JavaScript files ``el-pagination-endless.js`` and ``el-pagination_on_scroll.js`` was removed.\nHowever, please consider migrating: the old JavaScript files was removed, are\nno longer maintained, and don't provide the new JavaScript features.\n\nInstructions on how to migrate from the old version to the new one follow.\n\nBasic migration\n---------------\n\nBefore:\n\n.. code-block:: html+django\n\n    <h2>Entries:</h2>\n    {% include page_template %}\n\n    {% block js %}\n        {{ block.super }}\n        <script src=\"http://code.jquery.com/jquery-latest.js\"></script>\n        <script src=\"{{ STATIC_URL }}el-pagination/js/el-pagination-endless.js\"></script>\n    {% endblock %}\n\nNow:\n\n.. code-block:: html+django\n\n    <h2>Entries:</h2>\n    {% include page_template %}\n\n    {% block js %}\n        {{ block.super }}\n        <script src=\"http://code.jquery.com/jquery-latest.js\"></script>\n        <script src=\"{{ STATIC_URL }}el-pagination/js/el-pagination.js\"></script>\n        <script>$.endlessPaginate();</script>\n    {% endblock %}\n\nPagination on scroll\n--------------------\n\nBefore:\n\n.. code-block:: html+django\n\n    <h2>Entries:</h2>\n    {% include page_template %}\n\n    {% block js %}\n        {{ block.super }}\n        <script src=\"http://code.jquery.com/jquery-latest.js\"></script>\n        <script src=\"{{ STATIC_URL }}el-pagination/js/el-pagination-endless.js\"></script>\n        <script src=\"{{ STATIC_URL }}el-pagination/js/el-pagination_on_scroll.js\"></script>\n    {% endblock %}\n\nNow:\n\n.. code-block:: html+django\n\n    <h2>Entries:</h2>\n    {% include page_template %}\n\n    {% block js %}\n        {{ block.super }}\n        <script src=\"http://code.jquery.com/jquery-latest.js\"></script>\n        <script src=\"{{ STATIC_URL }}el-pagination/js/el-pagination.js\"></script>\n        <script>\n            $.endlessPaginate({paginateOnScroll: true});\n        </script>\n    {% endblock %}\n\nPagination on scroll with customized bottom margin\n--------------------------------------------------\n\nBefore:\n\n.. code-block:: html+django\n\n    <h2>Entries:</h2>\n    {% include page_template %}\n\n    {% block js %}\n        {{ block.super }}\n        <script src=\"http://code.jquery.com/jquery-latest.js\"></script>\n        <script src=\"{{ STATIC_URL }}el-pagination/js/el-pagination-endless.js\"></script>\n        <script src=\"{{ STATIC_URL }}el-pagination/js/el-pagination_on_scroll.js\"></script>\n        <script>\n            var endless_on_scroll_margin = 200;\n        </script>\n    {% endblock %}\n\nNow:\n\n.. code-block:: html+django\n\n    <h2>Entries:</h2>\n    {% include page_template %}\n\n    {% block js %}\n        {{ block.super }}\n        <script src=\"http://code.jquery.com/jquery-latest.js\"></script>\n        <script src=\"{{ STATIC_URL }}el-pagination/js/el-pagination.js\"></script>\n        <script>\n            $.endlessPaginate({\n                paginateOnScroll: true,\n                paginateOnScrollMargin: 200\n            });\n        </script>\n    {% endblock %}\n\n\nAvoid enabling Ajax on one or more paginations\n----------------------------------------------\n\nBefore:\n\n.. code-block:: html+django\n\n    <h2>Other entries:</h2>\n    <div class=\"endless_page_template endless_page_skip\">\n        {% include \"myapp/other_entries_page.html\" %}\n    </div>\n\n    {% block js %}\n        {{ block.super }}\n        <script src=\"http://code.jquery.com/jquery-latest.js\"></script>\n        <script src=\"{{ STATIC_URL }}el-pagination/js/el-pagination-endless.js\"></script>\n    {% endblock %}\n\nNow:\n\n.. code-block:: html+django\n\n    <h2>Other entries:</h2>\n    <div class=\"endless_page_template endless_page_skip\">\n        {% include \"myapp/other_entries_page.html\" %}\n    </div>\n\n    {% block js %}\n        {{ block.super }}\n        <script src=\"http://code.jquery.com/jquery-latest.js\"></script>\n        <script src=\"{{ STATIC_URL }}el-pagination/js/el-pagination.js\"></script>\n        <script>$('not:(.endless_page_skip)').endlessPaginate();</script>\n    {% endblock %}\n\nIn this last example, activating Ajax just where you want might be preferred\nover excluding nodes.\n"
  },
  {
    "path": "doc/lazy_pagination.rst",
    "content": "Lazy pagination\n===============\n\nUsually pagination requires hitting the database to get the total number of\nitems to display. Lazy pagination avoids this *select count* query and results\nin a faster page load, with a disadvantage: you won't know the total number of\npages in advance.\n\nFor this reason it is better to use lazy pagination in conjunction with\n:doc:`twitter_pagination` (e.g. using the :ref:`templatetags-show-more`\ntemplate tag).\n\nIn order to switch to lazy pagination you have to use the\n:ref:`templatetags-lazy-paginate` template tag instead of the\n:ref:`templatetags-paginate` one, e.g.:\n\n.. code-block:: html+django\n\n    {% load el_pagination_tags %}\n\n    {% lazy_paginate entries %}\n    {% for entry in entries %}\n        {# your code to show the entry #}\n    {% endfor %}\n    {% show_more %}\n\nThe :ref:`templatetags-lazy-paginate` tag can take all the args of the\n:ref:`templatetags-paginate` one, with one exception: negative indexes can not\nbe passed to the ``starting from page`` argument.\n"
  },
  {
    "path": "doc/multiple_pagination.rst",
    "content": "Multiple paginations in the same page\n=====================================\n\nSometimes it is necessary to show different types of paginated objects in the\nsame page. In this case we have to associate a different querystring key\nto every pagination.\n\nNormally, the key used is the one specified in\n``settings.ENDLESS_PAGINATION_PAGE_LABEL`` (see :doc:`customization`),\nbut in the case of multiple pagination the application provides a simple way to\noverride the settings.\n\nIf you do not need Ajax, the only file you need to edit is the template.\nHere is an example with 2 different paginations (*entries* and *other_entries*)\nin the same page, but there is no limit to the number of different paginations\nin a page:\n\n.. code-block:: html+django\n\n    {% load el_pagination_tags %}\n\n    {% paginate entries %}\n    {% for entry in entries %}\n        {# your code to show the entry #}\n    {% endfor %}\n    {% show_pages %}\n\n    {# \"other_entries_page\" is the new querystring key #}\n    {% paginate other_entries using \"other_entries_page\" %}\n    {% for entry in other_entries %}\n        {# your code to show the entry #}\n    {% endfor %}\n    {% show_pages %}\n\nThe ``using`` argument of the :ref:`templatetags-paginate` template tag allows\nyou to choose the name of the querystring key used to track the page number.\nIf not specified the system falls back to\n``settings.EL_PAGINATION_PAGE_LABEL``.\n\nIn the example above, the url *http://example.com?page=2&other_entries_page=3*\nrequests the second page of *entries* and the third page of *other_entries*.\n\nThe name of the querystring key can also be dinamically passed in the template\ncontext, e.g.:\n\n.. code-block:: html+django\n\n    {# page_variable is not surrounded by quotes #}\n    {% paginate other_entries using page_variable %}\n\nYou can use any style of pagination: :ref:`templatetags-show-pages`,\n:ref:`templatetags-get-pages`, :ref:`templatetags-show-more` etc...\n(see :doc:`templatetags_reference`).\n\nAdding Ajax for multiple pagination\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nObviously each pagination needs a template for the page contents. Remember to\nbox each page in a div with a class called *endless_page_template*, or to\nspecify the container selector passing an option to *$.endlessPaginate()* as\nseen in :ref:`Digg-style pagination and Ajax<digg-ajax>`.\n\n*myapp/entry_index.html*:\n\n.. code-block:: html+django\n\n    <h2>Entries:</h2>\n    <div class=\"endless_page_template\">\n        {% include \"myapp/entries_page.html\" %}\n    </div>\n\n    <h2>Other entries:</h2>\n    <div class=\"endless_page_template\">\n        {% include \"myapp/other_entries_page.html\" %}\n    </div>\n\n    {% block js %}\n        {{ block.super }}\n        <script src=\"http://code.jquery.com/jquery-latest.js\"></script>\n        <script src=\"{{ STATIC_URL }}el-pagination/js/el-pagination.js\"></script>\n        <script>$.endlessPaginate();</script>\n    {% endblock %}\n\nSee the :doc:`javascript` for further details on how to use the included\njQuery plugin.\n\n*myapp/entries_page.html*:\n\n.. code-block:: html+django\n\n    {% load el_pagination_tags %}\n\n    {% paginate entries %}\n    {% for entry in entries %}\n        {# your code to show the entry #}\n    {% endfor %}\n    {% show_pages %}\n\n*myapp/other_entries_page.html*:\n\n.. code-block:: html+django\n\n    {% load el_pagination_tags %}\n\n    {% paginate other_entries using other_entries_page %}\n    {% for entry in other_entries %}\n        {# your code to show the entry #}\n    {% endfor %}\n    {% show_pages %}\n\nAs seen :ref:`before<twitter-page-template>`, the decorator ``page_template``\nsimplifies the management of Ajax requests in views. You must, however, map\ndifferent paginations to different page templates.\n\nYou can chain decorator calls relating a template to the associated\nquerystring key, e.g.::\n\n    from endless_pagination.decorators import page_template\n\n    @page_template('myapp/entries_page.html')\n    @page_template('myapp/other_entries_page.html', key='other_entries_page')\n    def entry_index(\n            request, template='myapp/entry_index.html', extra_context=None):\n        context = {\n            'entries': Entry.objects.all(),\n            'other_entries': OtherEntry.objects.all(),\n        }\n        if extra_context is not None:\n            context.update(extra_context)\n        return render_to_response(\n            template, context, context_instance=RequestContext(request))\n\nAs seen in previous examples, if you do not specify the *key* kwarg in the\ndecorator, then the page template is associated to the querystring key\ndefined in the settings.\n\n.. _multiple-page-templates:\n\nYou can use the ``page_templates`` (note the trailing *s*) decorator in\nsubstitution of a decorator chain when you need multiple Ajax paginations.\nThe previous example can be written as::\n\n    from endless_pagination.decorators import page_templates\n\n    @page_templates({\n        'myapp/entries_page.html': None,\n        'myapp/other_entries_page.html': 'other_entries_page',\n    })\n    def entry_index():\n        ...\n\nAs seen, a dict object is passed to the ``page_templates`` decorator, mapping\ntemplates to querystring keys. Alternatively, you can also pass a sequence\nof ``(template, key)`` pairs, e.g.::\n\n    from endless_pagination.decorators import page_templates\n\n    @page_templates((\n        ('myapp/entries_page.html', None),\n        ('myapp/other_entries_page.html', 'other_entries_page'),\n    ))\n    def entry_index():\n        ...\n\nThis also supports serving different paginated objects with the same template.\n\nManually selecting what to bind\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nWhat if you need Ajax pagination only for *entries* and not for\n*other entries*? You can do this in a straightforward way using jQuery\nselectors, e.g.:\n\n.. code-block:: html+django\n\n    {% block js %}\n        {{ block.super }}\n        <script src=\"http://code.jquery.com/jquery-latest.js\"></script>\n        <script src=\"{{ STATIC_URL }}el-pagination/js/el-pagination.js\"></script>\n        <script>$('#entries').endlessPaginate();</script>\n    {% endblock %}\n\nThe call to *$('#entries').endlessPaginate()* applies Ajax pagination starting\nfrom the DOM node with id *entries* and to all sub-nodes. This means that\n*other entries* are left intact. Of course you can use any selector supported\nby jQuery.\n\nRefer to the :doc:`javascript` for an explanation of other features like\ncalling *$.endlessPaginate()* multiple times in order to customize the behavior\nof each pagination in a multiple pagination view.\n"
  },
  {
    "path": "doc/requirements.txt",
    "content": "# Documentation dependencies\nsphinx>=5.0.0,<6.0.0\nsphinx-rtd-theme>=1.0.0\nsphinxcontrib-applehelp>=1.0.4\nsphinxcontrib-devhelp>=1.0.2\nsphinxcontrib-htmlhelp>=2.0.0\nsphinxcontrib-serializinghtml>=1.1.5\nsphinxcontrib-qthelp>=1.0.3\ndocutils>=0.17.1\njinja2>=3.0.0\n\n# Project dependencies\ndjango>=3.2.0"
  },
  {
    "path": "doc/start.rst",
    "content": "Getting started\n===============\n\nRequirements\n~~~~~~~~~~~~\n\n======  ====================\nPython  >= 3.8\nDjango  >= 3.2\njQuery  >= 1.11.1\n======  ====================\n\nInstallation\n~~~~~~~~~~~~\n\nThe Git repository can be cloned with this command::\n\n    git clone https://github.com/shtalinberg/django-el-pagination.git\n\nThe ``el_pagination`` package, included in the distribution, should be\nplaced on the ``PYTHONPATH``.\n\nOtherwise you can just ``easy_install -Z django-el-pagination``\nor ``pip install django-el-pagination``.\n\nSettings\n~~~~~~~~\n\nAdd the request context processor to your *settings.py*, e.g.:\n\n.. code-block:: python\n\n    from django.conf.global_settings import TEMPLATES\n\n    TEMPLATES[0]['OPTIONS']['context_processors'].insert(0, 'django.core.context_processors.request')\n\nor  just adding it to the context_processors manually like so:\n\n.. code-block:: python\n\n    TEMPLATES = [\n        {\n            'BACKEND': 'django.template.backends.django.DjangoTemplates',\n            'DIRS': [os.path.join(BASE_DIR, 'templates'), ],\n            'APP_DIRS': True,\n            'OPTIONS': {\n                'context_processors': [\n                    '...',\n                    '...',\n                    '...',\n                    '...',\n                    'django.template.context_processors.request', ## For EL-pagination\n                ],\n            },\n        },\n    ]\n\nAdd ``'el_pagination'`` to the ``INSTALLED_APPS`` to your *settings.py*.\n\nSee the :doc:`customization` section for other settings.\n\nQuickstart\n~~~~~~~~~~\n\nGiven a template like this:\n\n.. code-block:: html+django\n\n    {% for entry in entries %}\n        {# your code to show the entry #}\n    {% endfor %}\n\nyou can use Digg-style pagination to display objects just by adding:\n\n.. code-block:: html+django\n\n    {% load el_pagination_tags %}\n\n    {% paginate entries %}\n    {% for entry in entries %}\n        {# your code to show the entry #}\n    {% endfor %}\n    {% show_pages %}\n\nDone.\n\nThis is just a basic example. To continue exploring all the Django Endless\nPagination features, have a look at :doc:`twitter_pagination` or\n:doc:`digg_pagination`.\n"
  },
  {
    "path": "doc/templatetags_reference.rst",
    "content": "Templatetags reference\n======================\n\n.. _templatetags-paginate:\n\npaginate\n~~~~~~~~\n\nUsage:\n\n.. code-block:: html+django\n\n    {% paginate entries %}\n\nAfter this call, the *entries* variable in the template context is replaced\nby only the entries of the current page.\n\nYou can also keep your *entries* original variable (usually a queryset)\nand add to the context another name that refers to entries of the current page,\ne.g.:\n\n.. code-block:: html+django\n\n    {% paginate entries as page_entries %}\n\nThe *as* argument is also useful when a nested context variable is provided\nas queryset. In this case, and only in this case, the resulting variable\nname is mandatory, e.g.:\n\n.. code-block:: html+django\n\n    {% paginate entries.all as entries %}\n\nThe number of paginated entries is taken from settings, but you can\noverride the default locally, e.g.:\n\n.. code-block:: html+django\n\n    {% paginate 20 entries %}\n\nOf course you can mix it all:\n\n.. code-block:: html+django\n\n    {% paginate 20 entries as paginated_entries %}\n\nBy default, the first page is displayed the first time you load the page,\nbut you can change this, e.g.:\n\n.. code-block:: html+django\n\n    {% paginate entries starting from page 3 %}\n\nWhen changing the default page, it is also possible to reference the last\npage (or the second last page, and so on) by using negative indexes, e.g:\n\n.. code-block:: html+django\n\n    {% paginate entries starting from page -1 %}\n\nThis can be also achieved using a template variable that was passed to the\ncontext, e.g.:\n\n.. code-block:: html+django\n\n    {% paginate entries starting from page page_number %}\n\nIf the passed page number does not exist, the first page is displayed.\nNote that negative indexes are specific to the ``{% paginate %}`` tag: this\nfeature cannot be used when contents are lazy paginated (see `lazy_paginate`_\nbelow).\n\nIf you have multiple paginations in the same page, you can change the\nquerydict key for the single pagination, e.g.:\n\n.. code-block:: html+django\n\n    {% paginate entries using article_page %}\n\nIn this case *article_page* is intended to be a context variable, but you can\nhardcode the key using quotes, e.g.:\n\n.. code-block:: html+django\n\n    {% paginate entries using 'articles_at_page' %}\n\nAgain, you can mix it all (the order of arguments is important):\n\n.. code-block:: html+django\n\n    {% paginate 20 entries starting from page 3 using page_key as paginated_entries %}\n\nAdditionally you can pass a path to be used for the pagination:\n\n.. code-block:: html+django\n\n    {% paginate 20 entries using page_key with pagination_url as paginated_entries %}\n\nThis way you can easily create views acting as API endpoints, and point your\nAjax calls to that API. In this case *pagination_url* is considered a\ncontext variable, but it is also possible to hardcode the URL, e.g.:\n\n.. code-block:: html+django\n\n    {% paginate 20 entries with \"/mypage/\" %}\n\nIf you want the first page to contain a different number of items than\nsubsequent pages, you can separate the two values with a comma, e.g. if\nyou want 3 items on the first page and 10 on other pages:\n\n.. code-block:: html+django\n\n    {% paginate 3,10 entries %}\n\nYou must use this tag before calling the `show_more`_, `get_pages`_ or\n`show_pages`_ ones.\n\n.. _templatetags-lazy-paginate:\n\nlazy_paginate\n~~~~~~~~~~~~~\n\nPaginate objects without hitting the database with a *select count* query.\nUsually pagination requires hitting the database to get the total number of\nitems to display. Lazy pagination avoids this *select count* query and results\nin a faster page load, with a disadvantage: you won't know the total number of\npages in advance.\n\nUse this in the same way as `paginate`_ tag when you are not interested in the\ntotal number of pages.\n\nThe ``lazy_paginate`` tag can take all the args of the ``paginate`` one, with\none exception: negative indexes can not be passed to the ``starting from page``\nargument.\n\n.. _templatetags-show-more:\n\nshow_more\n~~~~~~~~~\n\nShow the link to get the next page in a :doc:`twitter_pagination`. Usage:\n\n.. code-block:: html+django\n\n    {% show_more %}\n\nAlternatively you can override the label passed to the default template:\n\n.. code-block:: html+django\n\n    {% show_more \"even more\" %}\n\nYou can override the loading text too:\n\n.. code-block:: html+django\n\n    {% show_more \"even more\" \"working\" %}\n\nMust be called after `paginate`_ or `lazy_paginate`_.\n\n.. _templatetags-show-more-table:\n\nshow_more_table\n~~~~~~~~~~~~~~~\n\nSame as the `show_more`_, but for table pagination. Usage:\n\n.. code-block:: html+django\n\n    {% show_more_table %}\n\nIf use table in a :doc:`twitter_pagination`:\n\n.. code-block:: html+django\n\n    <table>\n      {% include page_template %}\n    </table>\n\nthen page template:\n\n.. code-block:: html+django\n\n    {% load el_pagination_tags %}\n\n    {% paginate 5 objects %}\n    {% for object in objects %}\n        <tr>\n            <td>\n                {{ object.title }}\n            </td>\n        </tr>\n    {% endfor %}\n    {% show_more_table \"More results\" %}\n\nFor :doc:`digg_pagination` use instead `show_more_table` in page template:\n\n.. code-block:: html+django\n\n    <tr>\n      <td>{% show_pages %}</td>\n    </td>\n\n.. _templatetags-get-pages:\n\nget_pages\n~~~~~~~~~\n\nUsage:\n\n.. code-block:: html+django\n\n    {% get_pages %}\n\nThis is mostly used for :doc:`digg_pagination`.\n\nThis call inserts in the template context a *pages* variable, as a sequence\nof page links. You can use *pages* in different ways:\n\n- just print *pages* and you will get Digg-style pagination displayed:\n\n.. code-block:: html+django\n\n    {{ pages.get_rendered }}\n\n- display pages count:\n\n.. code-block:: html+django\n\n    {{ pages|length }}\n\n- display numbers of objects in per page:\n\n.. code-block:: html+django\n\n    {{ pages.per_page_number }}\n\n- check if the page list contains more than one page:\n\n.. code-block:: html+django\n\n    {{ pages.paginated }}\n    {# the following is equivalent #}\n    {{ pages|length > 1 }}\n\n- get a specific page:\n\n.. code-block:: html+django\n\n    {# the current selected page #}\n    {{ pages.current }}\n\n    {# the first page #}\n    {{ pages.first }}\n\n    {# the last page #}\n    {{ pages.last }}\n\n    {# the previous page (or nothing if you are on first page) #}\n    {{ pages.previous }}\n\n    {# the next page (or nothing if you are in last page) #}\n    {{ pages.next }}\n\n    {# the third page #}\n    {{ pages.3 }}\n    {# this means page.1 is the same as page.first #}\n\n    {# the 1-based index of the first item on the current page #}\n    {{ pages.current_start_index }}\n\n    {# the 1-based index of the last item on the current page #}\n    {{ pages.current_end_index }}\n\n    {# the total number of objects, across all pages #}\n    {{ pages.total_count }}\n\n    {# the first page represented as an arrow #}\n    {{ pages.first_as_arrow }}\n\n    {# the last page represented as an arrow #}\n    {{ pages.last_as_arrow }}\n\n- iterate over *pages* to get all pages:\n\n.. code-block:: html+django\n\n    {% for page in pages %}\n        {# display page link #}\n        {{ page.render_link }}\n\n        {# the page url (beginning with \"?\") #}\n        {{ page.url }}\n\n        {# the page path #}\n        {{ page.path }}\n\n        {# the page number #}\n        {{ page.number }}\n\n        {# a string representing the page (commonly the page number) #}\n        {{ page.label }}\n\n        {# check if the page is the current one #}\n        {{ page.is_current }}\n\n        {# check if the page is the first one #}\n        {{ page.is_first }}\n\n        {# check if the page is the last one #}\n        {{ page.is_last }}\n\n        {### next two example work only with settings.EL_PAGINATION_USE_NEXT_PREVIOUS_LINKS = True ###}\n\n        {# check if the page is previous #}\n        {{ page.is_previous }}\n\n        {# check if the page is_next #}\n        {{ page.is_next }}\n\n    {% endfor %}\n\nYou can change the variable name, e.g.:\n\n.. code-block:: html+django\n\n    {% get_pages as page_links %}\n    {{ page_links.get_rendered }}\n    {# the current selected page #}\n    {{ page_links.current }}\n\nThis must be called after `paginate`_ or `lazy_paginate`_.\n\n.. _templatetags-show-pages:\n\nshow_pages\n~~~~~~~~~~\n\nShow page links. Usage:\n\n.. code-block:: html+django\n\n    {% show_pages %}\n\nIt is just a shortcut for:\n\n.. code-block:: html+django\n\n    {% get_pages %}\n    {{ pages.get_rendered }}\n\nYou can set ``EL_PAGINATION_PAGE_LIST_CALLABLE`` in your *settings.py* to\na callable used to customize the pages that are displayed.\n``EL_PAGINATION_PAGE_LIST_CALLABLE`` can also be a dotted path\nrepresenting a callable, e.g.::\n\n    EL_PAGINATION_PAGE_LIST_CALLABLE = 'path.to.callable'\n\nThe callable takes the current page number and the total number of pages,\nand must return a sequence of page numbers that will be displayed.\n\nThe sequence can contain other values:\n\n- *'previous'*: will display the previous page in that position;\n- *'next'*: will display the next page in that position;\n- *'first'*: will display the first page as an arrow;\n- *'last'*: will display the last page as an arrow;\n- *None*: a separator will be displayed in that position.\n\nHere is an example of a custom callable that displays the previous page, then\nthe first page, then a separator, then the current page, and finally the last\npage::\n\n    def get_page_numbers(current_page, num_pages):\n        return ('previous', 1, None, current_page, 'last')\n\nIf ``EL_PAGINATION_PAGE_LIST_CALLABLE`` is *None* the internal callable\n``endless_pagination.utils.get_page_numbers`` is used, generating a Digg-style\npagination.\n\nAn alternative implementation is available:\n``endless_pagination.utils.get_elastic_page_numbers``: it adapts its output\nto the number of pages, making it arguably more usable when there are many\nof them.\n\nThis must be called after `paginate`_ or `lazy_paginate`_.\n\n.. _templatetags-show-current-number:\n\nshow_current_number\n~~~~~~~~~~~~~~~~~~~\n\nShow the current page number, or insert it in the context.\n\nThis tag can for example be useful to change the page title according to\nthe current page number.\n\nTo just show current page number:\n\n.. code-block:: html+django\n\n    {% show_current_number %}\n\nIf you use multiple paginations in the same page, you can get the page\nnumber for a specific pagination using the querystring key, e.g.:\n\n.. code-block:: html+django\n\n    {% show_current_number using mykey %}\n\nThe default page when no querystring is specified is 1. If you changed it\nin the `paginate`_ template tag, you have to call  ``show_current_number``\naccording to your choice, e.g.:\n\n.. code-block:: html+django\n\n    {% show_current_number starting from page 3 %}\n\nThis can be also achieved using a template variable you passed to the\ncontext, e.g.:\n\n.. code-block:: html+django\n\n    {% show_current_number starting from page page_number %}\n\nYou can of course mix it all (the order of arguments is important):\n\n.. code-block:: html+django\n\n    {% show_current_number starting from page 3 using mykey %}\n\nIf you want to insert the current page number in the context, without\nactually displaying it in the template, use the *as* argument, i.e.:\n\n.. code-block:: html+django\n\n    {% show_current_number as page_number %}\n    {% show_current_number starting from page 3 using mykey as page_number %}\n"
  },
  {
    "path": "doc/thanks.rst",
    "content": "Thanks\n======\n\nThis application was initially inspired by the excellent tool\n*django-pagination* (see https://github.com/ericflo/django-pagination).\n\nThanks to Francesco Banconi for improving previous version of this application\n(django-endless-pagination)\n\nThanks to Jannis Leidel for improving the application with some new features,\nand for contributing the German translation.\n\nAnd thanks to Nicola 'tekNico' Larosa for reviewing the documentation and for\nimplementing the elastic pagination feature.\n"
  },
  {
    "path": "doc/twitter_pagination.rst",
    "content": "Twitter-style Pagination\n========================\n\nAssuming the developer wants Twitter-style pagination of\nentries of a blog post, in *views.py* we have class-based::\n\n    from el_pagination.views import AjaxListView\n\n    class EntryListView(AjaxListView):\n        context_object_name = \"entry_list\"\n        template_name = \"myapp/entry_list.html\"\n\n        def get_queryset(self):\n            return Entry.objects.all()\n\nor function-based::\n\n    def entry_index(request, template='myapp/entry_list.html'):\n        context = {\n            'entry_list': Entry.objects.all(),\n        }\n        return render(request, template, context)\n\n\nIn *myapp/entry_list.html*:\n\n.. code-block:: html+django\n\n    <h2>Entries:</h2>\n    {% for entry in entry_list %}\n        {# your code to show the entry #}\n    {% endfor %}\n\n.. _twitter-split-template:\n\nSplit the template\n~~~~~~~~~~~~~~~~~~\n\nThe response to an Ajax request should not return the entire template,\nbut only the portion of the page to be updated or added.\nSo it is convenient to extract from the template the part containing the\nentries, and use it to render the context if the request is Ajax.\nThe main template will include the extracted part, so it is convenient\nto put the page template name in the context.\n\n*views.py* class-based becomes::\n\n    from el_pagination.views import AjaxListView\n\n    class EntryListView(AjaxListView):\n        context_object_name = \"entry_list\"\n        template_name = \"myapp/entry_list.html\"\n        page_template='myapp/entry_list_page.html'\n\n        def get_queryset(self):\n            return Entry.objects.all()\n\nor function-based::\n\n    def entry_list(request,\n        template='myapp/entry_list.html',\n        page_template='myapp/entry_list_page.html'):\n        context = {\n            'entry_list': Entry.objects.all(),\n            'page_template': page_template,\n        }\n        if request.headers.get('x-requested-with') == 'XMLHttpRequest':\n            template = page_template\n        return render(request, template, context)\n\n\nSee :ref:`below<twitter-page-template>` how to obtain the same result\n**just decorating the view**.\n\n*myapp/entry_list.html* becomes:\n\n.. code-block:: html+django\n\n    <h2>Entries:</h2>\n    {% include page_template %}\n\n*myapp/entry_list_page.html* becomes:\n\n.. code-block:: html+django\n\n    {% for entry in entry_list %}\n        {# your code to show the entry #}\n    {% endfor %}\n\n.. _twitter-page-template:\n\nA shortcut for ajaxed views\n~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nA good practice in writing views is to allow other developers to inject\nthe template name and extra data, so that they are added to the context.\nThis allows the view to be easily reused. Let's resume the original view\nwith extra context injection:\n\n*views.py*::\n\n    def entry_index(request,\n            template='myapp/entry_list.html', extra_context=None):\n        context = {\n            'entry_list': Entry.objects.all(),\n        }\n        if extra_context is not None:\n            context.update(extra_context)\n        return render(request, template, context)\n\n\nSplitting templates and putting the Ajax template name in the context\nis easily achievable by using an included decorator.\n\n*views.py* becomes::\n\n    from el_pagination.decorators import page_template\n\n    @page_template('myapp/entry_list_page.html')  # just add this decorator\n    def entry_list(request,\n            template='myapp/entry_list.html', extra_context=None):\n        context = {\n            'entry_list': Entry.objects.all(),\n        }\n        if extra_context is not None:\n            context.update(extra_context)\n        return render(request, template, context)\n\n\nPaginating objects\n~~~~~~~~~~~~~~~~~~\n\nAll that's left is changing the page template and loading the\n:doc:`endless templatetags<templatetags_reference>`, the jQuery library and the\njQuery plugin ``el-pagination.js`` included in the distribution under\n``/static/el-pagination/js/``.\n\n*myapp/entry_list.html* becomes:\n\n.. code-block:: html+django\n\n    <h2>Entries:</h2>\n    {% include page_template %}\n\n    {% block js %}\n        {{ block.super }}\n        <script src=\"http://code.jquery.com/jquery-latest.js\"></script>\n        <script src=\"{{ STATIC_URL }}el-pagination/js/el-pagination.js\"></script>\n        <script>$.endlessPaginate();</script>\n    {% endblock %}\n\n*myapp/entry_list_page.html* becomes:\n\n.. code-block:: html+django\n\n    {% load el_pagination_tags %}\n\n    {% paginate entry_list %}\n    {% for entry in entry_list %}\n        {# your code to show the entry #}\n    {% endfor %}\n    {% show_more %}\n\nThe :ref:`templatetags-paginate` template tag takes care of customizing the\ngiven queryset and the current template context. In the context of a\nTwitter-style pagination the :ref:`templatetags-paginate` tag is often replaced\nby the :ref:`templatetags-lazy-paginate` one, which offers, more or less, the\nsame functionalities and allows for reducing database access: see\n:doc:`lazy_pagination`.\n\nThe :ref:`templatetags-show-more` one displays the link to navigate to the next\npage.\n\nYou might want to glance at the :doc:`javascript` for a detailed explanation of\nhow to integrate JavaScript and Ajax features in Django Endless Pagination.\n\nPagination on scroll\n~~~~~~~~~~~~~~~~~~~~\n\nIf you want new items to load when the user scroll down the browser page,\nyou can use the :ref:`pagination on scroll<javascript-pagination-on-scroll>`\nfeature: just set the *paginateOnScroll* option of *$.endlessPaginate()* to\n*true*, e.g.:\n\n.. code-block:: html+django\n\n    <h2>Entries:</h2>\n    {% include page_template %}\n\n    {% block js %}\n        {{ block.super }}\n        <script src=\"http://code.jquery.com/jquery-latest.js\"></script>\n        <script src=\"{{ STATIC_URL }}el-pagination/js/el-pagination.js\"></script>\n        <script>$.endlessPaginate({paginateOnScroll: true});</script>\n    {% endblock %}\n\nThat's all. See the :doc:`templatetags_reference` to improve the use of\nincluded templatetags.\n\nIt is possible to set the bottom margin used for\n:ref:`pagination on scroll<javascript-pagination-on-scroll>` (default is 1\npixel). For example, if you want the pagination on scroll to be activated when\n20 pixels remain to the end of the page:\n\n.. code-block:: html+django\n\n    <h2>Entries:</h2>\n    {% include page_template %}\n\n    {% block js %}\n        {{ block.super }}\n        <script src=\"http://code.jquery.com/jquery-latest.js\"></script>\n        <script src=\"{{ STATIC_URL }}el-pagination/js/el-pagination.js\"></script>\n        <script>\n            $.endlessPaginate({\n                paginateOnScroll: true,\n                paginateOnScrollMargin: 20\n            });\n        </script>\n    {% endblock %}\n\nAgain, see the :doc:`javascript`.\n\nOn scroll pagination using chunks\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nSometimes, when using on scroll pagination, you may want to still display\nthe *show more* link after each *N* pages. In Django Endless Pagination this is\ncalled *chunk size*. For instance, a chunk size of 5 means that a *show more*\nlink is displayed after page 5 is loaded, then after page 10, then after page\n15 and so on. Activating :ref:`chunks<javascript-chunks>` is straightforward,\njust use the *paginateOnScrollChunkSize* option:\n\n.. code-block:: html+django\n\n    {% block js %}\n        {{ block.super }}\n        <script src=\"http://code.jquery.com/jquery-latest.js\"></script>\n        <script src=\"{{ STATIC_URL }}el-pagination/js/el-pagination.js\"></script>\n        <script>\n            $.endlessPaginate({\n                paginateOnScroll: true,\n                paginateOnScrollChunkSize: 5\n            });\n        </script>\n    {% endblock %}\n\nSpecifying where the content will be inserted\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nIf you are paginating a table, you can use :ref:`templatetags-show-more-table` or you may want to include the *show_more* link\nafter the table itself, but the loaded content should be placed inside the\ntable.\n\nFor any case like this, you may specify the *contentSelector* option that\npoints to the element that will wrap the cumulative data:\n\n.. code-block:: html+django\n\n    {% block js %}\n        {{ block.super }}\n        <script src=\"http://code.jquery.com/jquery-latest.js\"></script>\n        <script src=\"{{ STATIC_URL }}el-pagination/js/el-pagination.js\"></script>\n        <script>\n            $.endlessPaginate({\n                contentSelector: '.endless_content_wrapper'\n            });\n        </script>\n    {% endblock %}\n\n.. note::\n\n    By default, the contentSelector is null, making each new page be inserted\n    before the *show_more* link container.\n\nWhen using this approach, you should take 2 more actions.\n\nAt first, the page template must be splitted a little different. You must do\nthe pagination in the main template and only apply pagination in the page\ntemplate if under ajax:\n\n*myapp/entry_list.html* becomes:\n\n.. code-block:: html+django\n\n    <h2>Entries:</h2>\n    {% paginate entry_list %}\n    <ul>\n        {% include page_template %}\n    </ul>\n    {% show_more %}\n\n    {% block js %}\n        {{ block.super }}\n        <script src=\"http://code.jquery.com/jquery-latest.js\"></script>\n        <script src=\"{{ STATIC_URL }}el-pagination/js/el-pagination.js\"></script>\n        <script>$.endlessPaginate();</script>\n    {% endblock %}\n\n*myapp/entry_list_page.html* becomes:\n\n.. code-block:: html+django\n\n    {% load el_pagination_tags %}\n\n    {% if request.is_ajax %}{% paginate entry_list %}{% endif %}\n    {% for entry in entry_list %}\n        {# your code to show the entry #}\n    {% endfor %}\n\nThis is needed because the *show_more* button now is taken off the\npage_template and depends of the *paginate* template tag. To avoid apply\npagination twice, we avoid run it a first time in the page_template.\n\nYou may also set the *EL_PAGINATION_PAGE_OUT_OF_RANGE_404* to True, so a blank\npage wouldn't render the first page (the default behavior). When a blank page\nis loaded and propagates the 404 error, the *show_more* link is removed.\n\nBefore version 2.0\n~~~~~~~~~~~~~~~~~~\n\nDjango Endless Pagination v2.0 introduces a redesigned Ajax support for\npagination. As seen above, Ajax can now be enabled using a brand new jQuery\nplugin that can be found in\n``static/el-pagination/js/el-pagination.js``.\n\nOld code was removed:\n\n.. code-block:: html+django\n\n    <script src=\"http://code.jquery.com/jquery-latest.js\"></script>\n    {# new jQuery plugin #}\n    <script src=\"{{ STATIC_URL }}el-pagination/js/el-pagination.js\"></script>\n    {# Removed. #}\n    <script src=\"{{ STATIC_URL }}el-pagination/js/el-pagination-endless.js\"></script>\n    <script src=\"{{ STATIC_URL }}el-pagination/js/el-pagination_on_scroll.js\"></script>\n\nHowever, please consider :ref:`migrating<javascript-migrate>` as soon as\npossible: the old JavaScript files are removed.\n\nPlease refer to the :doc:`javascript` for a detailed overview of the new\nfeatures and for instructions on :ref:`how to migrate<javascript-migrate>` from\nthe old JavaScript files to the new one.\n"
  },
  {
    "path": "el_pagination/__init__.py",
    "content": "\"\"\"Django pagination tools supporting Ajax, multiple and lazy pagination,\nTwitter-style and Digg-style pagination.\n\"\"\"\n\nVERSION = (4, 1, 2)\n\n__version__ = '.'.join(map(str, VERSION))\n\n\ndef get_version():\n    \"\"\"Return the Django EL Pagination version as a string.\"\"\"\n    return __version__\n"
  },
  {
    "path": "el_pagination/decorators.py",
    "content": "\"\"\"View decorators for Ajax powered pagination.\"\"\"\n\nfrom functools import wraps\n\nfrom el_pagination.settings import PAGE_LABEL, TEMPLATE_VARNAME\n\nQS_KEY = \"querystring_key\"\n\n\ndef page_template(template, key=PAGE_LABEL):\n    \"\"\"Return a view dynamically switching template if the request is Ajax.\n\n    Decorate a view that takes a *template* and *extra_context* keyword\n    arguments (like generic views).\n    The template is switched to *page_template* if request is ajax and\n    if *querystring_key* variable passed by the request equals to *key*.\n    This allows multiple Ajax paginations in the same page.\n    The name of the page template is given as *page_template* in the\n    extra context.\n    \"\"\"\n\n    def decorator(view):\n        @wraps(view)\n        def decorated(request, *args, **kwargs):\n            # Trust the developer: he wrote ``context.update(extra_context)``\n            # in his view.\n            extra_context = kwargs.setdefault(\"extra_context\", {})\n            extra_context[\"page_template\"] = template\n            # Switch the template when the request is Ajax.\n            querystring_key = request.GET.get(\n                QS_KEY, request.POST.get(QS_KEY, PAGE_LABEL)\n            )\n            if (\n                request.headers.get(\"x-requested-with\") == \"XMLHttpRequest\"\n                and querystring_key == key\n            ):\n                kwargs[TEMPLATE_VARNAME] = template\n            return view(request, *args, **kwargs)\n\n        return decorated\n\n    return decorator\n\n\ndef _get_template(querystring_key, mapping):\n    \"\"\"Return the template corresponding to the given ``querystring_key``.\"\"\"\n    default = None\n    try:\n        template_and_keys = mapping.items()\n    except AttributeError:\n        template_and_keys = mapping\n    for template, key in template_and_keys:\n        if key is None:\n            key = PAGE_LABEL\n            default = template\n        if key == querystring_key:\n            return template\n    return default\n\n\ndef page_templates(mapping):\n    \"\"\"Like the *page_template* decorator but manage multiple paginations.\n\n    You can map multiple templates to *querystring_keys* using the *mapping*\n    dict, e.g.::\n\n        @page_templates({\n            'page_contents1.html': None,\n            'page_contents2.html': 'go_to_page',\n        })\n        def myview(request):\n            ...\n\n    When the value of the dict is None then the default *querystring_key*\n    (defined in settings) is used. You can use this decorator instead of\n    chaining multiple *page_template* calls.\n    \"\"\"\n\n    def decorator(view):\n        @wraps(view)\n        def decorated(request, *args, **kwargs):\n            # Trust the developer: he wrote ``context.update(extra_context)``\n            # in his view.\n            extra_context = kwargs.setdefault(\"extra_context\", {})\n            querystring_key = request.GET.get(\n                QS_KEY, request.POST.get(QS_KEY, PAGE_LABEL)\n            )\n            template = _get_template(querystring_key, mapping)\n            extra_context[\"page_template\"] = template\n            # Switch the template when the request is Ajax.\n            if request.headers.get(\"x-requested-with\") == \"XMLHttpRequest\" and template:\n                kwargs[TEMPLATE_VARNAME] = template\n            return view(request, *args, **kwargs)\n\n        return decorated\n\n    return decorator\n"
  },
  {
    "path": "el_pagination/exceptions.py",
    "content": "\"\"\"Pagination exceptions.\"\"\"\n\n\nclass PaginationError(Exception):\n    \"\"\"Error in the pagination process.\"\"\"\n"
  },
  {
    "path": "el_pagination/loaders.py",
    "content": "\"\"\"Django EL Pagination object loaders.\"\"\"\n\nfrom importlib import import_module\n\nfrom django.core.exceptions import ImproperlyConfigured\n\n\ndef load_object(path):\n    \"\"\"Return the Python object represented by dotted *path*.\"\"\"\n    i = path.rfind('.')\n    module_name, object_name = path[:i], path[i + 1 :]\n    # Load module.\n    try:\n        module = import_module(module_name)\n    except ImportError as exc:\n        raise ImproperlyConfigured(f'Module {module_name} not found') from exc\n    except ValueError as exc:\n        raise ImproperlyConfigured(f'Invalid module {module_name}') from exc\n    # Load object.\n    try:\n        return getattr(module, object_name)\n    except AttributeError as exc:\n        msg = 'Module %r does not define an object named %r'\n        raise ImproperlyConfigured(msg % (module_name, object_name)) from exc\n"
  },
  {
    "path": "el_pagination/locale/de/LC_MESSAGES/django.po",
    "content": "# Django Endless Pagination Locale\n# Copyright (C) 2009-2013 Francesco Banconi\n# This file is distributed under the same license as the django-endless-pagination package.\n# Francesco Banconi <francesco.banconi@gmail.com>, 2009.\n#\n#, fuzzy\nmsgid \"\"\nmsgstr \"\"\n\"Project-Id-Version: PACKAGE VERSION\\n\"\n\"Report-Msgid-Bugs-To: \\n\"\n\"POT-Creation-Date: 2010-03-04 17:52+0100\\n\"\n\"PO-Revision-Date: 2010-03-04 18:00+0100\\n\"\n\"Last-Translator: Jannis Leidel <jannis@leidel.info>\\n\"\n\"Language-Team: LANGUAGE <LL@li.org>\\n\"\n\"MIME-Version: 1.0\\n\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\"Plural-Forms: nplurals=2; plural=(n != 1)\\n\"\n\n#: templates/endless/show_more.html:4\nmsgid \"more\"\nmsgstr \"mehr\"\n"
  },
  {
    "path": "el_pagination/locale/es/LC_MESSAGES/django.po",
    "content": "# Django Endless Pagination Locale\n# Copyright (C) 2009-2013 Francesco Banconi\n# This file is distributed under the same license as the django-endless-pagination package.\n# Francesco Banconi <francesco.banconi@gmail.com>, 2013.\n#\n#, fuzzy\nmsgid \"\"\nmsgstr \"\"\n\"Project-Id-Version: PACKAGE VERSION\\n\"\n\"Report-Msgid-Bugs-To: \\n\"\n\"POT-Creation-Date: 2013-02-11 18:03+0100\\n\"\n\"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n\"\n\"Last-Translator: Francesco Banconi <francesco.banconi@gmail.com>\\n\"\n\"Language-Team: LANGUAGE <LL@li.org>\\n\"\n\"Language: \\n\"\n\"MIME-Version: 1.0\\n\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\"Plural-Forms: nplurals=2; plural=(n != 1)\\n\"\n\n#: templates/endless/show_more.html:5\nmsgid \"more\"\nmsgstr \"Más resultados\"\n"
  },
  {
    "path": "el_pagination/locale/fr/LC_MESSAGES/django.po",
    "content": "# Django Endless Pagination Locale\n# Copyright (C) 2009-2013 Francesco Banconi\n# This file is distributed under the same license as the django-endless-pagination package.\n# Francesco Banconi <francesco.banconi@gmail.com>, 2013.\n#\n#, fuzzy\nmsgid \"\"\nmsgstr \"\"\n\"Project-Id-Version: PACKAGE VERSION\\n\"\n\"Report-Msgid-Bugs-To: \\n\"\n\"POT-Creation-Date: 2013-02-11 16:06+0100\\n\"\n\"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n\"\n\"Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n\"\n\"Language-Team: LANGUAGE <LL@li.org>\\n\"\n\"Language: \\n\"\n\"MIME-Version: 1.0\\n\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\"Plural-Forms: nplurals=2; plural=(n > 1)\\n\"\n\n#: templates/endless/show_more.html:5\nmsgid \"more\"\nmsgstr \"En voir plus\"\n"
  },
  {
    "path": "el_pagination/locale/it/LC_MESSAGES/django.po",
    "content": "# Django Endless Pagination Locale\n# Copyright (C) 2009-2013 Francesco Banconi\n# This file is distributed under the same license as the django-endless-pagination package.\n# Francesco Banconi <francesco.banconi@gmail.com>, 2009.\n#\n#, fuzzy\nmsgid \"\"\nmsgstr \"\"\n\"Project-Id-Version: PACKAGE VERSION\\n\"\n\"Report-Msgid-Bugs-To: \\n\"\n\"POT-Creation-Date: 2009-08-22 19:21+0200\\n\"\n\"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n\"\n\"Last-Translator: Francesco Banconi <francesco.banconi@gmail.com>\\n\"\n\"Language-Team: LANGUAGE <LL@li.org>\\n\"\n\"MIME-Version: 1.0\\n\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\n#: templates/endless/show_more.html:4\nmsgid \"more\"\nmsgstr \"mostra altri\"\n"
  },
  {
    "path": "el_pagination/locale/pt_BR/LC_MESSAGES/django.po",
    "content": "# Django Endless Pagination Locale\n# Copyright (C) 2009-2013 Francesco Banconi\n# This file is distributed under the same license as the django-endless-pagination package.\n# Francesco Banconi <francesco.banconi@gmail.com>, 2009.\n#\n#, fuzzy\nmsgid \"\"\nmsgstr \"\"\n\"Project-Id-Version: PACKAGE VERSION\\n\"\n\"Report-Msgid-Bugs-To: \\n\"\n\"POT-Creation-Date: 2017-05-08 20:34-0300\\n\"\n\"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n\"\n\"Last-Translator: Michel Sabchuk <michel@sabchuk.com.br>\\n\"\n\"Language-Team: LANGUAGE <LL@li.org>\\n\"\n\"MIME-Version: 1.0\\n\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\"Plural-Forms: nplurals=2; plural=(n > 1);\\n\"\n\n#: templates/el_pagination/show_more.html:5\nmsgid \"more\"\nmsgstr \"mais\"\n\n#: views.py:102\nmsgid \"Empty list and ``%(class_name)s.allow_empty`` is False.\"\nmsgstr \"Lista vazia e ``%(class_name)s.allow_empty`` é Falso.\"\n"
  },
  {
    "path": "el_pagination/locale/zh_CN/LC_MESSAGES/django.po",
    "content": "# Django Endless Pagination Locale\n# Copyright (C) 2009-2013 Francesco Banconi\n# This file is distributed under the same license as the django-endless-pagination package.\n# Francesco Banconi <francesco.banconi@gmail.com>, 2013.\n#\n#, fuzzy\nmsgid \"\"\nmsgstr \"\"\n\"Project-Id-Version: PACKAGE VERSION\\n\"\n\"Report-Msgid-Bugs-To: \\n\"\n\"POT-Creation-Date: 2009-08-22 19:21+0200\\n\"\n\"PO-Revision-Date: 2013-02-11 16:54+0800\\n\"\n\"Last-Translator: mozillazg <mozillazg101@gmail.com>\\n\"\n\"Language-Team: LANGUAGE <LL@li.org>\\n\"\n\"MIME-Version: 1.0\\n\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\n#: templates/endless/show_more.html:4\nmsgid \"more\"\nmsgstr \"查看更多\"\n"
  },
  {
    "path": "el_pagination/models.py",
    "content": "\"\"\"Ephemeral models used to represent a page and a list of pages.\"\"\"\n\nfrom django.template import loader\nfrom django.utils.encoding import force_str, iri_to_uri\n\nfrom el_pagination import loaders, settings, utils\n\n# Page templates cache.\n_template_cache = {}\n\n\nclass ELPage:\n    \"\"\"A page link representation.\n\n    Interesting attributes:\n\n        - *self.number*: the page number;\n        - *self.label*: the label of the link\n          (usually the page number as string);\n        - *self.url*: the url of the page (strting with \"?\");\n        - *self.path*: the path of the page;\n\n        - *self.is_current*: return True if page is the current page displayed;\n        - *self.is_first*: return True if page is the first page;\n        - *self.is_last*:  return True if page is the last page.\n    \"\"\"\n\n    def __init__(\n        self,\n        request,\n        number,\n        current_number,\n        total_number,\n        querystring_key,\n        label=None,\n        default_number=1,\n        override_path=None,\n        context=None,\n    ):\n        self._request = request\n        self.number = number\n        self.label = force_str(number) if label is None else label\n        self.querystring_key = querystring_key\n        self.context = context or {}\n        self.context['request'] = request\n\n        self.is_current = number == current_number\n        self.is_first = number == 1\n        self.is_last = number == total_number\n        if settings.USE_NEXT_PREVIOUS_LINKS:\n            self.is_previous = label and number == current_number - 1\n            self.is_next = label and number == current_number + 1\n\n        self.url = utils.get_querystring_for_page(\n            request, number, self.querystring_key, default_number=default_number\n        )\n        path = iri_to_uri(override_path or request.path)\n        self.path = f\"{path}{self.url}\"\n\n    def render_link(self):\n        \"\"\"Render the page as a link.\"\"\"\n        extra_context = {\n            'add_nofollow': settings.ADD_NOFOLLOW,\n            'page': self,\n            'querystring_key': self.querystring_key,\n        }\n        if self.is_current:\n            template_name = 'el_pagination/current_link.html'\n        else:\n            template_name = 'el_pagination/page_link.html'\n            if settings.USE_NEXT_PREVIOUS_LINKS:\n                if self.is_previous:\n                    template_name = 'el_pagination/previous_link.html'\n                if self.is_next:\n                    template_name = 'el_pagination/next_link.html'\n        if template_name not in _template_cache:\n            _template_cache[template_name] = loader.get_template(template_name)\n        template = _template_cache[template_name]\n        with self.context.push(**extra_context):\n            return template.render(self.context.flatten())\n\n\nclass PageList:\n    \"\"\"A sequence of endless pages.\"\"\"\n\n    def __init__(\n        self,\n        request,\n        page,\n        querystring_key,\n        context,\n        default_number=None,\n        override_path=None,\n    ):\n        self._request = request\n        self._page = page\n        self.context = context\n        self.context['request'] = request\n        if default_number is None:\n            self._default_number = 1\n        else:\n            self._default_number = int(default_number)\n        self._querystring_key = querystring_key\n        self._override_path = override_path\n        self._pages_list = []\n\n    def _endless_page(self, number, label=None):\n        \"\"\"Factory function that returns a *ELPage* instance.\n\n        This method works just like a partial constructor.\n        \"\"\"\n        return ELPage(\n            self._request,\n            number,\n            self._page.number,\n            len(self),\n            self._querystring_key,\n            label=label,\n            default_number=self._default_number,\n            override_path=self._override_path,\n            context=self.context,\n        )\n\n    def __getitem__(self, value):\n        # The type conversion is required here because in templates Django\n        # performs a dictionary lookup before the attribute lookups\n        # (when a dot is encountered).\n        try:\n            value = int(value)\n        except (TypeError, ValueError) as exc:\n            # A TypeError says to django to continue with an attribute lookup.\n            raise TypeError from exc\n        if 1 <= value <= len(self):\n            return self._endless_page(value)\n        raise IndexError('page list index out of range')\n\n    def __len__(self):\n        \"\"\"The length of the sequence is the total number of pages.\"\"\"\n        return self._page.paginator.num_pages\n\n    def __iter__(self):\n        \"\"\"Iterate over all the endless pages (from first to last).\"\"\"\n        for i in range(len(self)):\n            yield self[i + 1]\n\n    def __str__(self):\n        \"\"\"Return a rendered Digg-style pagination (by default).\n\n        The callable *settings.PAGE_LIST_CALLABLE* can be used to customize\n        how the pages are displayed. The callable takes the current page number\n        and the total number of pages, and must return a sequence of page\n        numbers that will be displayed. The sequence can contain other values:\n\n            - *'previous'*: will display the previous page in that position;\n            - *'next'*: will display the next page in that position;\n            - *'first'*: will display the first page as an arrow;\n            - *'last'*: will display the last page as an arrow;\n            - *None*: a separator will be displayed in that position.\n\n        Here is an example of custom calable that displays the previous page,\n        then the first page, then a separator, then the current page, and\n        finally the last page::\n\n            def get_page_numbers(current_page, num_pages):\n                return ('previous', 1, None, current_page, 'last')\n\n        If *settings.PAGE_LIST_CALLABLE* is None an internal callable is used,\n        generating a Digg-style pagination. The value of\n        *settings.PAGE_LIST_CALLABLE* can also be a dotted path to a callable.\n        \"\"\"\n        return ''\n\n    def get_pages_list(self):\n        if not self._pages_list:\n            callable_or_path = settings.PAGE_LIST_CALLABLE\n            if callable_or_path:\n                if callable(callable_or_path):\n                    pages_callable = callable_or_path\n                else:\n                    pages_callable = loaders.load_object(callable_or_path)\n            else:\n                pages_callable = utils.get_page_numbers\n            pages = []\n            for item in pages_callable(self._page.number, len(self)):\n                if item is None:\n                    pages.append(None)\n                elif item == 'previous':\n                    pages.append(self.previous())\n                elif item == 'next':\n                    pages.append(self.next())\n                elif item == 'first':\n                    pages.append(self.first_as_arrow())\n                elif item == 'last':\n                    pages.append(self.last_as_arrow())\n                else:\n                    pages.append(self[item])\n            self._pages_list = pages\n        return self._pages_list\n\n    def get_rendered(self):\n        if len(self) > 1:\n            template = loader.get_template('el_pagination/show_pages.html')\n            with self.context.push(pages=self.get_pages_list()):\n                return template.render(self.context.flatten())\n        return ''\n\n    def current(self):\n        \"\"\"Return the current page.\"\"\"\n        return self._endless_page(self._page.number)\n\n    def current_start_index(self):\n        \"\"\"Return the 1-based index of the first item on the current page.\"\"\"\n        return self._page.start_index()\n\n    def current_end_index(self):\n        \"\"\"Return the 1-based index of the last item on the current page.\"\"\"\n        return self._page.end_index()\n\n    def total_count(self):\n        \"\"\"Return the total number of objects, across all pages.\"\"\"\n        return self._page.paginator.count\n\n    def first(self, label=None):\n        \"\"\"Return the first page.\"\"\"\n        return self._endless_page(1, label=label)\n\n    def last(self, label=None):\n        \"\"\"Return the last page.\"\"\"\n        return self._endless_page(len(self), label=label)\n\n    def first_as_arrow(self):\n        \"\"\"Return the first page as an arrow.\n\n        The page label (arrow) is defined in ``settings.FIRST_LABEL``.\n        \"\"\"\n        return self.first(label=settings.FIRST_LABEL)\n\n    def last_as_arrow(self):\n        \"\"\"Return the last page as an arrow.\n\n        The page label (arrow) is defined in ``settings.LAST_LABEL``.\n        \"\"\"\n        return self.last(label=settings.LAST_LABEL)\n\n    def previous(self):\n        \"\"\"Return the previous page.\n\n        The page label is defined in ``settings.PREVIOUS_LABEL``.\n        Return an empty string if current page is the first.\n        \"\"\"\n        if self._page.has_previous():\n            return self._endless_page(\n                self._page.previous_page_number(), label=settings.PREVIOUS_LABEL\n            )\n        return ''\n\n    def next(self):\n        \"\"\"Return the next page.\n\n        The page label is defined in ``settings.NEXT_LABEL``.\n        Return an empty string if current page is the last.\n        \"\"\"\n        if self._page.has_next():\n            return self._endless_page(\n                self._page.next_page_number(), label=settings.NEXT_LABEL\n            )\n        return ''\n\n    def paginated(self):\n        \"\"\"Return True if this page list contains more than one page.\"\"\"\n        return len(self) > 1\n\n    def per_page_number(self):\n        \"\"\"Return the numbers of objects are normally display in per page.\"\"\"\n        return self._page.paginator.per_page\n"
  },
  {
    "path": "el_pagination/paginators.py",
    "content": "\"\"\"Customized Django paginators.\"\"\"\n\nfrom math import ceil\n\nfrom django.core.paginator import EmptyPage, Page, PageNotAnInteger, Paginator\n\n\nclass CustomPage(Page):\n    \"\"\"Handle different number of items on the first page.\"\"\"\n\n    def start_index(self):\n        \"\"\"Return the 1-based index of the first item on this page.\"\"\"\n        paginator = self.paginator\n        # Special case, return zero if no items.\n        if paginator.count == 0:\n            return 0\n        if self.number == 1:\n            return 1\n        return (self.number - 2) * paginator.per_page + paginator.first_page + 1\n\n    def end_index(self):\n        \"\"\"Return the 1-based index of the last item on this page.\"\"\"\n        paginator = self.paginator\n        # Special case for the last page because there can be orphans.\n        if self.number == paginator.num_pages:\n            return paginator.count\n        return (self.number - 1) * paginator.per_page + paginator.first_page\n\n\nclass BasePaginator(Paginator):\n    \"\"\"A base paginator class subclassed by the other real paginators.\n\n    Handle different number of items on the first page.\n    \"\"\"\n\n    def __init__(self, object_list, per_page, **kwargs):\n        self._num_pages = None\n        if 'first_page' in kwargs:\n            self.first_page = kwargs.pop('first_page')\n        else:\n            self.first_page = per_page\n        super().__init__(object_list, per_page, **kwargs)\n\n    def get_current_per_page(self, number):\n        return self.first_page if number == 1 else self.per_page\n\n\nclass DefaultPaginator(BasePaginator):\n    \"\"\"The default paginator used by this application.\"\"\"\n\n    def page(self, number):\n        number = self.validate_number(number)\n        if number == 1:\n            bottom = 0\n        else:\n            bottom = (number - 2) * self.per_page + self.first_page\n        top = bottom + self.get_current_per_page(number)\n        if top + self.orphans >= self.count:\n            top = self.count\n        return CustomPage(self.object_list[bottom:top], number, self)\n\n    def _get_num_pages(self):\n        if self._num_pages is None:\n            if self.count == 0 and not self.allow_empty_first_page:\n                self._num_pages = 0\n            else:\n                hits = max(0, self.count - self.orphans - self.first_page)\n                try:\n                    self._num_pages = int(ceil(hits / float(self.per_page))) + 1\n                except ZeroDivisionError:\n                    self._num_pages = 0  # fallback to a safe value\n        return self._num_pages\n\n    num_pages = property(_get_num_pages)\n\n\nclass LazyPaginatorCustomPage(Page):\n    \"\"\"Handle different number of items on the first page.\"\"\"\n\n    def start_index(self):\n        \"\"\"Return the 1-based index of the first item on this page.\"\"\"\n        paginator = self.paginator\n        if self.number == 1:\n            return 1\n        return (self.number - 2) * paginator.per_page + paginator.first_page + 1\n\n    def end_index(self):\n        \"\"\"Return the 1-based index of the last item on this page.\"\"\"\n        paginator = self.paginator\n        return (self.number - 1) * paginator.per_page + paginator.first_page\n\n\nclass LazyPaginator(BasePaginator):\n    \"\"\"Implement lazy pagination.\"\"\"\n\n    def validate_number(self, number):\n        try:\n            number = int(number)\n        except ValueError as exc:\n            raise PageNotAnInteger('That page number is not an integer') from exc\n        if number < 1:\n            raise EmptyPage('That page number is less than 1')\n        return number\n\n    def page(self, number):\n        number = self.validate_number(number)\n        current_per_page = self.get_current_per_page(number)\n        if number == 1:\n            bottom = 0\n        else:\n            bottom = (number - 2) * self.per_page + self.first_page\n        top = bottom + current_per_page\n        # Retrieve more objects to check if there is a next page.\n        objects = list(self.object_list[bottom : top + self.orphans + 1])\n        objects_count = len(objects)\n        if objects_count > (current_per_page + self.orphans):\n            # If another page is found, increase the total number of pages.\n            self._num_pages = number + 1\n            # In any case,  return only objects for this page.\n            objects = objects[:current_per_page]\n        elif (number != 1) and (objects_count <= self.orphans):\n            raise EmptyPage('That page contains no results')\n        else:\n            # This is the last page.\n            self._num_pages = number\n        return LazyPaginatorCustomPage(objects, number, self)\n\n    def _get_count(self):\n        raise NotImplementedError\n\n    count = property(_get_count)\n\n    def _get_num_pages(self):\n        return self._num_pages\n\n    num_pages = property(_get_num_pages)\n\n    def _get_page_range(self):\n        raise NotImplementedError\n\n    page_range = property(_get_page_range)\n"
  },
  {
    "path": "el_pagination/settings.py",
    "content": "# \"\"\"Django Endless Pagination settings file.\"\"\"\n\nfrom django.conf import settings\n\n# How many objects are normally displayed in a page\n# (overwriteable by templatetag).\nPER_PAGE = getattr(settings, 'EL_PAGINATION_PER_PAGE', 10)\n# The querystring key of the page number.\nPAGE_LABEL = getattr(settings, 'EL_PAGINATION_PAGE_LABEL', 'page')\n# See django *Paginator* definition of orphans.\nORPHANS = getattr(settings, 'EL_PAGINATION_ORPHANS', 0)\n\n# If you use the default *show_more* template, here you can customize\n# the content of the loader hidden element.\n# Html is safe here, e.g. you can show your pretty animated gif:\n#    EL_PAGINATION_LOADING = \"\"\"\n#        <img src=\"/static/img/loader.gif\" alt=\"loading\" />\n#    \"\"\"\nLOADING = getattr(settings, 'EL_PAGINATION_LOADING', 'loading')\n\nUSE_NEXT_PREVIOUS_LINKS = getattr(\n    settings, 'EL_PAGINATION_USE_NEXT_PREVIOUS_LINKS', False\n)\n\n# Labels for previous and next page links.\nPREVIOUS_LABEL = getattr(settings, 'EL_PAGINATION_PREVIOUS_LABEL', '&lt;')\nNEXT_LABEL = getattr(settings, 'EL_PAGINATION_NEXT_LABEL', '&gt;')\n\n# Labels for first and last page links.\nFIRST_LABEL = getattr(settings, 'EL_PAGINATION_FIRST_LABEL', '&lt;&lt;')\nLAST_LABEL = getattr(settings, 'EL_PAGINATION_LAST_LABEL', '&gt;&gt;')\n\n# Set to True if your SEO alchemist wants all the links in Digg-style\n# pagination to be ``nofollow``.\nADD_NOFOLLOW = getattr(settings, 'EL_PAGINATION_ADD_NOFOLLOW', False)\n\n# Callable (or dotted path to a callable) returning pages to be displayed.\n# If None, a default callable is used (which produces Digg-style pagination).\nPAGE_LIST_CALLABLE = getattr(settings, 'EL_PAGINATION_PAGE_LIST_CALLABLE', None)\n\n# The default callable returns a sequence of pages producing Digg-style\n# pagination, and depending on the settings below.\nDEFAULT_CALLABLE_EXTREMES = getattr(\n    settings, 'EL_PAGINATION_DEFAULT_CALLABLE_EXTREMES', 3\n)\nDEFAULT_CALLABLE_AROUNDS = getattr(\n    settings, 'EL_PAGINATION_DEFAULT_CALLABLE_AROUNDS', 2\n)\n# Whether or not the first and last pages arrows are displayed.\nDEFAULT_CALLABLE_ARROWS = getattr(\n    settings, 'EL_PAGINATION_DEFAULT_CALLABLE_ARROWS', False\n)\n\n# Template variable name for *page_template* decorator.\nTEMPLATE_VARNAME = getattr(settings, 'EL_PAGINATION_TEMPLATE_VARNAME', 'template')\n\n# If page out of range, throw a 404 exception\nPAGE_OUT_OF_RANGE_404 = getattr(settings, 'EL_PAGINATION_PAGE_OUT_OF_RANGE_404', False)\n"
  },
  {
    "path": "el_pagination/static/el-pagination/js/el-pagination.js",
    "content": "'use strict';\n\n(function ($) {\n\n    $.fn.endlessPaginate = function(options) {\n        var defaults = {\n            // Twitter-style pagination container selector.\n            containerSelector: '.endless_container',\n            // Twitter-style pagination loading selector.\n            loadingSelector: '.endless_loading',\n            // Twitter-style pagination link selector.\n            moreSelector: 'a.endless_more',\n            // Twitter-style pagination content wrapper selector.\n            contentSelector: null,\n            // Digg-style pagination page template selector.\n            pageSelector: '.endless_page_template',\n            // Digg-style pagination link selector.\n            pagesSelector: 'a.endless_page_link',\n            // Callback called when the user clicks to get another page.\n            onClick: function() {},\n            // Callback called when the new page is correctly displayed.\n            onCompleted: function() {},\n            // Set this to true to use the paginate-on-scroll feature.\n            paginateOnScroll: false,\n            // If paginate-on-scroll is on, this margin will be used.\n            paginateOnScrollMargin : 1,\n            // If paginate-on-scroll is on, it is possible to define chunks.\n            paginateOnScrollChunkSize: 0\n        },\n            settings = $.extend(defaults, options);\n\n        var getContext = function(link) {\n            return {\n                key: link.data(\"el-querystring-key\").split(' ')[0],\n                url: link.attr('href')\n            };\n        };\n\n        return this.each(function() {\n            var element = $(this),\n                loadedPages = 1;\n\n            // Twitter-style pagination.\n            element.on('click', settings.moreSelector, function() {\n                var link = $(this),\n                    html_link = link.get(0),\n                    content_wrapper = element.find(settings.contentSelector),\n                    container = link.closest(settings.containerSelector),\n                    loading = container.find(settings.loadingSelector);\n                // Avoid multiple Ajax calls.\n                if (loading.is(':visible')) {\n                    return false;\n                }\n                link.hide();\n                loading.show();\n                var context = getContext(link);\n                // Fire onClick callback.\n                if (settings.onClick.apply(html_link, [context]) !== false) {\n                    var data = 'querystring_key=' + context.key;\n                    // Send the Ajax request.\n                    $.get(context.url, data, function (fragment) {\n                        // Increase the number of loaded pages.\n                        loadedPages += 1;\n\n                        if (!content_wrapper.length) {\n                            // Replace pagination container (the default behavior)\n                            container.before(fragment);\n                            container.remove();\n                        } else {\n                            // Insert the content in the specified wrapper and increment link\n                            content_wrapper.append(fragment);\n                            var nextPage = 'page=' + (loadedPages + 1);\n                            link.attr('href', link.attr('href').replace(/page=\\d+/, nextPage));\n                            link.show();\n                            loading.hide();\n                        }\n\n                        // Fire onCompleted callback.\n                        settings.onCompleted.apply(\n                            html_link, [context, $.trim(fragment)]);\n                    }).fail(function (xhr, textStatus, error) {\n                        // Remove the container left if any\n                        container.remove();\n                    });\n                }\n                return false;\n            });\n\n            // On scroll pagination.\n            if (settings.paginateOnScroll) {\n                var win = $(window),\n                    doc = $(document);\n                doc.on('scroll', function () {\n                    if (doc.height() - win.height() -\n                        win.scrollTop() <= settings.paginateOnScrollMargin) {\n                        // Do not paginate on scroll if chunks are used and\n                        // the current chunk is complete.\n                        var chunckSize = settings.paginateOnScrollChunkSize;\n                        if (!chunckSize || loadedPages % chunckSize) {\n                            element.find(settings.moreSelector).trigger('click');\n                        } else {\n                            element.find(settings.moreSelector).addClass('endless_chunk_complete');\n                        }\n                    }\n                });\n            }\n\n            // Digg-style pagination.\n            element.on('click', settings.pagesSelector, function() {\n                var link = $(this),\n                    html_link = link.get(0),\n                    context = getContext(link);\n                // Fire onClick callback.\n                if (settings.onClick.apply(html_link, [context]) !== false) {\n                    var page_template = link.closest(settings.pageSelector),\n                        data = 'querystring_key=' + context.key;\n                    // Send the Ajax request.\n                    page_template.load(context.url, data, function(fragment) {\n                        // Fire onCompleted callback.\n                        settings.onCompleted.apply(\n                            html_link, [context, $.trim(fragment)]);\n                    });\n                }\n                return false;\n            });\n        });\n    };\n\n    $.endlessPaginate = function(options) {\n        return $('body').endlessPaginate(options);\n    };\n\n})(jQuery);\n"
  },
  {
    "path": "el_pagination/templates/el_pagination/current_link.html",
    "content": "<span class=\"endless_page_current\">\n    <strong>{{ page.label|safe }}</strong>\n</span>\n"
  },
  {
    "path": "el_pagination/templates/el_pagination/next_link.html",
    "content": "<a href=\"{{ page.path }}\"\n   rel=\"next{% if add_nofollow %} nofollow{% endif %}\"\n   data-el-querystring-key=\"{{ querystring_key }}\"\n   class=\"endless_page_link\">{{ page.label|safe }}</a>\n"
  },
  {
    "path": "el_pagination/templates/el_pagination/page_link.html",
    "content": "<a href=\"{{ page.path }}\"\n    {% if add_nofollow %}rel=\"nofollow\"{% endif %}\n    data-el-querystring-key=\"{{ querystring_key }}\"\n    class=\"endless_page_link\">{{ page.label|safe }}</a>\n"
  },
  {
    "path": "el_pagination/templates/el_pagination/previous_link.html",
    "content": "<a href=\"{{ page.path }}\"\n   rel=\"prev{% if add_nofollow %} nofollow{% endif %}\"\n   data-el-querystring-key=\"{{ querystring_key }}\"\n   class=\"endless_page_link\">{{ page.label|safe }}</a>\n"
  },
  {
    "path": "el_pagination/templates/el_pagination/show_more.html",
    "content": "{% load i18n %}\n{% if querystring %}\n    <div class=\"endless_container\">\n        <a class=\"endless_more{% if class_name %} {{ class_name }}{% endif %}\" href=\"{{ path }}{{ querystring }}\"\n            data-el-querystring-key=\"{{ querystring_key }}\">{% if label %}{{ label|safe }}{% else %}{% trans \"more\" %}{% endif %}</a>\n        <div class=\"endless_loading\" style=\"display: none;\">{{ loading|safe }}</div>\n    </div>\n{% endif %}\n"
  },
  {
    "path": "el_pagination/templates/el_pagination/show_more_table.html",
    "content": "{% load i18n %}\n{% if querystring %}\n<tr class=\"endless_container\">\n    <td colspan=\"100%\">\n        <a class=\"endless_more{% if class_name %} {{ class_name }}{% endif %}\" href=\"{{ path }}{{ querystring }}\"\n            data-el-querystring-key=\"{{ querystring_key }}\">{% if label %}{{ label|safe }}{% else %}{% trans \"more\" %}{% endif %}</a>\n        <span class=\"endless_loading\" style=\"display: none;\">{{ loading|safe }}</span>\n    </td>\n</tr>\n{% endif %}\n"
  },
  {
    "path": "el_pagination/templates/el_pagination/show_pages.html",
    "content": "{% for page in pages %}\n    {{ page.render_link|default:'<span class=\"endless_separator\">...</span>' }}\n{% endfor %}\n"
  },
  {
    "path": "el_pagination/templatetags/__init__.py",
    "content": ""
  },
  {
    "path": "el_pagination/templatetags/el_pagination_tags.py",
    "content": "\"\"\"Django EL(Endless) Pagination template tags.\"\"\"\n\nimport re\n\nfrom django import template\nfrom django.http import Http404\nfrom django.utils.encoding import force_str, iri_to_uri\n\nfrom el_pagination import models, settings, utils\nfrom el_pagination.paginators import DefaultPaginator, EmptyPage, LazyPaginator\n\nregister = template.Library()\n\n__all__ = ['register']\n\nPAGINATE_EXPRESSION = re.compile(\n    r\"\"\"\n    ^   # Beginning of line.\n    (((?P<first_page>\\w+)\\,)?(?P<per_page>\\w+(\\.\\w+)?)\\s+)?  # First page, per page.\n    (?P<objects>[\\.\\w]+)  # Objects / queryset.\n    (\\s+starting\\s+from\\s+page\\s+(?P<number>[\\-]?\\d+|\\w+))?  # Page start.\n    (\\s+using\\s+(?P<key>[\\\"\\'\\-\\w]+))?  # Querystring key.\n    (\\s+with\\s+(?P<override_path>[\\\"\\'\\/\\w]+))?  # Override path.\n    (\\s+as\\s+(?P<var_name>\\w+))?  # Context variable name.\n    $   # End of line.\n\"\"\",\n    re.VERBOSE,\n)\n\nSHOW_CURRENT_NUMBER_EXPRESSION = re.compile(\n    r\"\"\"\n    ^   # Beginning of line.\n    (starting\\s+from\\s+page\\s+(?P<number>\\w+))?\\s*  # Page start.\n    (using\\s+(?P<key>[\\\"\\'\\-\\w]+))?\\s*  # Querystring key.\n    (as\\s+(?P<var_name>\\w+))?  # Context variable name.\n    $   # End of line.\n\"\"\",\n    re.VERBOSE,\n)\n\n\n@register.tag\ndef paginate(parser, token, paginator_class=None):\n    \"\"\"Paginate objects.\n\n    Usage:\n\n    .. code-block:: html+django\n\n        {% paginate entries %}\n\n    After this call, the *entries* variable in the template context is replaced\n    by only the entries of the current page.\n\n    You can also keep your *entries* original variable (usually a queryset)\n    and add to the context another name that refers to entries of the current\n    page, e.g.:\n\n    .. code-block:: html+django\n\n        {% paginate entries as page_entries %}\n\n    The *as* argument is also useful when a nested context variable is provided\n    as queryset. In this case, and only in this case, the resulting variable\n    name is mandatory, e.g.:\n\n    .. code-block:: html+django\n\n        {% paginate entries.all as entries %}\n\n    The number of paginated entries is taken from settings, but you can\n    override the default locally, e.g.:\n\n    .. code-block:: html+django\n\n        {% paginate 20 entries %}\n\n    Of course you can mix it all:\n\n    .. code-block:: html+django\n\n        {% paginate 20 entries as paginated_entries %}\n\n    By default, the first page is displayed the first time you load the page,\n    but you can change this, e.g.:\n\n    .. code-block:: html+django\n\n        {% paginate entries starting from page 3 %}\n\n    When changing the default page, it is also possible to reference the last\n    page (or the second last page, and so on) by using negative indexes, e.g:\n\n    .. code-block:: html+django\n\n        {% paginate entries starting from page -1 %}\n\n    This can be also achieved using a template variable that was passed to the\n    context, e.g.:\n\n    .. code-block:: html+django\n\n        {% paginate entries starting from page page_number %}\n\n    If the passed page number does not exist, the first page is displayed.\n\n    If you have multiple paginations in the same page, you can change the\n    querydict key for the single pagination, e.g.:\n\n    .. code-block:: html+django\n\n        {% paginate entries using article_page %}\n\n    In this case *article_page* is intended to be a context variable, but you\n    can hardcode the key using quotes, e.g.:\n\n    .. code-block:: html+django\n\n        {% paginate entries using 'articles_at_page' %}\n\n    Again, you can mix it all (the order of arguments is important):\n\n    .. code-block:: html+django\n\n        {% paginate 20 entries\n            starting from page 3 using page_key as paginated_entries %}\n\n    Additionally you can pass a path to be used for the pagination:\n\n    .. code-block:: html+django\n\n        {% paginate 20 entries\n            using page_key with pagination_url as paginated_entries %}\n\n    This way you can easily create views acting as API endpoints, and point\n    your Ajax calls to that API. In this case *pagination_url* is considered a\n    context variable, but it is also possible to hardcode the URL, e.g.:\n\n    .. code-block:: html+django\n\n        {% paginate 20 entries with \"/mypage/\" %}\n\n    If you want the first page to contain a different number of items than\n    subsequent pages, you can separate the two values with a comma, e.g. if\n    you want 3 items on the first page and 10 on other pages:\n\n    .. code-block:: html+django\n\n    {% paginate 3,10 entries %}\n\n    You must use this tag before calling the {% show_more %} one.\n    \"\"\"\n    # Validate arguments.\n    try:\n        tag_name, tag_args = token.contents.split(None, 1)\n    except ValueError as exc:\n        tag = token.contents.split()[0]\n        msg = f'{tag!r} tag requires arguments'\n        raise template.TemplateSyntaxError(msg) from exc\n\n    # Use a regexp to catch args.\n    match = PAGINATE_EXPRESSION.match(tag_args)\n    if match is None:\n        msg = f'Invalid arguments for {tag_name!r} tag'\n        raise template.TemplateSyntaxError(msg)\n\n    # Retrieve objects.\n    kwargs = match.groupdict()\n    objects = kwargs.pop('objects')\n\n    # The variable name must be present if a nested context variable is passed.\n    if '.' in objects and kwargs['var_name'] is None:\n        msg = (\n            '%(tag)r tag requires a variable name `as` argumnent if the '\n            'queryset is provided as a nested context variable (%(objects)s). '\n            'You must either pass a direct queryset (e.g. taking advantage '\n            'of the `with` template tag) or provide a new variable name to '\n            'store the resulting queryset (e.g. `%(tag)s %(objects)s as '\n            'objects`).'\n        ) % {'tag': tag_name, 'objects': objects}\n        raise template.TemplateSyntaxError(msg)\n\n    # Call the node.\n    return PaginateNode(paginator_class, objects, **kwargs)\n\n\n@register.tag\ndef lazy_paginate(parser, token):\n    \"\"\"Lazy paginate objects.\n\n    Paginate objects without hitting the database with a *select count* query.\n\n    Use this the same way as *paginate* tag when you are not interested\n    in the total number of pages.\n    \"\"\"\n    return paginate(parser, token, paginator_class=LazyPaginator)\n\n\nclass PaginateNode(template.Node):\n    \"\"\"Add to context the objects of the current page.\n\n    Also add the Django paginator's *page* object.\n    \"\"\"\n\n    def __init__(\n        self,\n        paginator_class,\n        objects,\n        first_page=None,\n        per_page=None,\n        var_name=None,\n        number=None,\n        key=None,\n        override_path=None,\n    ):\n        self.paginator = paginator_class or DefaultPaginator\n        self.objects = template.Variable(objects)\n\n        # If *var_name* is not passed, then the queryset name will be used.\n        self.var_name = objects if var_name is None else var_name\n\n        # If *per_page* is not passed then the default value form settings\n        # will be used.\n        self.per_page_variable = None\n        if per_page is None:\n            self.per_page = settings.PER_PAGE\n        elif per_page.isdigit():\n            self.per_page = int(per_page)\n        else:\n            self.per_page_variable = template.Variable(per_page)\n\n        # Handle first page: if it is not passed then *per_page* is used.\n        self.first_page_variable = None\n        if first_page is None:\n            self.first_page = None\n        elif first_page.isdigit():\n            self.first_page = int(first_page)\n        else:\n            self.first_page_variable = template.Variable(first_page)\n\n        # Handle page number when it is not specified in querystring.\n        self.page_number_variable = None\n        if number is None:\n            self.page_number = 1\n        else:\n            try:\n                self.page_number = int(number)\n            except ValueError:\n                self.page_number_variable = template.Variable(number)\n\n        # Set the querystring key attribute.\n        self.querystring_key_variable = None\n        if key is None:\n            self.querystring_key = settings.PAGE_LABEL\n        elif key[0] in ('\"', \"'\") and key[-1] == key[0]:\n            self.querystring_key = key[1:-1]\n        else:\n            self.querystring_key_variable = template.Variable(key)\n\n        # Handle *override_path*.\n        self.override_path_variable = None\n\n        if override_path is None:\n            self.override_path = None\n        elif (\n            override_path[0] in ('\"', \"'\") and override_path[-1] == override_path[0]\n        ):  # noqa\n            self.override_path = override_path[1:-1]\n        else:\n            self.override_path_variable = template.Variable(override_path)\n\n    def render(self, context):\n        # Handle page number when it is not specified in querystring.\n        if self.page_number_variable is None:\n            default_number = self.page_number\n        else:\n            default_number = int(self.page_number_variable.resolve(context))\n\n        # Calculate the number of items to show on each page.\n        if self.per_page_variable is None:\n            per_page = self.per_page\n        else:\n            per_page = int(self.per_page_variable.resolve(context))\n\n        # Calculate the number of items to show in the first page.\n        if self.first_page_variable is None:\n            first_page = self.first_page or per_page\n        else:\n            first_page = int(self.first_page_variable.resolve(context))\n\n        # User can override the querystring key to use in the template.\n        # The default value is defined in the settings file.\n        if self.querystring_key_variable is None:\n            querystring_key = self.querystring_key\n        else:\n            querystring_key = self.querystring_key_variable.resolve(context)\n\n        # Retrieve the override path if used.\n        if self.override_path_variable is None:\n            override_path = self.override_path\n        else:\n            override_path = self.override_path_variable.resolve(context)\n\n        # Retrieve the queryset and create the paginator object.\n        objects = self.objects.resolve(context)\n        paginator = self.paginator(\n            objects, per_page, first_page=first_page, orphans=settings.ORPHANS\n        )\n\n        # Normalize the default page number if a negative one is provided.\n        if default_number < 0:\n            default_number = utils.normalize_page_number(\n                default_number, paginator.page_range\n            )\n\n        # The current request is used to get the requested page number.\n        page_number = utils.get_page_number_from_request(\n            context['request'], querystring_key, default=default_number\n        )\n\n        # Get the page.\n        try:\n            page = paginator.page(page_number)\n        except EmptyPage:\n            page = paginator.page(1)\n            if settings.PAGE_OUT_OF_RANGE_404:\n                raise Http404('Page out of range')  # pylint: disable=raise-missing-from\n\n        # Populate the context with required data.\n        data = {\n            'default_number': default_number,\n            'override_path': override_path,\n            'page': page,\n            'querystring_key': querystring_key,\n        }\n        context.update({'endless': data, self.var_name: page.object_list})\n        return ''\n\n\n@register.inclusion_tag('el_pagination/show_more.html', takes_context=True)\ndef show_more(context, label=None, loading=settings.LOADING, class_name=None):\n    \"\"\"Show the link to get the next page in a Twitter-like pagination.\n\n    Usage::\n\n        {% show_more %}\n\n    Alternatively you can override the label passed to the default template::\n\n        {% show_more \"even more\" %}\n\n    You can override the loading text too::\n\n        {% show_more \"even more\" \"working\" %}\n\n    You could pass in the extra CSS style class name as a third argument\n\n       {% show_more \"even more\" \"working\" \"class_name\" %}\n\n    Must be called after ``{% paginate objects %}``.\n    \"\"\"\n    # This template tag could raise a PaginationError: you have to call\n    # *paginate* or *lazy_paginate* before including the showmore template.\n    data = utils.get_data_from_context(context)\n    page = data['page']\n    # show the template only if there is a next page\n    if page.has_next():\n        request = context['request']\n        page_number = page.next_page_number()\n        # Generate the querystring.\n        querystring_key = data['querystring_key']\n        querystring = utils.get_querystring_for_page(\n            request, page_number, querystring_key, default_number=data['default_number']\n        )\n        return {\n            'label': label,\n            'loading': loading,\n            'class_name': class_name,\n            'path': iri_to_uri(data['override_path'] or request.path),\n            'querystring': querystring,\n            'querystring_key': querystring_key,\n            'request': request,\n        }\n    # No next page, nothing to see.\n    return {}\n\n\n@register.inclusion_tag('el_pagination/show_more_table.html', takes_context=True)\ndef show_more_table(context, label=None, loading=settings.LOADING):\n    \"\"\"Show the link to get the next page in a Twitter-like pagination in a\n    template for table.\n    Usage::\n        {% show_more_table %}\n    Alternatively you can override the label passed to the default template::\n        {% show_more_table \"even more\" %}\n    You can override the loading text too::\n        {% show_more_table \"even more\" \"working\" %}\n    Must be called after ``{% paginate objects %}``.\n    \"\"\"\n    # This template tag could raise a PaginationError: you have to call\n    # *paginate* or *lazy_paginate* before including the showmore template.\n    return show_more(context, label, loading)\n\n\n@register.tag\ndef get_pages(parser, token):\n    \"\"\"Add to context the list of page links.\n\n    Usage:\n\n    .. code-block:: html+django\n\n        {% get_pages %}\n\n    This is mostly used for Digg-style pagination.\n    This call inserts in the template context a *pages* variable, as a sequence\n    of page links. You can use *pages* in different ways:\n\n    - just print *pages.get_rendered* and you will get Digg-style pagination displayed:\n\n    .. code-block:: html+django\n\n        {{ pages.get_rendered }}\n\n    - display pages count:\n\n    .. code-block:: html+django\n\n        {{ pages|length }}\n\n    - check if the page list contains more than one page:\n\n    .. code-block:: html+django\n\n        {{ pages.paginated }}\n        {# the following is equivalent #}\n        {{ pages|length > 1 }}\n\n    - get a specific page:\n\n    .. code-block:: html+django\n\n        {# the current selected page #}\n        {{ pages.current }}\n\n        {# the first page #}\n        {{ pages.first }}\n\n        {# the last page #}\n        {{ pages.last }}\n\n        {# the previous page (or nothing if you are on first page) #}\n        {{ pages.previous }}\n\n        {# the next page (or nothing if you are in last page) #}\n        {{ pages.next }}\n\n        {# the third page #}\n        {{ pages.3 }}\n        {# this means page.1 is the same as page.first #}\n\n        {# the 1-based index of the first item on the current page #}\n        {{ pages.current_start_index }}\n\n        {# the 1-based index of the last item on the current page #}\n        {{ pages.current_end_index }}\n\n        {# the total number of objects, across all pages #}\n        {{ pages.total_count }}\n\n        {# the first page represented as an arrow #}\n        {{ pages.first_as_arrow }}\n\n        {# the last page represented as an arrow #}\n        {{ pages.last_as_arrow }}\n\n    - iterate over *pages* to get all pages:\n\n    .. code-block:: html+django\n\n        {% for page in pages %}\n            {# display page link #}\n            {{ page.render_link}}\n\n            {# the page url (beginning with \"?\") #}\n            {{ page.url }}\n\n            {# the page path #}\n            {{ page.path }}\n\n            {# the page number #}\n            {{ page.number }}\n\n            {# a string representing the page (commonly the page number) #}\n            {{ page.label }}\n\n            {# check if the page is the current one #}\n            {{ page.is_current }}\n\n            {# check if the page is the first one #}\n            {{ page.is_first }}\n\n            {# check if the page is the last one #}\n            {{ page.is_last }}\n        {% endfor %}\n\n    You can change the variable name, e.g.:\n\n    .. code-block:: html+django\n\n        {% get_pages as page_links %}\n\n    Must be called after ``{% paginate objects %}``.\n    \"\"\"\n    # Validate args.\n    try:\n        tag_name, args = token.contents.split(None, 1)\n    except ValueError:\n        var_name = 'pages'\n    else:\n        args = args.split()\n        if len(args) == 2 and args[0] == 'as':\n            var_name = args[1]\n        else:\n            msg = f'Invalid arguments for {tag_name!r} tag'\n            raise template.TemplateSyntaxError(msg)\n    # Call the node.\n    return GetPagesNode(var_name)\n\n\nclass GetPagesNode(template.Node):\n    \"\"\"Add the page list to context.\"\"\"\n\n    def __init__(self, var_name):\n        self.var_name = var_name\n\n    def render(self, context):\n        # This template tag could raise a PaginationError: you have to call\n        # *paginate* or *lazy_paginate* before including the getpages template.\n        data = utils.get_data_from_context(context)\n        # Add the PageList instance to the context.\n        context[self.var_name] = models.PageList(\n            context['request'],\n            data['page'],\n            data['querystring_key'],\n            context=context,\n            default_number=data['default_number'],\n            override_path=data['override_path'],\n        )\n        return ''\n\n\n@register.tag\ndef show_pages(parser, token):\n    \"\"\"Show page links.\n\n    Usage:\n\n    .. code-block:: html+django\n\n        {% show_pages %}\n\n    It is just a shortcut for:\n\n    .. code-block:: html+django\n\n        {% get_pages %}\n        {{ pages.get_rendered }}\n\n    You can set ``ENDLESS_PAGINATION_PAGE_LIST_CALLABLE`` in your *settings.py*\n    to a callable, or to a dotted path representing a callable, used to\n    customize the pages that are displayed.\n\n    See the *__unicode__* method of ``endless_pagination.models.PageList`` for\n    a detailed explanation of how the callable can be used.\n\n    Must be called after ``{% paginate objects %}``.\n    \"\"\"\n    # Validate args.\n    if len(token.contents.split()) != 1:\n        tag = token.contents.split()[0]\n        raise template.TemplateSyntaxError(f'{tag!r} tag takes no arguments')\n    # Call the node.\n    return ShowPagesNode()\n\n\nclass ShowPagesNode(template.Node):\n    \"\"\"Show the pagination.\"\"\"\n\n    def render(self, context):\n        # This template tag could raise a PaginationError: you have to call\n        # *paginate* or *lazy_paginate* before including the getpages template.\n        data = utils.get_data_from_context(context)\n        # Return the string representation of the sequence of pages.\n        pages = models.PageList(\n            context['request'],\n            data['page'],\n            data['querystring_key'],\n            default_number=data['default_number'],\n            override_path=data['override_path'],\n            context=context,\n        )\n        return pages.get_rendered()\n\n\n@register.tag\ndef show_current_number(parser, token):\n    \"\"\"Show the current page number, or insert it in the context.\n\n    This tag can for example be useful to change the page title according to\n    the current page number.\n\n    To just show current page number:\n\n    .. code-block:: html+django\n\n        {% show_current_number %}\n\n    If you use multiple paginations in the same page, you can get the page\n    number for a specific pagination using the querystring key, e.g.:\n\n    .. code-block:: html+django\n\n        {% show_current_number using mykey %}\n\n    The default page when no querystring is specified is 1. If you changed it\n    in the `paginate`_ template tag, you have to call  ``show_current_number``\n    according to your choice, e.g.:\n\n    .. code-block:: html+django\n\n        {% show_current_number starting from page 3 %}\n\n    This can be also achieved using a template variable you passed to the\n    context, e.g.:\n\n    .. code-block:: html+django\n\n        {% show_current_number starting from page page_number %}\n\n    You can of course mix it all (the order of arguments is important):\n\n    .. code-block:: html+django\n\n        {% show_current_number starting from page 3 using mykey %}\n\n    If you want to insert the current page number in the context, without\n    actually displaying it in the template, use the *as* argument, i.e.:\n\n    .. code-block:: html+django\n\n        {% show_current_number as page_number %}\n        {% show_current_number\n            starting from page 3 using mykey as page_number %}\n\n    \"\"\"\n    # Validate args.\n    try:\n        tag_name, args = token.contents.split(None, 1)\n    except ValueError:\n        key = None\n        number = None\n        tag_name = token.contents[0]\n        var_name = None\n    else:\n        # Use a regexp to catch args.\n        match = SHOW_CURRENT_NUMBER_EXPRESSION.match(args)\n        if match is None:\n            msg = f'Invalid arguments for {tag_name!r} tag'\n            raise template.TemplateSyntaxError(msg)\n        # Retrieve objects.\n        groupdict = match.groupdict()\n        key = groupdict['key']\n        number = groupdict['number']\n        var_name = groupdict['var_name']\n    # Call the node.\n    return ShowCurrentNumberNode(number, key, var_name)\n\n\nclass ShowCurrentNumberNode(template.Node):\n    \"\"\"Show the page number taken from context.\"\"\"\n\n    def __init__(self, number, key, var_name):\n        # Retrieve the page number.\n        self.page_number_variable = None\n        if number is None:\n            self.page_number = 1\n        elif number.isdigit():\n            self.page_number = int(number)\n        else:\n            self.page_number_variable = template.Variable(number)\n\n        # Get the queystring key.\n        self.querystring_key_variable = None\n        if key is None:\n            self.querystring_key = settings.PAGE_LABEL\n        elif key[0] in ('\"', \"'\") and key[-1] == key[0]:\n            self.querystring_key = key[1:-1]\n        else:\n            self.querystring_key_variable = template.Variable(key)\n\n        # Get the template variable name.\n        self.var_name = var_name\n\n    def render(self, context):\n        # Get the page number to use if it is not specified in querystring.\n        if self.page_number_variable is None:\n            default_number = self.page_number\n        else:\n            default_number = int(self.page_number_variable.resolve(context))\n\n        # User can override the querystring key to use in the template.\n        # The default value is defined in the settings file.\n        if self.querystring_key_variable is None:\n            querystring_key = self.querystring_key\n        else:\n            querystring_key = self.querystring_key_variable.resolve(context)\n\n        # The request object is used to retrieve the current page number.\n        page_number = utils.get_page_number_from_request(\n            context['request'], querystring_key, default=default_number\n        )\n\n        if self.var_name is None:\n            return force_str(page_number)\n        context[self.var_name] = page_number\n        return ''\n"
  },
  {
    "path": "el_pagination/tests/__init__.py",
    "content": "\"\"\"Test model definitions.\"\"\"\n\n\nfrom django.core.management import call_command\n\ncall_command('makemigrations', verbosity=0)\ncall_command('migrate', verbosity=0)\n"
  },
  {
    "path": "el_pagination/tests/integration/__init__.py",
    "content": "\"\"\"Integration tests base objects definitions.\"\"\"\n\n\nimport os\nimport unittest\nfrom contextlib import contextmanager\n\nfrom django.contrib.staticfiles.testing import StaticLiveServerTestCase\nfrom django.http import QueryDict\nfrom django.urls import reverse\n\nfrom selenium.common import exceptions\n\n# from selenium.webdriver import Firefox\nfrom selenium.webdriver.firefox.options import Options\nfrom selenium.webdriver.firefox.webdriver import WebDriver\nfrom selenium.webdriver.support import ui\n\n# Disable selenium as default. Difficult to setup for local tests. Must be enabled\n# in CI environment.\nUSE_SELENIUM = os.getenv('USE_SELENIUM', 0) in (1, True, '1')\n\n\ndef setup_package():\n    \"\"\"Set up the Selenium driver once for all tests.\"\"\"\n    # Just skipping *setup_package* and *teardown_package* generates an\n    # uncaught exception under Python 2.6.\n    if USE_SELENIUM:\n        # Create a Selenium browser instance.\n        options = Options()\n        options.add_argument('-headless')\n        selenium = SeleniumTestCase.selenium = WebDriver(options=options)\n        selenium.maximize_window()\n        SeleniumTestCase.wait = ui.WebDriverWait(selenium, 5)\n        SeleniumTestCase.selenium.implicitly_wait(3)\n\n\ndef teardown_package():\n    \"\"\"Quit the Selenium driver.\"\"\"\n    if USE_SELENIUM:\n        SeleniumTestCase.selenium.quit()\n\n\n@unittest.skipIf(\n    not USE_SELENIUM,\n    'excluding integration tests: environment variable USE_SELENIUM is not set.')\nclass SeleniumTestCase(StaticLiveServerTestCase):\n    \"\"\"Base test class for integration tests.\"\"\"\n\n    PREVIOUS = '<'\n    NEXT = '>'\n    MORE = 'More results'\n    selector = 'div.{0} > h4'\n\n    def setUp(self):\n        self.url = self.live_server_url + reverse(self.view_name)\n\n        # Give the browser a little time; Firefox sometimes throws\n        # random errors if you hit it too soon\n        # time.sleep(1)\n\n    #     @classmethod\n    #     def setUpClass(cls):\n    #         if not SHOW_BROWSER:\n    #             # start display\n    #             cls.xvfb = Xvfb(width=1280, height=720)\n    #             cls.xvfb.start()\n\n    #         # Create a Selenium browser instance.\n    #         cls.browser = os.getenv('SELENIUM_BROWSER', 'firefox')\n    #         # start browser\n    #         if cls.browser == 'firefox':\n    #             cls.selenium = webdriver.Firefox()\n    #         elif cls.browser == 'htmlunit':\n    #             cls.selenium = webdriver.Remote(desired_capabilities=DesiredCapabilities.HTMLUNITWITHJS)\n    #         elif cls.browser == 'iphone':\n    #             command_executor = \"http://127.0.0.1:3001/wd/hub\"\n    #             cls.selenium = webdriver.Remote(command_executor=command_executor,\n    #                 desired_capabilities=DesiredCapabilities.IPHONE)\n    #         elif cls.browser == 'safari':\n    #             cls.selenium = webdriver.Remote(desired_capabilities={\n    #                 \"browserName\": \"safari\", \"version\": \"\",\n    #                 \"platform\": \"MAC\", \"javascriptEnabled\": True})\n    #         else:\n    #             cls.selenium = webdriver.Chrome()\n    #         cls.selenium.maximize_window()\n    #         cls.wait = ui.WebDriverWait(cls.selenium, 10)\n    #         cls.selenium.implicitly_wait(3)\n    #\n    #         super(SeleniumTestCase, cls).setUpClass()\n\n    #     @classmethod\n    #     def tearDownClass(cls):\n    #         # stop browser\n    #         cls.selenium.quit()\n    #         super(SeleniumTestCase, cls).tearDownClass()\n    #         if not SHOW_BROWSER:\n    #             # stop display\n    #             cls.xvfb.stop()\n\n    def get(self, url=None, data=None, **kwargs):\n        \"\"\"Load a web page in the current browser session.\n\n        If *url* is None, *self.url* is used.\n        The querydict can be expressed providing *data* or *kwargs*.\n        \"\"\"\n        if url is None:\n            url = self.url\n\n        querydict = QueryDict('', mutable=True)\n        if data is not None:\n            querydict.update(data)\n        querydict.update(kwargs)\n        path = f'{url}?{querydict.urlencode()}'\n\n        # the following javascript scrolls down the entire page body.  Since Twitter\n        # uses \"infinite scrolling\", more content will be added to the bottom of the\n        # DOM as you scroll... since it is in the loop, it will scroll down up to 100\n        # times.\n        # for _ in range(100):\n        #     self.selenium.execute_script(\"window.scrollTo(0, document.body.scrollHeight);\")\n\n        # print all of the page source that was loaded\n        # print self.selenium.page_source.encode(\"utf-8\")\n        return self.selenium.get(path)\n\n    def wait_ajax(self):\n        \"\"\"Wait for the document to be ready.\"\"\"\n        def document_ready(driver):\n            script = \"\"\"\n                return (\n                    document.readyState === 'complete' &&\n                    jQuery.active === 0 &&\n                    typeof jQuery != 'undefined'\n                );\n            \"\"\"\n            return driver.execute_script(script)\n\n        self.wait.until(document_ready)\n        return self.wait\n\n    def click_link(self, text, index=0):\n        \"\"\"Click the link with the given *text* and *index*.\"\"\"\n        link = self.selenium.find_elements_by_link_text(str(text))[index]\n        link.click()\n        return link\n\n    def scroll_down(self):\n        \"\"\"Scroll down to the bottom of the page.\"\"\"\n        script = 'window.scrollTo(0, document.body.scrollHeight);'\n        self.selenium.execute_script(script)\n\n    def get_current_elements(self, class_name, driver=None):\n        \"\"\"Return the range of current elements as a list of numbers.\"\"\"\n        elements = []\n        selector = self.selector.format(class_name)\n        if driver is None:\n            driver = self.selenium\n        for element in driver.find_elements_by_css_selector(selector):\n            elements.append(int(element.text.split()[1]))\n        return elements\n\n    def asserLinksEqual(self, count, text):\n        \"\"\"Assert the page contains *count* links with given *text*.\"\"\"\n        def link_condition_attended(driver):\n            links = driver.find_elements_by_link_text(str(text))\n            return len(links) == count\n        self.wait.until(link_condition_attended)\n        return self.wait\n\n    def assertElements(self, class_name, elements):\n        \"\"\"Assert the current page contains the given *elements*.\"\"\"\n        current_elements = self.get_current_elements(class_name)\n        self.assertSequenceEqual(\n            elements, current_elements, (\n                'Elements differ: {expected} != {actual}\\n'\n                'Class name: {class_name}\\n'\n                'Expected elements: {expected}\\n'\n                'Actual elements: {actual}'\n            ).format(\n                actual=current_elements,\n                expected=elements,\n                class_name=class_name,\n            )\n        )\n\n    @contextmanager\n    def assertNewElements(self, class_name, new_elements):\n        \"\"\"Fail when new elements are not found in the page.\"\"\"\n        def new_elements_loaded(driver):\n            elements = self.get_current_elements(class_name, driver=driver)\n            return elements == new_elements\n        yield\n        try:\n            self.wait_ajax().until(new_elements_loaded)\n        except exceptions.TimeoutException:\n            self.assertElements(class_name, new_elements)\n\n    @contextmanager\n    def assertSameURL(self):\n        \"\"\"Assert the URL does not change after executing the yield block.\"\"\"\n        current_url = self.selenium.current_url\n        yield\n        self.wait_ajax()\n        self.assertEqual(current_url, self.selenium.current_url)\n"
  },
  {
    "path": "el_pagination/tests/integration/test_callbacks.py",
    "content": "\"\"\"Javascript callbacks integration tests.\"\"\"\n\n\n\nfrom el_pagination.tests.integration import SeleniumTestCase\n\n\nclass CallbacksTest(SeleniumTestCase):\n\n    view_name = 'callbacks'\n\n    def notifications_loaded(self, driver):\n        return driver.find_elements_by_id('fragment')\n\n    def assertNotificationsEqual(self, notifications):\n        \"\"\"Assert the given *notifications* equal the ones in the DOM.\"\"\"\n        self.wait_ajax().until(self.notifications_loaded)\n        find = self.selenium.find_element_by_id\n        for key, value in notifications.items():\n            self.assertEqual(value, find(key).text)\n\n    def test_can_navigate_site(self):\n        self.selenium.get(self.live_server_url)  # use the live server url\n        assert 'Testing project - Django Endless Pagination' in \\\n            self.selenium.title\n\n    def test_on_click(self):\n        # Ensure the onClick callback is correctly called.\n        self.get()\n        self.click_link(2)\n        self.assertNotificationsEqual({\n            'onclick': 'Object 1',\n            'onclick-label': '2',\n            'onclick-url': '/callbacks/?page=2',\n            'onclick-key': 'page',\n        })\n\n    def test_on_completed(self):\n        # Ensure the onCompleted callback is correctly called.\n        self.get(page=10)\n        self.click_link(1)\n        self.assertNotificationsEqual({\n            'oncompleted': 'Object 1',\n            'oncompleted-label': '1',\n            'oncompleted-url': '/callbacks/',\n            'oncompleted-key': 'page',\n            'fragment': 'Object 3',\n        })\n"
  },
  {
    "path": "el_pagination/tests/integration/test_chunks.py",
    "content": "\"\"\"On scroll chunks integration tests.\"\"\"\n\n\n\nfrom el_pagination.tests.integration import SeleniumTestCase\n\n\nclass ChunksPaginationTest(SeleniumTestCase):\n\n    view_name = 'chunks'\n\n    def test_new_elements_loaded(self):\n        # Ensure new pages are loaded on scroll.\n        self.get()\n        with self.assertNewElements('object', range(1, 11)):\n            with self.assertNewElements('item', range(1, 11)):\n                self.scroll_down()\n\n    def test_url_not_changed(self):\n        # Ensure the request is done using Ajax (the page does not refresh).\n        self.get()\n        with self.assertSameURL():\n            self.scroll_down()\n\n    def test_direct_link(self):\n        # Ensure direct links work.\n        self.get(data={'page': 2, 'items-page': 3})\n        current_url = self.selenium.current_url\n        self.assertElements('object', range(6, 11))\n        self.assertElements('item', range(11, 16))\n        self.assertIn('page=2', current_url)\n        self.assertIn('items-page=3', current_url)\n\n    def test_subsequent_page(self):\n        # Ensure next page is correctly loaded in a subsequent page, even if\n        # normally it is the last page of the chunk.\n        self.get(page=3)\n        with self.assertNewElements('object', range(11, 21)):\n            self.scroll_down()\n\n    def test_chunks(self):\n        # Ensure new items are not loaded on scroll if the chunk is complete.\n        self.get()\n        while len(self.get_current_elements('item')) < 20:\n            self.scroll_down()\n            self.wait_ajax()\n        self.scroll_down()\n        self.wait_ajax()\n        self.assertElements('object', range(1, 16))\n        self.assertElements('item', range(1, 21))\n"
  },
  {
    "path": "el_pagination/tests/integration/test_digg.py",
    "content": "\"\"\"Digg-style pagination integration tests.\"\"\"\n\n\n\nfrom el_pagination.tests.integration import SeleniumTestCase\n\n\nclass DiggPaginationTest(SeleniumTestCase):\n\n    view_name = 'digg'\n\n    def test_new_elements_loaded(self):\n        # Ensure a new page is loaded on click.\n        self.get()\n        with self.assertNewElements('object', range(6, 11)):\n            self.click_link(2)\n\n    def test_url_not_changed(self):\n        # Ensure the request is done using Ajax (the page does not refresh).\n        self.get()\n        with self.assertSameURL():\n            self.click_link(2)\n\n    def test_direct_link(self):\n        # Ensure direct links work.\n        self.get(page=4)\n        self.assertElements('object', range(16, 21))\n        self.assertIn('page=4', self.selenium.current_url)\n\n    def test_next(self):\n        # Ensure next page is correctly loaded.\n        self.get()\n        with self.assertSameURL():\n            with self.assertNewElements('object', range(6, 11)):\n                self.click_link(self.NEXT)\n\n    def test_previous(self):\n        # Ensure previous page is correctly loaded.\n        self.get(page=4)\n        with self.assertSameURL():\n            with self.assertNewElements('object', range(11, 16)):\n                self.click_link(self.PREVIOUS)\n\n    def test_no_previous_link_in_first_page(self):\n        # Ensure there is no previous link on the first page.\n        self.get()\n        self.asserLinksEqual(0, self.PREVIOUS)\n\n    def test_no_next_link_in_last_page(self):\n        # Ensure there is no forward link on the last page.\n        self.get(page=10)\n        self.asserLinksEqual(0, self.NEXT)\n\n\nclass DiggPaginationTableTest(DiggPaginationTest):\n\n    view_name = 'digg-table'\n    selector = 'tr.{0} td > h4'\n"
  },
  {
    "path": "el_pagination/tests/integration/test_feed_wrapper.py",
    "content": "\"\"\"Twitter-style pagination feeding an specific content wrapper integration tests.\"\"\"\n\n\n\nimport el_pagination.settings\nfrom el_pagination.tests.integration import SeleniumTestCase\n\n\nclass FeedWrapperPaginationTest(SeleniumTestCase):\n\n    view_name = 'feed-wrapper'\n    selector = 'tbody > tr > td:first-child'\n\n    def test_new_elements_loaded(self):\n        # Ensure a new page is loaded on click.\n        self.get()\n        with self.assertNewElements('object', range(1, 21)):\n            self.click_link(self.MORE)\n\n    def test_url_not_changed(self):\n        # Ensure the request is done using Ajax (the page does not refresh).\n        self.get()\n        with self.assertSameURL():\n            self.click_link(self.MORE)\n\n    def test_direct_link(self):\n        # Ensure direct links work.\n        self.get(page=3)\n        self.assertElements('object', range(21, 31))\n        self.assertIn('page=3', self.selenium.current_url)\n\n    def test_subsequent_page(self):\n        # Ensure next page is correctly loaded in a subsequent page.\n        self.get(page=2)\n        with self.assertNewElements('object', range(11, 31)):\n            self.click_link(self.MORE)\n\n    def test_feed_wrapper__test_multiple_show_more_through_all_pages(self):\n        # Make a 404 error when page is empty\n        el_pagination.settings.PAGE_OUT_OF_RANGE_404 = True\n\n        # Ensure new pages are loaded again and again.\n        self.get()\n        for page in range(2, 6):\n            expected_range = range(1, 10 * page + 1)\n            with self.assertSameURL():\n                with self.assertNewElements('object', expected_range):\n                    self.click_link(self.MORE)\n\n                    # The more link is kept even in the last page, once it\n                    # doesn't know it is the last\n                    self.asserLinksEqual(1, self.MORE)\n\n        # After one more click, the more link itself is removed\n        self.click_link(self.MORE)\n        self.asserLinksEqual(0, self.MORE)\n\n        # Return to initial condition\n        el_pagination.settings.PAGE_OUT_OF_RANGE_404 = False\n\n    def test_no_more_link_in_last_page_opened_directly(self):\n        self.get(page=5)\n        self.asserLinksEqual(0, self.MORE)\n        self.assertElements('object', range(41, 51))\n"
  },
  {
    "path": "el_pagination/tests/integration/test_multiple.py",
    "content": "\"\"\"Multiple pagination integration tests.\"\"\"\n\n\n\nfrom el_pagination.tests.integration import SeleniumTestCase\n\n\nclass MultiplePaginationTest(SeleniumTestCase):\n\n    view_name = 'multiple'\n\n    def test_new_elements_loaded(self):\n        # Ensure a new page is loaded on click for each paginated elements.\n        self.get()\n        with self.assertNewElements('object', range(4, 7)):\n            with self.assertNewElements('item', range(7, 10)):\n                with self.assertNewElements('entry', range(1, 5)):\n                    self.click_link(2, 0)\n                    self.click_link(3, 1)\n                    self.click_link(self.MORE)\n\n    def test_url_not_changed(self):\n        # Ensure the requests are done using Ajax (the page does not refresh).\n        self.get()\n        with self.assertSameURL():\n            self.click_link(2, 0)\n            self.click_link(3, 1)\n            self.click_link(self.MORE)\n\n    def test_direct_link(self):\n        # Ensure direct links work.\n        self.get(data={'objects-page': 3, 'items-page': 4, 'entries-page': 5})\n        self.assertElements('object', range(7, 10))\n        self.assertElements('item', range(10, 13))\n        self.assertElements('entry', range(11, 14))\n        self.assertIn('objects-page=3', self.selenium.current_url)\n        self.assertIn('items-page=4', self.selenium.current_url)\n        self.assertIn('entries-page=5', self.selenium.current_url)\n\n    def test_subsequent_pages(self):\n        # Ensure elements are correctly loaded starting from a subsequent page.\n        self.get(data={'objects-page': 2, 'items-page': 2, 'entries-page': 2})\n        with self.assertNewElements('object', range(1, 4)):\n            with self.assertNewElements('item', range(7, 10)):\n                with self.assertNewElements('entry', range(2, 8)):\n                    self.click_link(self.PREVIOUS, 0)\n                    self.click_link(self.NEXT, 1)\n                    self.click_link(self.MORE)\n\n    def test_no_more_link_in_last_page(self):\n        # Ensure there is no more or forward links on the last pages.\n        self.get(data={'objects-page': 7, 'items-page': 7, 'entries-page': 8})\n        self.asserLinksEqual(0, self.NEXT)\n        self.asserLinksEqual(0, self.MORE)\n"
  },
  {
    "path": "el_pagination/tests/integration/test_onscroll.py",
    "content": "\"\"\"On scroll pagination integration tests.\"\"\"\n\n\n\nfrom el_pagination.tests.integration import SeleniumTestCase\n\n\nclass OnScrollPaginationTest(SeleniumTestCase):\n\n    view_name = 'onscroll'\n\n    def test_new_elements_loaded(self):\n        # Ensure a new page is loaded on scroll.\n        self.get()\n        with self.assertNewElements('object', range(1, 21)):\n            self.scroll_down()\n\n    def test_url_not_changed(self):\n        # Ensure the request is done using Ajax (the page does not refresh).\n        self.get()\n        with self.assertSameURL():\n            self.scroll_down()\n\n    def test_direct_link(self):\n        # Ensure direct links work.\n        self.get(page=3)\n        self.assertElements('object', range(21, 31))\n        self.assertIn('page=3', self.selenium.current_url)\n\n    def test_subsequent_page(self):\n        # Ensure next page is correctly loaded in a subsequent page.\n        self.get(page=2)\n        with self.assertNewElements('object', range(11, 31)):\n            self.scroll_down()\n\n    def test_multiple_show_more(self):\n        # Ensure new pages are loaded again and again.\n        self.get()\n        for page in range(2, 5):\n            expected_range = range(1, 10 * page + 1)\n            with self.assertSameURL():\n                with self.assertNewElements('object', expected_range):\n                    self.scroll_down()\n\n    def test_scrolling_last_page(self):\n        # Ensure scrolling on the last page is a no-op.\n        self.get(page=5)\n        with self.assertNewElements('object', range(41, 51)):\n            self.scroll_down()\n\n\nclass OnScrollPaginationTableTest(OnScrollPaginationTest):\n\n    view_name = 'onscroll-table'\n    selector = 'tr.{0} td > h4'\n"
  },
  {
    "path": "el_pagination/tests/integration/test_twitter.py",
    "content": "\"\"\"Twitter-style pagination integration tests.\"\"\"\n\n\n\nfrom el_pagination.tests.integration import SeleniumTestCase\n\n\nclass TwitterPaginationTest(SeleniumTestCase):\n\n    view_name = 'twitter'\n\n    def test_new_elements_loaded(self):\n        # Ensure a new page is loaded on click.\n        self.get()\n        with self.assertNewElements('object', range(1, 11)):\n            self.click_link(self.MORE)\n\n    def test_url_not_changed(self):\n        # Ensure the request is done using Ajax (the page does not refresh).\n        self.get()\n        with self.assertSameURL():\n            self.click_link(self.MORE)\n\n    def test_direct_link(self):\n        # Ensure direct links work.\n        self.get(page=4)\n        self.assertElements('object', range(16, 21))\n        self.assertIn('page=4', self.selenium.current_url)\n\n    def test_subsequent_page(self):\n        # Ensure next page is correctly loaded in a subsequent page.\n        self.get(page=2)\n        with self.assertNewElements('object', range(6, 16)):\n            self.click_link(self.MORE)\n\n    def test_multiple_show_more(self):\n        # Ensure new pages are loaded again and again.\n        self.get()\n        for page in range(2, 5):\n            expected_range = range(1, 5 * page + 1)\n            with self.assertSameURL():\n                with self.assertNewElements('object', expected_range):\n                    self.click_link(self.MORE)\n\n    def test_no_more_link_in_last_page(self):\n        # Ensure there is no more link on the last page.\n        self.get(page=10)\n        self.asserLinksEqual(0, self.MORE)\n\n\nclass TwitterPaginationTableTest(TwitterPaginationTest):\n\n    view_name = 'twitter-table'\n    selector = 'tr.{0} td > h4'\n"
  },
  {
    "path": "el_pagination/tests/templatetags/__init__.py",
    "content": ""
  },
  {
    "path": "el_pagination/tests/templatetags/test_el_pagination_tags.py",
    "content": "\"\"\"Endless template tags tests.\"\"\"\n\n\n\nimport string\nimport sys\nimport unittest\nimport xml.etree.ElementTree as etree\n\nfrom django.http import Http404\nfrom django.template import Context, Template, TemplateSyntaxError\nfrom django.template.context import make_context\nfrom django.test import TestCase\nfrom django.test.client import RequestFactory\n\nfrom el_pagination import settings\nfrom el_pagination.exceptions import PaginationError\nfrom el_pagination.models import PageList\nfrom project.models import make_model_instances\n\nskip_if_old_etree = unittest.skipIf(\n    sys.version_info < (2, 7), 'XPath not supported by this Python version.')\n\n\nclass TemplateTagsTestMixin(object):\n    \"\"\"Base test mixin for template tags.\"\"\"\n\n    def setUp(self):\n        self.factory = RequestFactory()\n\n    def render(self, request, contents, **kwargs):\n        \"\"\"Render *contents* using given *request*.\n\n        The context data is represented by keyword arguments.\n        Is no keyword arguments are provided, a default context will be used.\n\n        Return the generated HTML and the modified context.\n        \"\"\"\n        template = Template('{% load el_pagination_tags %}' + contents)\n        context_data = kwargs.copy() if kwargs else {'objects': range(47)}\n        context_data['request'] = request\n        context = Context(context_data)\n        if isinstance(context, dict):  # <-- my temporary workaround\n            context = make_context(context, request, autoescape=self.backend.engine.autoescape)\n        html = template.render(context)\n        return html.strip(), context\n\n    def request(self, url='/', page=None, data=None, **kwargs):\n        \"\"\"Return a Django request for the given *page*.\"\"\"\n        querydict = {} if data is None else data\n        querydict.update(kwargs)\n        if page is not None:\n            querydict[settings.PAGE_LABEL] = page\n        return self.factory.get(url, querydict)\n\n\nclass EtreeTemplateTagsTestMixin(TemplateTagsTestMixin):\n    \"\"\"Mixin for template tags returning a rendered HTML.\"\"\"\n\n    def render(self, request, contents, **kwargs):\n        \"\"\"Return the etree root node of the HTML output.\n\n        Does not return the context.\n        \"\"\"\n        html, _ = super().render(\n            request, contents, **kwargs)\n        if html:\n            return etree.fromstring('<html>{0}</html>'.format(html))\n\n\nclass PaginateTestMixin(TemplateTagsTestMixin):\n    \"\"\"Test mixin for *paginate* and *lazy_paginate* tags.\n\n    Subclasses must define *tagname*.\n    \"\"\"\n\n    def assertPaginationNumQueries(self, num_queries, template, queryset=None):\n        \"\"\"Assert the expected *num_queries* are actually executed.\n\n        The given *queryset* is paginated using *template*. If the *queryset*\n        is not given, a default queryset containing 47 model instances is used.\n        In the *template*, the queryset must be referenced as ``objects``.\n\n        Return the resulting list of objects for the current page.\n        \"\"\"\n        if queryset is None:\n            queryset = make_model_instances(47)\n        request = self.request()\n        with self.assertNumQueries(num_queries):\n            _, context = self.render(request, template, objects=queryset)\n            objects = list(context['objects'])\n        return objects\n\n    def assertRangeEqual(self, expected, actual):\n        \"\"\"Assert the *expected* range equals the *actual* one.\"\"\"\n        self.assertListEqual(list(expected), list(actual))\n\n    def render(self, request, contents, **kwargs):\n        text = string.Template(contents).substitute(tagname=self.tagname)\n        return super().render(request, text, **kwargs)\n\n    def test_object_list(self):\n        # Ensure the queryset is correctly updated.\n        template = '{% $tagname objects %}'\n        html, context = self.render(self.request(), template)\n        self.assertRangeEqual(range(settings.PER_PAGE), context['objects'])\n        self.assertEqual('', html)\n\n    def test_per_page_argument(self):\n        # Ensure the queryset reflects the given ``per_page`` argument.\n        template = '{% $tagname 20 objects %}'\n        _, context = self.render(self.request(), template)\n        self.assertRangeEqual(range(20), context['objects'])\n\n    def test_per_page_argument_as_variable(self):\n        # Ensure the queryset reflects the given ``per_page`` argument.\n        # In this case, the argument is provided as context variable.\n        template = '{% $tagname per_page entries %}'\n        _, context = self.render(\n            self.request(), template, entries=range(47), per_page=5)\n        self.assertRangeEqual(range(5), context['entries'])\n\n    def test_first_page_argument(self):\n        # Ensure the queryset reflects the given ``first_page`` argument.\n        template = '{% $tagname 10,20 objects %}'\n        _, context = self.render(self.request(), template)\n        self.assertRangeEqual(range(10), context['objects'])\n        # Check the second page.\n        _, context = self.render(self.request(page=2), template)\n        self.assertRangeEqual(range(10, 30), context['objects'])\n\n    def test_first_page_argument_as_variable(self):\n        # Ensure the queryset reflects the given ``first_page`` argument.\n        # In this case, the argument is provided as context variable.\n        template = '{% $tagname first_page,subsequent_pages entries %}'\n        context_data = {\n            'entries': range(47),\n            'first_page': 1,\n            'subsequent_pages': 40,\n        }\n        _, context = self.render(self.request(), template, **context_data)\n        self.assertSequenceEqual([0], context['entries'])\n        # Check the second page.\n        _, context = self.render(\n            self.request(page=2), template, **context_data)\n        self.assertRangeEqual(range(1, 41), context['entries'])\n\n    def test_starting_from_page_argument(self):\n        # Ensure the queryset reflects the given ``starting_from_page`` arg.\n        template = '{% $tagname 10 objects starting from page 3 %}'\n        _, context = self.render(self.request(), template)\n        self.assertRangeEqual(range(20, 30), context['objects'])\n\n    def test_starting_from_page_argument_as_variable(self):\n        # Ensure the queryset reflects the given ``starting_from_page`` arg.\n        # In this case, the argument is provided as context variable.\n        template = '{% $tagname 10 entries starting from page mypage %}'\n        _, context = self.render(\n            self.request(), template, entries=range(47), mypage=2)\n        self.assertRangeEqual(range(10, 20), context['entries'])\n\n    def test_using_argument(self):\n        # Ensure the template tag uses the given querystring key.\n        template = '{% $tagname 20 objects using \"my-page\" %}'\n        _, context = self.render(\n            self.request(data={'my-page': 2}), template)\n        self.assertRangeEqual(range(20, 40), context['objects'])\n\n    def test_using_argument_as_variable(self):\n        # Ensure the template tag uses the given querystring key.\n        # In this case, the argument is provided as context variable.\n        template = '{% $tagname 20 entries using qskey %}'\n        _, context = self.render(\n            self.request(p=3), template, entries=range(47), qskey='p')\n        self.assertRangeEqual(range(40, 47), context['entries'])\n\n    def test_with_argument(self):\n        # Ensure the context contains the correct override path.\n        template = '{% $tagname 10 objects with \"/mypath/\" %}'\n        _, context = self.render(self.request(), template)\n        self.assertEqual('/mypath/', context['endless']['override_path'])\n\n    def test_with_argument_as_variable(self):\n        # Ensure the context contains the correct override path.\n        # In this case, the argument is provided as context variable.\n        path = '/my/path/'\n        template = '{% $tagname 10 entries with path %}'\n        _, context = self.render(\n            self.request(), template, entries=range(47), path=path)\n        self.assertEqual(path, context['endless']['override_path'])\n\n    def test_as_argument(self):\n        # Ensure it is possible to change the resulting context variable.\n        template = '{% $tagname 20 objects as object_list %}'\n        _, context = self.render(self.request(), template)\n        self.assertRangeEqual(range(20), context['object_list'])\n        # The input queryset has not been changed.\n        self.assertRangeEqual(range(47), context['objects'])\n\n    def test_complete_argument_list(self):\n        # Ensure the tag works providing all the arguments.\n        template = (\n            '{% $tagname 5,10 objects '\n            'starting from page 2 '\n            'using mypage '\n            'with path '\n            'as paginated %}'\n        )\n        _, context = self.render(\n            self.request(), template, objects=range(47), mypage='page-number',\n            path='mypath')\n        self.assertRangeEqual(range(5, 15), context['paginated'])\n        self.assertEqual('mypath', context['endless']['override_path'])\n\n    def test_invalid_arguments(self):\n        # An error is raised if invalid arguments are provided.\n        templates = (\n            '{% $tagname %}',\n            '{% $tagname foo bar spam eggs %}',\n            '{% $tagname 20 objects as object_list using \"mykey\" %}',\n        )\n        request = self.request()\n        for template in templates:\n            with self.assertRaises(TemplateSyntaxError):\n                self.render(request, template)\n\n    def test_invalid_page(self):\n        # The first page is displayed if an invalid page is provided.\n        settings.PAGE_OUT_OF_RANGE_404 = False\n        template = '{% $tagname 5 objects %}'\n        _, context = self.render(self.request(page=0), template)\n        self.assertRangeEqual(range(5), context['objects'])\n\n    def test_invalid_page_with_raise_404_enabled(self):\n        # The 404 error is raised if an invalid page is provided and\n        # env variable PAGE_OUT_OF_RANGE_404 is set to True.\n        settings.PAGE_OUT_OF_RANGE_404 = True\n        template = '{% $tagname 5 objects %}'\n        with self.assertRaises(Http404):\n            self.render(self.request(page=0), template)\n\n    def test_nested_context_variable(self):\n        # Ensure nested context variables are correctly handled.\n        manager = {'all': range(47)}\n        template = '{% $tagname 5 manager.all as objects %}'\n        _, context = self.render(self.request(), template, manager=manager)\n        self.assertRangeEqual(range(5), context['objects'])\n\n    def test_failing_nested_context_variable(self):\n        # An error is raised if a nested context variable is used but no\n        # alias is provided.\n        manager = {'all': range(47)}\n        template = '{% $tagname 5 manager.all %}'\n        with self.assertRaises(TemplateSyntaxError) as cm:\n            self.render(self.request(), template, manager=manager)\n        self.assertIn('manager.all', str(cm.exception))\n\n    def test_multiple_pagination(self):\n        # Ensure multiple pagination works correctly.\n        letters = string.ascii_letters\n        template = (\n            '{% $tagname 10,20 objects %}'\n            '{% $tagname 1 items using items_page %}'\n            '{% $tagname 5 entries.all using \"entries\" as myentries %}'\n        )\n        _, context = self.render(\n            self.request(page=2, entries=3), template,\n            objects=range(47), entries={'all': letters},\n            items=['foo', 'bar'], items_page='p')\n        self.assertRangeEqual(range(10, 30), context['objects'])\n        self.assertSequenceEqual(['foo'], context['items'])\n        self.assertSequenceEqual(letters[10:15], context['myentries'])\n        self.assertSequenceEqual(letters, context['entries']['all'])\n\n\nclass PaginateTest(PaginateTestMixin, TestCase):\n\n    tagname = 'paginate'\n\n    def test_starting_from_last_page_argument(self):\n        # Ensure the queryset reflects the given ``starting_from_page``\n        # argument when the last page is requested.\n        template = '{% $tagname 10 objects starting from page -1 %}'\n        _, context = self.render(self.request(), template)\n        self.assertRangeEqual(range(40, 47), context['objects'])\n\n    def test_starting_from_negative_page_argument(self):\n        # Ensure the queryset reflects the given ``starting_from_page``\n        # argument when a negative number is passed as value.\n        template = '{% $tagname 10 objects starting from page -3 %}'\n        _, context = self.render(self.request(), template)\n        self.assertRangeEqual(range(20, 30), context['objects'])\n\n    def test_starting_from_negative_page_argument_as_variable(self):\n        # Ensure the queryset reflects the given ``starting_from_page``\n        # argument when a negative number is passed as value.\n        # In this case, the argument is provided as context variable.\n        template = '{% $tagname 10 objects starting from page mypage %}'\n        _, context = self.render(\n            self.request(), template, objects=range(47), mypage=-2\n        )\n        self.assertRangeEqual(range(30, 40), context['objects'])\n\n    def test_starting_from_negative_page_out_of_range(self):\n        # Ensure the last page is returned when the ``starting_from_page``\n        # argument, given a negative value, produces an out of range error.\n        template = '{% $tagname 10 objects starting from page -5 %}'\n        _, context = self.render(self.request(), template)\n        self.assertRangeEqual(range(10), context['objects'])\n\n    def test_num_queries(self):\n        # Ensure paginating objects hits the database for the correct number\n        # of times.\n        template = '{% $tagname 10 objects %}'\n        objects = self.assertPaginationNumQueries(2, template)\n        self.assertEqual(10, len(objects))\n\n    def test_num_queries_starting_from_another_page(self):\n        # Ensure paginating objects hits the database for the correct number\n        # of times if pagination is performed starting from another page.\n        template = '{% $tagname 10 objects starting from page 3 %}'\n        self.assertPaginationNumQueries(2, template)\n\n    def test_num_queries_starting_from_last_page(self):\n        # Ensure paginating objects hits the database for the correct number\n        # of times if pagination is performed starting from last page.\n        template = '{% $tagname 10 objects starting from page -1 %}'\n        self.assertPaginationNumQueries(2, template)\n\n\nclass LazyPaginateTest(PaginateTestMixin, TestCase):\n\n    tagname = 'lazy_paginate'\n\n    def test_starting_from_negative_page_raises_error(self):\n        # A *NotImplementedError* is raised if a negative value is given to\n        # the ``starting_from_page`` argument of ``lazy_paginate``.\n        template = '{% $tagname 10 objects starting from page -1 %}'\n        with self.assertRaises(NotImplementedError):\n            self.render(self.request(), template)\n\n    def test_num_queries(self):\n        # Ensure paginating objects hits the database for the correct number\n        # of times. If lazy pagination is used, the ``SELECT COUNT`` query\n        # should be avoided.\n        template = '{% $tagname 10 objects %}'\n        objects = self.assertPaginationNumQueries(1, template)\n        self.assertEqual(10, len(objects))\n\n    def test_num_queries_starting_from_another_page(self):\n        # Ensure paginating objects hits the database for the correct number\n        # of times if pagination is performed starting from another page.\n        template = '{% $tagname 10 objects starting from page 3 %}'\n        self.assertPaginationNumQueries(1, template)\n\n\n@skip_if_old_etree\nclass ShowMoreTest(EtreeTemplateTagsTestMixin, TestCase):\n\n    tagname = 'show_more'\n\n    def render(self, request, contents, **kwargs):\n        text = string.Template(contents).substitute(tagname=self.tagname)\n        return super(ShowMoreTest, self).render(request, text, **kwargs)\n\n    def test_first_page_next_url(self):\n        # Ensure the link to the next page is correctly generated\n        # in the first page.\n        template = '{% paginate objects %}{% $tagname %}'\n        tree = self.render(self.request(), template)\n        link = tree.find('.//a[@class=\"endless_more\"]')\n        expected = '/?{0}={1}'.format(settings.PAGE_LABEL, 2)\n        self.assertEqual(expected, link.attrib['href'])\n\n    def test_page_next_url(self):\n        # Ensure the link to the next page is correctly generated.\n        template = '{% paginate objects %}{% $tagname %}'\n        tree = self.render(self.request(page=3), template)\n        link = tree.find('.//a[@class=\"endless_more\"]')\n        expected = '/?{0}={1}'.format(settings.PAGE_LABEL, 4)\n        self.assertEqual(expected, link.attrib['href'])\n\n    def test_last_page(self):\n        # Ensure the output for the last page is empty.\n        template = '{% paginate 40 objects %}{% $tagname %}'\n        tree = self.render(self.request(page=2), template)\n        self.assertIsNone(tree)\n\n    def test_customized_label(self):\n        # Ensure the link to the next page is correctly generated.\n        template = '{% paginate objects %}{% $tagname \"again and again\" %}'\n        tree = self.render(self.request(), template)\n        link = tree.find('.//a[@class=\"endless_more\"]')\n        self.assertEqual('again and again', link.text)\n\n    def test_customized_loading(self):\n        # Ensure the link to the next page is correctly generated.\n        template = '{% paginate objects %}{% $tagname \"more\" \"working\" %}'\n        tree = self.render(self.request(), template)\n        loading = tree.find('.//*[@class=\"endless_loading\"]')\n        self.assertEqual('working', loading.text)\n\n\n@skip_if_old_etree\nclass ShowMoreTableTest(ShowMoreTest):\n\n    tagname = 'show_more_table'\n\n\nclass GetPagesTest(TemplateTagsTestMixin, TestCase):\n\n    def test_page_list(self):\n        # Ensure the page list is correctly included in the context.\n        template = '{% paginate objects %}{% get_pages %}'\n        html, context = self.render(self.request(), template)\n        self.assertEqual('', html)\n        self.assertIn('pages', context)\n        self.assertIsInstance(context['pages'], PageList)\n\n    def test_different_varname(self):\n        # Ensure the page list is correctly included in the context when\n        # using a different variable name.\n        template = '{% paginate objects %}{% get_pages as page_list %}'\n        _, context = self.render(self.request(), template)\n        self.assertIn('page_list', context)\n        self.assertIsInstance(context['page_list'], PageList)\n\n    def test_page_numbers(self):\n        # Ensure the current page in the page list reflects the current\n        # page number.\n        template = '{% lazy_paginate objects %}{% get_pages %}'\n        for page_number in range(1, 5):\n            _, context = self.render(self.request(page=page_number), template)\n            page = context['pages'].current()\n            self.assertEqual(page_number, page.number)\n\n    def test_without_paginate_tag(self):\n        # An error is raised if this tag is used before the paginate one.\n        template = '{% get_pages %}'\n        with self.assertRaises(PaginationError):\n            self.render(self.request(), template)\n\n    def test_invalid_arguments(self):\n        # An error is raised if invalid arguments are provided.\n        template = '{% lazy_paginate objects %}{% get_pages foo bar %}'\n        request = self.request()\n        with self.assertRaises(TemplateSyntaxError):\n            self.render(request, template)\n\n    def test_starting_from_negative_page_in_another_page(self):\n        # Ensure the default page is missing the querystring when another\n        # page is displayed.\n        template = (\n            '{% paginate 10 objects starting from page -1 %}'\n            '{% get_pages %}'\n        )\n        _, context = self.render(\n            self.request(), template, objects=range(47), page=1)\n        page = context['pages'].last()\n        self.assertEqual('', page.url)\n\n    def test_pages_length(self):\n        # Ensure the pages length returns the correct number of pages.\n        template = '{% paginate 10 objects %}{% get_pages %}{{ pages|length }}'\n        html, context = self.render(self.request(), template)\n        self.assertEqual('5', html)\n\n\n@skip_if_old_etree\nclass ShowPagesTest(EtreeTemplateTagsTestMixin, TestCase):\n\n    def test_current_page(self):\n        # Ensure the current page in the page list reflects the current\n        # page number.\n        template = '{% paginate objects %}{% show_pages %}'\n        for page_number in range(1, 6):\n            tree = self.render(self.request(page=page_number), template)\n            current = tree.find('.//*[@class=\"endless_page_current\"]')\n            text = ''.join(element.text for element in current)\n            self.assertEqual(str(page_number), text)\n\n    def test_links(self):\n        # Ensure the correct number of links is always displayed.\n        template = '{% paginate objects %}{% show_pages %}'\n        for page_number in range(1, 6):\n            tree = self.render(self.request(page=page_number), template)\n            links = tree.findall('.//a')\n            expected = 5 if page_number == 1 or page_number == 5 else 6\n            self.assertEqual(expected, len(links))\n\n    def test_without_paginate_tag(self):\n        # An error is raised if this tag is used before the paginate one.\n        template = '{% show_pages %}'\n        with self.assertRaises(PaginationError):\n            self.render(self.request(), template)\n\n    def test_invalid_arguments(self):\n        # An error is raised if invalid arguments are provided.\n        template = '{% lazy_paginate objects %}{% show_pages foo bar %}'\n        request = self.request()\n        with self.assertRaises(TemplateSyntaxError):\n            self.render(request, template)\n\n\nclass ShowCurrentNumberTest(TemplateTagsTestMixin, TestCase):\n\n    def test_current_number(self):\n        # Ensure the current number is correctly returned.\n        template = '{% show_current_number %}'\n        for page_number in range(1, 6):\n            html, _ = self.render(self.request(page=page_number), template)\n            self.assertEqual(page_number, int(html))\n\n    def test_starting_from_page_argument(self):\n        # Ensure the number reflects the given ``starting_from_page`` arg.\n        template = '{% show_current_number starting from page 3 %}'\n        html, _ = self.render(self.request(), template)\n        self.assertEqual(3, int(html))\n\n    def test_starting_from_page_argument_as_variable(self):\n        # Ensure the number reflects the given ``starting_from_page`` arg.\n        # In this case, the argument is provided as context variable.\n        template = '{% show_current_number starting from page mypage %}'\n        html, _ = self.render(\n            self.request(), template, entries=range(47), mypage=2)\n        self.assertEqual(2, int(html))\n\n    def test_using_argument(self):\n        # Ensure the template tag uses the given querystring key.\n        template = '{% show_current_number using \"mypage\" %}'\n        html, _ = self.render(\n            self.request(mypage=2), template)\n        self.assertEqual(2, int(html))\n\n    def test_using_argument_as_variable(self):\n        # Ensure the template tag uses the given querystring key.\n        # In this case, the argument is provided as context variable.\n        template = '{% show_current_number using qskey %}'\n        html, _ = self.render(\n            self.request(p=5), template, entries=range(47), qskey='p')\n        self.assertEqual(5, int(html))\n\n    def test_as_argument(self):\n        # Ensure it is possible add the page number to context.\n        template = '{% show_current_number as page_number %}'\n        html, context = self.render(self.request(page=4), template)\n        self.assertEqual('', html)\n        self.assertIn('page_number', context)\n        self.assertEqual(4, context['page_number'])\n\n    def test_complete_argument_list(self):\n        # Ensure the tag works providing all the arguments.\n        template = (\n            '{% show_current_number '\n            'starting from page 2 '\n            'using mypage '\n            'as number %}'\n        )\n        html, context = self.render(\n            self.request(), template, objects=range(47), mypage='page-number')\n        self.assertEqual(2, context['number'])\n\n    def test_invalid_arguments(self):\n        # An error is raised if invalid arguments are provided.\n        templates = (\n            '{% show_current_number starting from page %}',\n            '{% show_current_number foo bar spam eggs %}',\n            '{% show_current_number as number using key %}',\n        )\n        request = self.request()\n        for template in templates:\n            with self.assertRaises(TemplateSyntaxError):\n                self.render(request, template)\n"
  },
  {
    "path": "el_pagination/tests/test_decorators.py",
    "content": "\"\"\"Decorator tests.\"\"\"\n\n\n\nfrom django.test import TestCase\nfrom django.test.client import RequestFactory\n\nfrom el_pagination import decorators\n\n\nclass DecoratorsTestMixin(object):\n    \"\"\"Base test mixin for decorators.\n\n    Subclasses (actual test cases) must implement the ``get_decorator`` method\n    and the ``arg`` attribute to be used as argument for the decorator.\n    \"\"\"\n\n    def setUp(self):\n        self.factory = RequestFactory()\n        self.ajax_headers = {'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'}\n        self.default = 'default.html'\n        self.page = 'page.html'\n        self.page_url = '/?page=2&mypage=10&querystring_key=page'\n        self.mypage = 'mypage.html'\n        self.mypage_url = '/?page=2&mypage=10&querystring_key=mypage'\n\n    def get_decorator(self):\n        \"\"\"Return the decorator that must be exercised.\"\"\"\n        raise NotImplementedError\n\n    def assertTemplatesEqual(self, expected_active, expected_page, templates):\n        \"\"\"Assert active template and page template are the ones given.\"\"\"\n        self.assertSequenceEqual([expected_active, expected_page], templates)\n\n    def decorate(self, *args, **kwargs):\n        \"\"\"Return a view decorated with ``self.decorator(*args, **kwargs)``.\"\"\"\n\n        def view(request, extra_context=None, template=self.default):\n            \"\"\"Test view that will be decorated in tests.\"\"\"\n            context = {}\n            if extra_context is not None:\n                context.update(extra_context)\n            return template, context['page_template']\n\n        decorator = self.get_decorator()\n        return decorator(*args, **kwargs)(view)\n\n    def test_decorated(self):\n        # Ensure the view is correctly decorated.\n        view = self.decorate(self.arg)\n        templates = view(self.factory.get('/'))\n        self.assertTemplatesEqual(self.default, self.page, templates)\n\n    def test_request_with_querystring_key(self):\n        # If the querystring key refers to the handled template,\n        # the view still uses the default template if the request is not Ajax.\n        view = self.decorate(self.arg)\n        templates = view(self.factory.get(self.page_url))\n        self.assertTemplatesEqual(self.default, self.page, templates)\n\n    def test_ajax_request(self):\n        # Ensure the view serves the template fragment if the request is Ajax.\n        view = self.decorate(self.arg)\n        templates = view(self.factory.get('/', **self.ajax_headers))\n        self.assertTemplatesEqual(self.page, self.page, templates)\n\n    def test_ajax_request_with_querystring_key(self):\n        # If the querystring key refers to the handled template,\n        # the view switches the template if the request is Ajax.\n        view = self.decorate(self.arg)\n        templates = view(self.factory.get(self.page_url, **self.ajax_headers))\n        self.assertTemplatesEqual(self.page, self.page, templates)\n\n    def test_unexistent_page(self):\n        # Ensure the default page and is returned if the querystring points\n        # to a page that is not defined.\n        view = self.decorate(self.arg)\n        templates = view(self.factory.get('/?querystring_key=does-not-exist'))\n        self.assertTemplatesEqual(self.default, self.page, templates)\n\n\nclass PageTemplateTest(DecoratorsTestMixin, TestCase):\n\n    arg = 'page.html'\n\n    def get_decorator(self):\n        return decorators.page_template\n\n    def test_request_with_querystring_key_to_mypage(self):\n        # If the querystring key refers to another template,\n        # the view still uses the default template if the request is not Ajax.\n        view = self.decorate(self.arg)\n        templates = view(self.factory.get(self.mypage_url))\n        self.assertTemplatesEqual(self.default, self.page, templates)\n\n    def test_ajax_request_with_querystring_key_to_mypage(self):\n        # If the querystring key refers to another template,\n        # the view still uses the default template even if the request is Ajax.\n        view = self.decorate(self.arg)\n        templates = view(\n            self.factory.get(self.mypage_url, **self.ajax_headers))\n        self.assertTemplatesEqual(self.default, self.page, templates)\n\n    def test_ajax_request_to_mypage(self):\n        # Ensure the view serves the template fragment if the request is Ajax\n        # and another template fragment is requested.\n        view = self.decorate(self.mypage, key='mypage')\n        templates = view(\n            self.factory.get(self.mypage_url, **self.ajax_headers))\n        self.assertTemplatesEqual(self.mypage, self.mypage, templates)\n\n\nclass PageTemplatesTest(DecoratorsTestMixin, TestCase):\n\n    arg = {'page.html': None, 'mypage.html': 'mypage'}\n\n    def get_decorator(self):\n        return decorators.page_templates\n\n    def test_request_with_querystring_key_to_mypage(self):\n        # If the querystring key refers to another template,\n        # the view still uses the default template if the request is not Ajax.\n        view = self.decorate(self.arg)\n        templates = view(self.factory.get(self.mypage_url))\n        self.assertTemplatesEqual(self.default, self.mypage, templates)\n\n    def test_ajax_request_with_querystring_key_to_mypage(self):\n        # If the querystring key refers to another template,\n        # the view switches to the givent template if the request is Ajax.\n        view = self.decorate(self.arg)\n        templates = view(\n            self.factory.get(self.mypage_url, **self.ajax_headers))\n        self.assertTemplatesEqual(self.mypage, self.mypage, templates)\n\n\nclass PageTemplatesWithTupleTest(PageTemplatesTest):\n\n    arg = (('page.html', None), ('mypage.html', 'mypage'))\n"
  },
  {
    "path": "el_pagination/tests/test_loaders.py",
    "content": "\"\"\"Loader tests.\"\"\"\n\n\n\nfrom contextlib import contextmanager\n\nfrom django.core.exceptions import ImproperlyConfigured\nfrom django.test import TestCase\n\nfrom el_pagination import loaders\n\ntest_object = 'test object'\n\n\nclass ImproperlyConfiguredTestMixin(object):\n    \"\"\"Include an ImproperlyConfigured assertion.\"\"\"\n\n    @contextmanager\n    def assertImproperlyConfigured(self, message):\n        \"\"\"Assert the code in the context manager block raises an error.\n\n        The error must be ImproperlyConfigured, and the error message must\n        include the given *message*.\n        \"\"\"\n        try:\n            yield\n        except ImproperlyConfigured as err:\n            self.assertIn(message, str(err).lower())\n        else:\n            self.fail('ImproperlyConfigured not raised')\n\n\nclass AssertImproperlyConfiguredTest(ImproperlyConfiguredTestMixin, TestCase):\n\n    def test_assertion(self):\n        # Ensure the assertion does not fail if ImproperlyConfigured is raised\n        # with the given error message.\n        with self.assertImproperlyConfigured('error'):\n            raise ImproperlyConfigured('Example error text')\n\n    def test_case_insensitive(self):\n        # Ensure the error message test is case insensitive.\n        with self.assertImproperlyConfigured('error'):\n            raise ImproperlyConfigured('Example ERROR text')\n\n    def test_assertion_fails_different_message(self):\n        # Ensure the assertion fails if ImproperlyConfigured is raised with\n        # a different message.\n        with self.assertRaises(AssertionError):\n            with self.assertImproperlyConfigured('failure'):\n                raise ImproperlyConfigured('Example error text')\n\n    def test_assertion_fails_no_exception(self):\n        # Ensure the assertion fails if ImproperlyConfigured is not raised.\n        with self.assertRaises(AssertionError) as cm:\n            with self.assertImproperlyConfigured('error'):\n                pass\n        self.assertEqual('ImproperlyConfigured not raised', str(cm.exception))\n\n    def test_assertion_fails_different_exception(self):\n        # Ensure other exceptions are not swallowed.\n        with self.assertRaises(TypeError):\n            with self.assertImproperlyConfigured('error'):\n                raise TypeError\n\n\nclass LoadObjectTest(ImproperlyConfiguredTestMixin, TestCase):\n\n    def setUp(self):\n        self.module = self.__class__.__module__\n\n    def test_valid_path(self):\n        # Ensure the object is correctly loaded if the provided path is valid.\n        path = '.'.join((self.module, 'test_object'))\n        self.assertIs(test_object, loaders.load_object(path))\n\n    def test_module_not_found(self):\n        # An error is raised if the module cannot be found.\n        with self.assertImproperlyConfigured('not found'):\n            loaders.load_object('__invalid__.module')\n\n    def test_invalid_module(self):\n        # An error is raised if the provided path is not a valid dotted string.\n        with self.assertImproperlyConfigured('invalid'):\n            loaders.load_object('')\n\n    def test_object_not_found(self):\n        # An error is raised if the object cannot be found in the module.\n        path = '.'.join((self.module, '__does_not_exist__'))\n        with self.assertImproperlyConfigured('object'):\n            loaders.load_object(path)\n"
  },
  {
    "path": "el_pagination/tests/test_models.py",
    "content": "\"\"\"Model tests.\"\"\"\n\n\n\nfrom contextlib import contextmanager\n\nfrom django.template import Context\nfrom django.test import TestCase\nfrom django.test.client import RequestFactory\nfrom django.utils.encoding import force_str\n\nfrom el_pagination import models as el_models\nfrom el_pagination import settings, utils\nfrom el_pagination.paginators import DefaultPaginator\n\n\n@contextmanager\ndef local_settings(**kwargs):\n    \"\"\"Override local Django Endless Pagination settings.\n\n    This context manager can be used in a way similar to Django own\n    ``TestCase.settings()``.\n    \"\"\"\n    original_values = []\n    for key, value in kwargs.items():\n        original_values.append([key, getattr(settings, key)])\n        setattr(settings, key, value)\n    try:\n        yield\n    finally:\n        for key, value in original_values:\n            setattr(settings, key, value)\n\n\nclass LocalSettingsTest(TestCase):\n\n    def setUp(self):\n        settings._LOCAL_SETTINGS_TEST = 'original'\n\n    def tearDown(self):\n        del settings._LOCAL_SETTINGS_TEST\n\n    def test_settings_changed(self):\n        # Check that local settings are changed.\n        with local_settings(_LOCAL_SETTINGS_TEST='changed'):\n            self.assertEqual('changed', settings._LOCAL_SETTINGS_TEST)\n\n    def test_settings_restored(self):\n        # Check that local settings are restored.\n        with local_settings(_LOCAL_SETTINGS_TEST='changed'):\n            pass\n        self.assertEqual('original', settings._LOCAL_SETTINGS_TEST)\n\n    def test_restored_after_exception(self):\n        # Check that local settings are restored after an exception.\n        with self.assertRaises(RuntimeError):\n            with local_settings(_LOCAL_SETTINGS_TEST='changed'):\n                raise RuntimeError()\n            self.assertEqual('original', settings._LOCAL_SETTINGS_TEST)\n\n\ndef page_list_callable_arrows(number, num_pages):\n    \"\"\"Wrap ``el_pagination.utils.get_page_numbers``.\n\n    Set first / last page arrows to True.\n    \"\"\"\n    return utils.get_page_numbers(number, num_pages, arrows=True)\n\n\npage_list_callable_dummy = lambda number, num_pages: [None]\n\n\nclass PageListTest(TestCase):\n\n    def setUp(self):\n        self.paginator = DefaultPaginator(range(30), 7, orphans=2)\n        self.current_number = 2\n        self.page_label = 'page'\n        self.factory = RequestFactory()\n        self.request = self.factory.get(\n            self.get_path_for_page(self.current_number))\n        self.pages = el_models.PageList(\n            self.request, self.paginator.page(self.current_number),\n            self.page_label, context=Context())\n\n    def get_url_for_page(self, number):\n        \"\"\"Return a url for the given page ``number``.\"\"\"\n        return '?{0}={1}'.format(self.page_label, number)\n\n    def get_path_for_page(self, number):\n        \"\"\"Return a path for the given page ``number``.\"\"\"\n        return '/' + self.get_url_for_page(number)\n\n    def check_page(\n            self, page, number, is_first, is_last, is_current, label=None):\n        \"\"\"Perform several assertions on the given page attrs.\"\"\"\n        if label is None:\n            label = force_str(page.number)\n        self.assertEqual(label, page.label)\n        self.assertEqual(number, page.number)\n        self.assertEqual(is_first, page.is_first)\n        self.assertEqual(is_last, page.is_last)\n        self.assertEqual(is_current, page.is_current)\n\n    def check_page_list_callable(self, callable_or_path):\n        \"\"\"Check the provided *page_list_callable* is actually used.\"\"\"\n        with local_settings(PAGE_LIST_CALLABLE=callable_or_path):\n            rendered = force_str(self.pages.get_rendered()).strip()\n        expected = '<span class=\"endless_separator\">...</span>'\n        self.assertEqual(expected, rendered)\n\n    def test_length(self):\n        # Ensure the length of the page list equals the number of pages.\n        self.assertEqual(self.paginator.num_pages, len(self.pages))\n\n    def test_paginated(self):\n        # Ensure the *paginated* method returns True if the page list contains\n        # more than one page, False otherwise.\n        page = DefaultPaginator(range(10), 10).page(1)\n        pages = el_models.PageList(self.request, page, self.page_label,\n                                   context=Context())\n        self.assertFalse(pages.paginated())\n        self.assertTrue(self.pages.paginated())\n\n    def test_first_page(self):\n        # Ensure the attrs of the first page are correctly defined.\n        page = self.pages.first()\n        self.assertEqual('/', page.path)\n        self.assertEqual('', page.url)\n        self.check_page(page, 1, True, False, False)\n\n    def test_last_page(self):\n        # Ensure the attrs of the last page are correctly defined.\n        page = self.pages.last()\n        self.check_page(page, len(self.pages), False, True, False)\n\n    def test_first_page_as_arrow(self):\n        # Ensure the attrs of the first page are correctly defined when the\n        # page is represented as an arrow.\n        page = self.pages.first_as_arrow()\n        self.assertEqual('/', page.path)\n        self.assertEqual('', page.url)\n        self.check_page(\n            page, 1, True, False, False, label=settings.FIRST_LABEL)\n\n    def test_last_page_as_arrow(self):\n        # Ensure the attrs of the last page are correctly defined when the\n        # page is represented as an arrow.\n        page = self.pages.last_as_arrow()\n        self.check_page(\n            page, len(self.pages), False, True, False,\n            label=settings.LAST_LABEL)\n\n    def test_current_page(self):\n        # Ensure the attrs of the current page are correctly defined.\n        page = self.pages.current()\n        self.check_page(page, self.current_number, False, False, True)\n\n    def test_path(self):\n        # Ensure the path of each page is correctly generated.\n        for num, page in enumerate(list(self.pages)[1:]):\n            expected = self.get_path_for_page(num + 2)\n            self.assertEqual(expected, page.path)\n\n    def test_url(self):\n        # Ensure the path of each page is correctly generated.\n        for num, page in enumerate(list(self.pages)[1:]):\n            expected = self.get_url_for_page(num + 2)\n            self.assertEqual(expected, page.url)\n\n    def test_current_indexes(self):\n        # Ensure the 1-based indexes of the first and last items on the current\n        # page are correctly returned.\n        self.assertEqual(8, self.pages.current_start_index())\n        self.assertEqual(14, self.pages.current_end_index())\n\n    def test_total_count(self):\n        # Ensure the total number of objects is correctly returned.\n        self.assertEqual(30, self.pages.total_count())\n\n    def test_page_render(self):\n        # Ensure the page is correctly rendered.\n        page = self.pages.first()\n        rendered_page = force_str(page.render_link())\n        self.assertIn('href=\"/\"', rendered_page)\n        self.assertIn(page.label, rendered_page)\n\n    def test_current_page_render(self):\n        # Ensure the page is correctly rendered.\n        page = self.pages.current()\n        rendered_page = force_str(page.render_link())\n        self.assertNotIn('href', rendered_page)\n        self.assertIn(page.label, rendered_page)\n\n    def test_page_list_render(self):\n        # Ensure the page list is correctly rendered.\n        rendered = force_str(self.pages.get_rendered())\n        self.assertEqual(5, rendered.count('<a href'))\n        self.assertIn(settings.PREVIOUS_LABEL, rendered)\n        self.assertIn(settings.NEXT_LABEL, rendered)\n\n    def test_page_list_render_using_arrows(self):\n        # Ensure the page list is correctly rendered when using first / last\n        # page arrows.\n        page_list_callable = (\n            'el_pagination.tests.test_models.page_list_callable_arrows')\n        with local_settings(PAGE_LIST_CALLABLE=page_list_callable):\n            rendered = force_str(self.pages.get_rendered())\n        self.assertEqual(7, rendered.count('<a href'))\n        self.assertIn(settings.FIRST_LABEL, rendered)\n        self.assertIn(settings.LAST_LABEL, rendered)\n\n    def test_page_list_render_just_one_page(self):\n        # Ensure nothing is rendered if the page list contains only one page.\n        page = DefaultPaginator(range(10), 10).page(1)\n        pages = el_models.PageList(self.request, page, self.page_label,\n                                   context=Context())\n        self.assertEqual('', force_str(pages))\n\n    def test_different_default_number(self):\n        # Ensure the page path is generated based on the default number.\n        pages = el_models.PageList(\n            self.request, self.paginator.page(2), self.page_label,\n            default_number=2, context=Context())\n        self.assertEqual('/', pages.current().path)\n        self.assertEqual(self.get_path_for_page(1), pages.first().path)\n\n    def test_index_error(self):\n        # Ensure an error if raised if a non existent page is requested.\n        with self.assertRaises(IndexError):\n            self.pages[len(self.pages) + 1]\n\n    def test_previous(self):\n        # Ensure the correct previous page is returned.\n        previous_page = self.pages.previous()\n        self.assertEqual(self.current_number - 1, previous_page.number)\n\n    def test_previous_attrs(self):\n        # Ensure the attrs of the next page are correctly defined.\n        with local_settings(USE_NEXT_PREVIOUS_LINKS=True):\n            previous_page = self.pages.previous()\n        self.assertEqual(True, previous_page.is_previous)\n        self.check_page(\n            previous_page, self.current_number - 1, True, False, False, label=settings.PREVIOUS_LABEL)\n\n    def test_next(self):\n        # Ensure the correct next page is returned.\n        next_page = self.pages.next()\n        self.assertEqual(self.current_number + 1, next_page.number)\n\n    def test_next_attrs(self):\n        # Ensure the attrs of the next page are correctly defined.\n        with local_settings(USE_NEXT_PREVIOUS_LINKS=True):\n            next_page = self.pages.next()\n        self.assertEqual(True, next_page.is_next)\n        self.check_page(\n            next_page, self.current_number + 1, False, False, False, label=settings.NEXT_LABEL)\n\n    def test_no_previous(self):\n        # An empty string is returned if the previous page cannot be found.\n        pages = el_models.PageList(\n            self.request, self.paginator.page(1), self.page_label,\n            context=Context())\n        self.assertEqual('', pages.previous())\n\n    def test_no_next(self):\n        # An empty string is returned if the next page cannot be found.\n        num_pages = self.paginator.num_pages\n        pages = el_models.PageList(\n            self.request, self.paginator.page(num_pages), self.page_label,\n            context=Context())\n        self.assertEqual('', pages.next())\n\n    def test_customized_page_list_callable(self):\n        # The page list is rendered based on ``settings.PAGE_LIST_CALLABLE``.\n        self.check_page_list_callable(page_list_callable_dummy)\n\n    def test_customized_page_list_dotted_path(self):\n        # The option ``settings.PAGE_LIST_CALLABLE`` can be provided as a\n        # dotted path, e.g.: 'path.to.my.callable'.\n        self.check_page_list_callable(\n            'el_pagination.tests.test_models.page_list_callable_dummy')\n\n    def test_whitespace_in_path(self):\n        # Ensure white spaces in paths are correctly handled.\n        path = '/a path/containing spaces/'\n        request = self.factory.get(path)\n        next = el_models.PageList(\n            request, self.paginator.page(self.current_number),\n            self.page_label, context=Context()).next()\n        self.assertEqual(path.replace(' ', '%20') + next.url, next.path)\n\n    def test_lookup(self):\n        # Ensure the page list correctly handles lookups.\n        pages = self.pages\n        self.assertEqual(pages.first().number, pages[1].number)\n\n    def test_invalid_lookup(self):\n        # A TypeError is raised if the lookup is not valid.\n        with self.assertRaises(TypeError):\n            self.pages['invalid']\n"
  },
  {
    "path": "el_pagination/tests/test_paginators.py",
    "content": "\"\"\"Paginator tests.\"\"\"\n\n\n\nfrom django.test import TestCase\n\nfrom el_pagination import paginators\n\n\nclass PaginatorTestMixin(object):\n    \"\"\"Base test mixin for paginators.\n\n    Subclasses (actual test cases) must define the ``paginator_class`` name.\n    \"\"\"\n\n    def setUp(self):\n        self.items = list(range(30))\n        self.per_page = 7\n        self.paginator = self.paginator_class(\n            self.items, self.per_page, orphans=2)\n\n    def test_object_list(self):\n        # Ensure the paginator correctly returns objects for each page.\n        first_page = self.paginator.first_page\n        expected = self.items[first_page:first_page + self.per_page]\n        object_list = self.paginator.page(2).object_list\n        self.assertSequenceEqual(expected, object_list)\n\n    def test_orphans(self):\n        # Ensure orphans are included in the last page.\n        object_list = self.paginator.page(4).object_list\n        self.assertSequenceEqual(self.items[-9:], object_list)\n\n    def test_no_orphans(self):\n        # Ensure exceeding orphans generate a new page.\n        paginator = self.paginator_class(range(11), 8, orphans=2)\n        object_list = paginator.page(2).object_list\n        self.assertEqual(3, len(object_list))\n\n    def test_empty_page(self):\n        # En error if raised if the requested page does not exist.\n        with self.assertRaises(paginators.EmptyPage):\n            self.paginator.page(5)\n\n    def test_invalid_page(self):\n        # En error is raised if the requested page is not valid.\n        with self.assertRaises(paginators.PageNotAnInteger):\n            self.paginator.page('__not_valid__')\n        with self.assertRaises(paginators.EmptyPage):\n            self.paginator.page(0)\n\n\nclass DifferentFirstPagePaginatorTestMixin(PaginatorTestMixin):\n    \"\"\"Base test mixin for paginators.\n\n    This time the paginator defined in ``setUp`` has different number of\n    items on the first page.\n    Subclasses (actual test cases) must define the ``paginator_class`` name.\n    \"\"\"\n\n    def setUp(self):\n        self.items = list(range(26))\n        self.per_page = 7\n        self.paginator = self.paginator_class(\n            self.items, self.per_page, first_page=3, orphans=2)\n\n    def test_no_orphans(self):\n        # Ensure exceeding orphans generate a new page.\n        paginator = self.paginator_class(range(11), 5, first_page=3, orphans=2)\n        object_list = paginator.page(3).object_list\n        self.assertEqual(3, len(object_list))\n\n\nclass DefaultPaginatorTest(PaginatorTestMixin, TestCase):\n\n    paginator_class = paginators.DefaultPaginator\n\n    def test_indexes(self):\n        # Ensure start and end indexes are correct.\n        page = self.paginator.page(2)\n        self.assertEqual(self.per_page + 1, page.start_index())\n        self.assertEqual(self.per_page * 2, page.end_index())\n\n    def test_items_count(self):\n        # Ensure the paginator reflects the number of items.\n        self.assertEqual(len(self.items), self.paginator.count)\n\n    def test_num_pages(self):\n        # Ensure the number of pages is correctly calculated.\n        self.assertEqual(4, self.paginator.num_pages)\n\n    def test_page_range(self):\n        # Ensure the page range is correctly calculated.\n        self.assertSequenceEqual([1, 2, 3, 4], self.paginator.page_range)\n\n    def test_no_items(self):\n        # Ensure the right values are returned if the page contains no items.\n        paginator = self.paginator_class([], 10)\n        page = paginator.page(1)\n        self.assertEqual(0, paginator.count)\n        self.assertEqual(0, page.start_index())\n\n    def test_single_page_indexes(self):\n        # Ensure the returned indexes are correct for a single page pagination.\n        paginator = self.paginator_class(range(6), 5, orphans=2)\n        page = paginator.page(1)\n        self.assertEqual(1, page.start_index())\n        self.assertEqual(6, page.end_index())\n\n\nclass LazyPaginatorTest(PaginatorTestMixin, TestCase):\n\n    paginator_class = paginators.LazyPaginator\n\n    def test_items_count(self):\n        # The lazy paginator does not implement items count.\n        with self.assertRaises(NotImplementedError):\n            self.paginator.count\n\n    def test_num_pages(self):\n        # The number of pages depends on the current page.\n        self.paginator.page(2)\n        self.assertEqual(3, self.paginator.num_pages)\n\n    def test_page_range(self):\n        # The lazy paginator does not implement page range.\n        with self.assertRaises(NotImplementedError):\n            self.paginator.page_range\n\n\nclass DifferentFirstPageDefaultPaginatorTest(\n        DifferentFirstPagePaginatorTestMixin, TestCase):\n\n    paginator_class = paginators.DefaultPaginator\n\n\nclass DifferentFirstPageLazyPaginatorTest(\n        DifferentFirstPagePaginatorTestMixin, TestCase):\n\n    paginator_class = paginators.LazyPaginator\n"
  },
  {
    "path": "el_pagination/tests/test_utils.py",
    "content": "\"\"\"Utilities tests.\"\"\"\n\n\n\nfrom django.test import TestCase\nfrom django.test.client import RequestFactory\n\nfrom el_pagination import utils\nfrom el_pagination.exceptions import PaginationError\nfrom el_pagination.settings import PAGE_LABEL\n\n\nclass GetDataFromContextTest(TestCase):\n\n    def test_valid_context(self):\n        # Ensure the endless data is correctly retrieved from context.\n        context = {'endless': 'test-data'}\n        self.assertEqual('test-data', utils.get_data_from_context(context))\n\n    def test_invalid_context(self):\n        # A ``PaginationError`` is raised if the data cannot be found\n        # in the context.\n        self.assertRaises(PaginationError, utils.get_data_from_context, {})\n\n\nclass GetPageNumberFromRequestTest(TestCase):\n\n    def setUp(self):\n        self.factory = RequestFactory()\n\n    def test_no_querystring_key(self):\n        # Ensure the first page is returned if page info cannot be\n        # retrieved from the querystring.\n        request = self.factory.get('/')\n        self.assertEqual(1, utils.get_page_number_from_request(request))\n\n    def test_default_querystring_key(self):\n        # Ensure the default page label is used if ``querystring_key``\n        # is not provided.\n        request = self.factory.get('?{0}=2'.format(PAGE_LABEL))\n        self.assertEqual(2, utils.get_page_number_from_request(request))\n\n    def test_default(self):\n        # Ensure the default page number is returned if page info cannot be\n        # retrieved from the querystring.\n        request = self.factory.get('/')\n        page_number = utils.get_page_number_from_request(request, default=3)\n        self.assertEqual(3, page_number)\n\n    def test_custom_querystring_key(self):\n        # Ensure the page returned correctly reflects the ``querystring_key``.\n        request = self.factory.get('?mypage=4'.format(PAGE_LABEL))  # noqa: F523\n        page_number = utils.get_page_number_from_request(\n            request, querystring_key='mypage')\n        self.assertEqual(4, page_number)\n\n    def test_post_data(self):\n        # The page number can also be present in POST data.\n        request = self.factory.post('/', {PAGE_LABEL: 5})\n        self.assertEqual(5, utils.get_page_number_from_request(request))\n\n\nclass GetPageNumbersTest(TestCase):\n\n    def test_defaults(self):\n        # Ensure the pages are returned correctly using the default values.\n        pages = utils.get_page_numbers(10, 20)\n        expected = [\n            'previous', 1, 2, 3, None, 8, 9, 10, 11, 12,\n            None, 18, 19, 20, 'next']\n        self.assertSequenceEqual(expected, pages)\n\n    def test_first_page(self):\n        # Ensure the correct pages are returned if the first page is requested.\n        pages = utils.get_page_numbers(1, 10)\n        expected = [1, 2, 3, None, 8, 9, 10, 'next']\n        self.assertSequenceEqual(expected, pages)\n\n    def test_last_page(self):\n        # Ensure the correct pages are returned if the last page is requested.\n        pages = utils.get_page_numbers(10, 10)\n        expected = ['previous', 1, 2, 3, None, 8, 9, 10]\n        self.assertSequenceEqual(expected, pages)\n\n    def test_no_extremes(self):\n        # Ensure the correct pages are returned with no extremes.\n        pages = utils.get_page_numbers(10, 20, extremes=0)\n        expected = ['previous', 8, 9, 10, 11, 12, 'next']\n        self.assertSequenceEqual(expected, pages)\n\n    def test_no_arounds(self):\n        # Ensure the correct pages are returned with no arounds.\n        pages = utils.get_page_numbers(10, 20, arounds=0)\n        expected = ['previous', 1, 2, 3, None, 10, None, 18, 19, 20, 'next']\n        self.assertSequenceEqual(expected, pages)\n\n    def test_no_extremes_arounds(self):\n        # Ensure the correct pages are returned with no extremes and arounds.\n        pages = utils.get_page_numbers(10, 20, extremes=0, arounds=0)\n        expected = ['previous', 10, 'next']\n        self.assertSequenceEqual(expected, pages)\n\n    def test_one_page(self):\n        # Ensure the correct pages are returned if there is only one page.\n        pages = utils.get_page_numbers(1, 1)\n        expected = [1]\n        self.assertSequenceEqual(expected, pages)\n\n    def test_arrows(self):\n        # Ensure the pages are returned correctly adding first / last arrows.\n        pages = utils.get_page_numbers(5, 10, arrows=True)\n        expected = [\n            'first', 'previous', 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 'next', 'last']\n        self.assertSequenceEqual(expected, pages)\n\n    def test_arrows_first_page(self):\n        # Ensure the correct pages are returned if the first page is requested\n        # adding first / last arrows.\n        pages = utils.get_page_numbers(1, 5, arrows=True)\n        expected = [1, 2, 3, 4, 5, 'next', 'last']\n        self.assertSequenceEqual(expected, pages)\n\n    def test_arrows_last_page(self):\n        # Ensure the correct pages are returned if the last page is requested\n        # adding first / last arrows.\n        pages = utils.get_page_numbers(5, 5, arrows=True)\n        expected = ['first', 'previous', 1, 2, 3, 4, 5]\n        self.assertSequenceEqual(expected, pages)\n\n\nclass IterFactorsTest(TestCase):\n\n    def _run_tests(self, test_data):\n        for starting_factor, num_factors, expected in test_data:\n            factor = utils._iter_factors(starting_factor)\n            factors = [next(factor) for idx in range(num_factors)]\n            self.assertEqual(expected, factors)\n\n    def test__iter_factors(self):\n        # Ensure the correct values are progressively generated.\n        test_data = (\n            (1, 10, [1, 3, 10, 30, 100, 300, 1000, 3000, 10000, 30000]),\n            (5, 10, [5, 15, 50, 150, 500, 1500, 5000, 15000, 50000, 150000]),\n            (10, 10, [\n                10, 30, 100, 300, 1000, 3000, 10000, 30000, 100000, 300000]),\n        )\n        self._run_tests(test_data)\n\n\nclass MakeElasticRangeTest(TestCase):\n\n    def _run_tests(self, test_data):\n        for begin, end, expected in test_data:\n            elastic_range = utils._make_elastic_range(begin, end)\n            self.assertEqual(expected, elastic_range)\n\n    def test___make_elastic_range_units(self):\n        # Ensure an S-curved range of pages is correctly generated for units.\n        test_data = (\n            (1, 1, [1]),\n            (1, 2, [1, 2]),\n            (2, 2, [2]),\n            (1, 3, [1, 2, 3]),\n            (2, 3, [2, 3]),\n            (3, 3, [3]),\n            (1, 4, [1, 2, 3, 4]),\n            (2, 4, [2, 3, 4]),\n            (3, 4, [3, 4]),\n            (4, 4, [4]),\n            (1, 5, [1, 2, 4, 5]),\n            (2, 5, [2, 3, 4, 5]),\n            (3, 5, [3, 4, 5]),\n            (4, 5, [4, 5]),\n            (5, 5, [5]),\n            (1, 6, [1, 2, 5, 6]),\n            (2, 6, [2, 3, 5, 6]),\n            (3, 6, [3, 4, 5, 6]),\n            (4, 6, [4, 5, 6]),\n            (5, 6, [5, 6]),\n            (6, 6, [6]),\n            (1, 7, [1, 2, 4, 6, 7]),\n            (2, 7, [2, 3, 6, 7]),\n            (3, 7, [3, 4, 6, 7]),\n            (4, 7, [4, 5, 6, 7]),\n            (5, 7, [5, 6, 7]),\n            (6, 7, [6, 7]),\n            (7, 7, [7]),\n            (1, 8, [1, 2, 4, 5, 7, 8]),\n            (2, 8, [2, 3, 5, 7, 8]),\n            (3, 8, [3, 4, 7, 8]),\n            (4, 8, [4, 5, 7, 8]),\n            (5, 8, [5, 6, 7, 8]),\n            (6, 8, [6, 7, 8]),\n            (7, 8, [7, 8]),\n            (8, 8, [8]),\n            (1, 9, [1, 2, 4, 6, 8, 9]),\n            (2, 9, [2, 3, 5, 6, 8, 9]),\n            (3, 9, [3, 4, 6, 8, 9]),\n            (4, 9, [4, 5, 8, 9]),\n            (5, 9, [5, 6, 8, 9]),\n            (6, 9, [6, 7, 8, 9]),\n            (7, 9, [7, 8, 9]),\n            (8, 9, [8, 9]),\n            (9, 9, [9]),\n            (1, 10, [1, 2, 4, 7, 9, 10]),\n            (2, 10, [2, 3, 5, 7, 9, 10]),\n            (3, 10, [3, 4, 6, 7, 9, 10]),\n            (4, 10, [4, 5, 7, 9, 10]),\n            (5, 10, [5, 6, 9, 10]),\n            (6, 10, [6, 7, 9, 10]),\n            (7, 10, [7, 8, 9, 10]),\n            (8, 10, [8, 9, 10]),\n            (9, 10, [9, 10]),\n            (10, 10, [10]),\n        )\n        self._run_tests(test_data)\n\n    def test___make_elastic_range_tens(self):\n        # Ensure an S-curved range of pages is correctly generated for tens.\n        test_data = (\n            (1, 20, [1, 2, 4, 17, 19, 20]),\n            (5, 20, [5, 6, 8, 17, 19, 20]),\n            (10, 20, [10, 11, 13, 17, 19, 20]),\n            (11, 20, [11, 12, 14, 17, 19, 20]),\n            (1, 50, [1, 2, 4, 11, 40, 47, 49, 50]),\n            (10, 50, [10, 11, 13, 20, 40, 47, 49, 50]),\n            (25, 50, [25, 26, 28, 35, 40, 47, 49, 50]),\n            (1, 100, [1, 2, 4, 11, 31, 70, 90, 97, 99, 100]),\n            (25, 100, [25, 26, 28, 35, 55, 70, 90, 97, 99, 100]),\n            (50, 100, [50, 51, 53, 60, 90, 97, 99, 100]),\n            (75, 100, [75, 76, 78, 85, 90, 97, 99, 100]),\n        )\n        self._run_tests(test_data)\n\n    def test___make_elastic_range_more(self):\n        # An S-curved range of pages is correctly generated for larger numbers.\n        test_data = (\n            (1, 500, [1, 5, 13, 41, 121, 380, 460, 488, 496, 500]),\n            (1, 1000, [1, 10, 28, 91, 271, 730, 910, 973, 991, 1000]),\n            (1, 10000, [\n                1, 100, 298, 991, 2971, 7030, 9010, 9703, 9901, 10000]),\n            (1, 100000, [\n                1, 1000, 2998, 9991, 29971, 70030, 90010, 97003, 99001,\n                100000]),\n            (1, 1000000, [\n                1, 10000, 29998, 99991, 299971, 700030, 900010, 970003,\n                990001, 1000000]),\n        )\n        self._run_tests(test_data)\n\n\nclass GetElasticPageNumbersTest(TestCase):\n\n    def _run_tests(self, test_data):\n        for current_page, num_pages, expected in test_data:\n            pages = utils.get_elastic_page_numbers(current_page, num_pages)\n            self.assertSequenceEqual(expected, pages)\n\n    def test_get_elastic_page_numbers_units(self):\n        # Ensure the callable returns the expected values for units.\n        test_data = (\n            (1, 1, [1]),\n            (1, 2, [1, 2]),\n            (2, 2, [1, 2]),\n            (1, 3, [1, 2, 3]),\n            (3, 3, [1, 2, 3]),\n            (1, 4, [1, 2, 3, 4]),\n            (4, 4, [1, 2, 3, 4]),\n            (1, 5, [1, 2, 3, 4, 5]),\n            (5, 5, [1, 2, 3, 4, 5]),\n            (1, 6, [1, 2, 3, 4, 5, 6]),\n            (6, 6, [1, 2, 3, 4, 5, 6]),\n            (1, 7, [1, 2, 3, 4, 5, 6, 7]),\n            (7, 7, [1, 2, 3, 4, 5, 6, 7]),\n            (1, 8, [1, 2, 3, 4, 5, 6, 7, 8]),\n            (8, 8, [1, 2, 3, 4, 5, 6, 7, 8]),\n            (1, 9, [1, 2, 3, 4, 5, 6, 7, 8, 9]),\n            (9, 9, [1, 2, 3, 4, 5, 6, 7, 8, 9]),\n            (1, 10, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]),\n            (6, 10, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]),\n            (10, 10, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]),\n        )\n        self._run_tests(test_data)\n\n    def test_get_elastic_page_numbers_tens(self):\n        # Ensure the callable returns the expected values for tens.\n        test_data = (\n            (1, 11, [\n                1, 2, 4, 8, 10, 11, 'next', 'last']),\n            (2, 11, [\n                'first', 'previous', 1, 2, 3, 5, 8, 10, 11, 'next', 'last']),\n            (3, 11, [\n                'first', 'previous', 1, 2, 3, 4, 6, 8, 10, 11, 'next',\n                'last']),\n            (4, 11, [\n                'first', 'previous', 1, 2, 3, 4, 5, 7, 8, 10, 11, 'next',\n                'last']),\n            (5, 11, [\n                'first', 'previous', 1, 2, 4, 5, 6, 8, 10, 11, 'next',\n                'last']),\n            (6, 11, [\n                'first', 'previous', 1, 2, 5, 6, 7, 10, 11, 'next', 'last']),\n            (7, 11, [\n                'first', 'previous', 1, 2, 4, 6, 7, 8, 10, 11, 'next',\n                'last']),\n            (8, 11, [\n                'first', 'previous', 1, 2, 4, 5, 7, 8, 9, 10, 11, 'next',\n                'last']),\n            (9, 11, [\n                'first', 'previous', 1, 2, 4, 6, 8, 9, 10, 11, 'next',\n                'last']),\n            (10, 11, [\n                'first', 'previous', 1, 2, 4, 7, 9, 10, 11, 'next', 'last']),\n            (11, 11, ['first', 'previous', 1, 2, 4, 8, 10, 11]),\n            (1, 12, [1, 2, 4, 9, 11, 12, 'next', 'last']),\n            (2, 12, [\n                'first', 'previous', 1, 2, 3, 5, 9, 11, 12, 'next', 'last']),\n            (6, 12, [\n                'first', 'previous', 1, 2, 5, 6, 7, 9, 11, 12, 'next',\n                'last']),\n            (7, 12, [\n                'first', 'previous', 1, 2, 4, 6, 7, 8, 11, 12, 'next',\n                'last']),\n            (11, 12, [\n                'first', 'previous', 1, 2, 4, 8, 10, 11, 12, 'next', 'last']),\n            (12, 12, ['first', 'previous', 1, 2, 4, 9, 11, 12]),\n            (1, 15, [1, 2, 4, 12, 14, 15, 'next', 'last']),\n            (5, 15, [\n                'first', 'previous', 1, 2, 4, 5, 6, 8, 12, 14, 15, 'next',\n                'last']),\n            (10, 15, [\n                'first', 'previous', 1, 2, 4, 7, 9, 10, 11, 14, 15, 'next',\n                'last']),\n            (15, 15, ['first', 'previous', 1, 2, 4, 12, 14, 15]),\n            (1, 100, [1, 2, 4, 11, 31, 70, 90, 97, 99, 100, 'next', 'last']),\n            (25, 100, [\n                'first', 'previous', 1, 2, 4, 11, 15, 22, 24, 25, 26, 28, 35,\n                55, 70, 90, 97, 99, 100, 'next', 'last']),\n            (75, 100, [\n                'first', 'previous', 1, 2, 4, 11, 31, 45, 65, 72, 74, 75, 76,\n                78, 85, 90, 97, 99, 100, 'next', 'last']),\n            (100, 100, [\n                'first', 'previous', 1, 2, 4, 11, 31, 70, 90, 97, 99, 100]),\n        )\n        self._run_tests(test_data)\n\n    def test_get_elastic_page_numbers_more(self):\n        # Ensure the callable returns the expected values for larger numbers.\n        test_data = (\n            (1, 500, [\n                1, 5, 13, 41, 121, 380, 460, 488, 496, 500, 'next', 'last']),\n            (150, 500, [\n                'first', 'previous', 1, 2, 4, 11, 31, 120, 140, 147, 149, 150,\n                153, 159, 180, 240, 410, 470, 491, 497, 500, 'next', 'last']),\n            (350, 500, [\n                'first', 'previous', 1, 4, 10, 31, 91, 260, 320, 341, 347, 350,\n                351, 353, 360, 380, 470, 490, 497, 499, 500, 'next', 'last']),\n            (500, 500, [\n                'first', 'previous', 1, 5, 13, 41, 121, 380, 460, 488, 496,\n                500]),\n            (100, 1000, [\n                'first', 'previous', 1, 2, 4, 11, 31, 70, 90, 97, 99, 100, 109,\n                127, 190, 370, 730, 910, 973, 991, 1000, 'next', 'last']),\n            (1000, 10000, [\n                'first', 'previous', 1, 10, 28, 91, 271, 730, 910, 973, 991,\n                1000, 1090, 1270, 1900, 3700, 7300, 9100, 9730, 9910, 10000,\n                'next', 'last']),\n            (10000, 100000, [\n                'first', 'previous', 1, 100, 298, 991, 2971, 7030, 9010, 9703,\n                9901, 10000, 10900, 12700, 19000, 37000, 73000, 91000, 97300,\n                99100, 100000, 'next', 'last']),\n            (100000, 1000000, [\n                'first', 'previous', 1, 1000, 2998, 9991, 29971, 70030, 90010,\n                97003, 99001, 100000, 109000, 127000, 190000, 370000, 730000,\n                910000, 973000, 991000, 1000000, 'next', 'last']),\n        )\n        self._run_tests(test_data)\n\n\nclass GetQuerystringForPageTest(TestCase):\n\n    def setUp(self):\n        self.factory = RequestFactory()\n\n    def test_querystring(self):\n        # Ensure the querystring is correctly generated from request.\n        request = self.factory.get('/')\n        querystring = utils.get_querystring_for_page(request, 2, 'mypage')\n        self.assertEqual('?mypage=2', querystring)\n\n    def test_default_page(self):\n        # Ensure the querystring is empty for the default page.\n        request = self.factory.get('/')\n        querystring = utils.get_querystring_for_page(\n            request, 3, 'mypage', default_number=3)\n        self.assertEqual('', querystring)\n\n    def test_composition(self):\n        # Ensure existing querystring is correctly preserved.\n        request = self.factory.get('/?mypage=1&foo=bar')\n        querystring = utils.get_querystring_for_page(request, 4, 'mypage')\n        self.assertIn('mypage=4', querystring)\n        self.assertIn('foo=bar', querystring)\n\n    def test_querystring_key(self):\n        # The querystring key is deleted from the querystring if present.\n        request = self.factory.get('/?querystring_key=mykey')\n        querystring = utils.get_querystring_for_page(request, 5, 'mypage')\n        self.assertEqual('?mypage=5', querystring)\n\n\nclass NormalizePageNumberTest(TestCase):\n\n    page_range = [1, 2, 3, 4]\n\n    def test_in_range(self):\n        # Ensure the correct page number is returned when the requested\n        # negative index is in range.\n        page_numbers = [-1, -2, -3, -4]\n        expected_results = reversed(self.page_range)\n        for page_number, expected in zip(page_numbers, expected_results):\n            result = utils.normalize_page_number(page_number, self.page_range)\n            self.assertEqual(expected, result)\n\n    def test_out_of_range(self):\n        # Ensure the page number 1 returned when the requested negative index\n        # is out of range.\n        result = utils.normalize_page_number(-5, self.page_range)\n        self.assertEqual(self.page_range[0], result)\n"
  },
  {
    "path": "el_pagination/tests/test_views.py",
    "content": "\"\"\"View tests.\"\"\"\n\n\n\nfrom django.core.exceptions import ImproperlyConfigured\nfrom django.http import Http404\nfrom django.test import TestCase\nfrom django.test.client import RequestFactory\n\nfrom el_pagination import views\nfrom project.models import TestModel, make_model_instances\n\n\nclass AjaxListViewTest(TestCase):\n\n    model_page_template = 'el_pagination/testmodel_list_page.html'\n    model_template_name = 'el_pagination/testmodel_list.html'\n    page_template = 'page_template.html'\n    template_name = 'template.html'\n    url = '/?page=2'\n\n    def setUp(self):\n        factory = RequestFactory()\n        self.request = factory.get(self.url)\n        self.ajax_request = factory.get(\n            self.url, HTTP_X_REQUESTED_WITH='XMLHttpRequest')\n\n    def check_response(self, response, template_name, object_list):\n        \"\"\"Execute several assertions on the response.\n\n        Check that the response has a successful status code,\n        uses ``template_name`` and contains ``object_list``.\n        \"\"\"\n        self.assertEqual(200, response.status_code)\n        self.assertSequenceEqual([template_name], response.template_name)\n        self.assertSequenceEqual(\n            list(object_list), response.context_data['object_list'])\n\n    def make_view(self, *args, **kwargs):\n        \"\"\"Return an instance of AjaxListView.\"\"\"\n        return views.AjaxListView.as_view(*args, **kwargs)\n\n    def test_list(self):\n        # Ensure the view correctly adds the list to context.\n        view = self.make_view(\n            queryset=range(30),\n            template_name=self.template_name,\n            page_template=self.page_template,\n        )\n        response = view(self.request)\n        self.check_response(response, self.template_name, range(30))\n\n    def test_list_ajax(self):\n        # Ensure the list view switches templates when the request is Ajax.\n        view = self.make_view(\n            queryset=range(30),\n            template_name=self.template_name,\n            page_template=self.page_template,\n        )\n        response = view(self.ajax_request)\n        self.check_response(response, self.page_template, range(30))\n\n    def test_queryset(self):\n        # Ensure the view correctly adds the queryset to context.\n        queryset = make_model_instances(30)\n        view = self.make_view(queryset=queryset)\n        response = view(self.request)\n        self.check_response(response, self.model_template_name, queryset)\n\n    def test_queryset_ajax(self):\n        # Ensure the queryset view switches templates when the request is Ajax.\n        queryset = make_model_instances(30)\n        view = self.make_view(queryset=queryset)\n        response = view(self.ajax_request)\n        self.check_response(response, self.model_page_template, queryset)\n\n    def test_model(self):\n        # Ensure the view correctly uses the model to generate the template.\n        queryset = make_model_instances(30)\n        view = self.make_view(model=TestModel)\n        response = view(self.request)\n        self.check_response(response, self.model_template_name, queryset)\n\n    def test_model_ajax(self):\n        # Ensure the model view switches templates when the request is Ajax.\n        queryset = make_model_instances(30)\n        view = self.make_view(model=TestModel)\n        response = view(self.ajax_request)\n        self.check_response(response, self.model_page_template, queryset)\n\n    def test_missing_queryset_or_model(self):\n        # An error is raised if both queryset and model are not provided.\n        view = self.make_view()\n        with self.assertRaises(ImproperlyConfigured) as cm:\n            view(self.request)\n        self.assertIn('queryset', str(cm.exception))\n        self.assertIn('model', str(cm.exception))\n\n    def test_missing_page_template(self):\n        # An error is raised if the ``page_template`` name is not provided.\n        view = self.make_view(queryset=range(30))\n        with self.assertRaises(ImproperlyConfigured) as cm:\n            view(self.request)\n        self.assertIn('page_template', str(cm.exception))\n\n    def test_do_not_allow_empty(self):\n        # An error is raised if the list is empty and ``allow_empty`` is\n        # set to False.\n        view = self.make_view(model=TestModel, allow_empty=False)\n        with self.assertRaises(Http404) as cm:\n            view(self.request)\n        self.assertIn('allow_empty', str(cm.exception))\n\n    def test_view_in_context(self):\n        # Ensure the view is included in the template context.\n        view = self.make_view(\n            queryset=range(30),\n            page_template=self.page_template,\n        )\n        response = view(self.ajax_request)\n        view_instance = response.context_data['view']\n        self.assertIsInstance(view_instance, views.AjaxListView)\n"
  },
  {
    "path": "el_pagination/utils.py",
    "content": "\"\"\"Django EL Pagination utility functions.\"\"\"\n\nfrom el_pagination import exceptions\nfrom el_pagination.settings import (\n    DEFAULT_CALLABLE_AROUNDS,\n    DEFAULT_CALLABLE_ARROWS,\n    DEFAULT_CALLABLE_EXTREMES,\n    PAGE_LABEL,\n)\n\n\ndef get_data_from_context(context):\n    \"\"\"Get the django paginator data object from the given *context*.\n\n    The context is a dict-like object. If the context key ``endless``\n    is not found, a *PaginationError* is raised.\n    \"\"\"\n    try:\n        return context['endless']\n    except KeyError as exc:\n        raise exceptions.PaginationError(\n            'Cannot find endless data in context.'\n        ) from exc\n\n\ndef get_page_number_from_request(request, querystring_key=PAGE_LABEL, default=1):\n    \"\"\"Retrieve the current page number from *GET* or *POST* data.\n\n    If the page does not exists in *request*, or is not a number,\n    then *default* number is returned.\n    \"\"\"\n    try:\n        return int(request.GET.get(querystring_key, request.POST.get(querystring_key)))\n    except (KeyError, TypeError, ValueError):\n        return default\n\n\ndef get_page_numbers(\n    current_page,\n    num_pages,\n    extremes=DEFAULT_CALLABLE_EXTREMES,\n    arounds=DEFAULT_CALLABLE_AROUNDS,\n    arrows=DEFAULT_CALLABLE_ARROWS,\n):\n    \"\"\"Default callable for page listing.\n\n    Produce a Digg-style pagination.\n    \"\"\"\n    page_range = range(1, num_pages + 1)\n    pages = []\n    if current_page != 1:\n        if arrows:\n            pages.append('first')\n        pages.append('previous')\n\n    # Get first and last pages (extremes).\n    first = page_range[:extremes]\n    pages.extend(first)\n    last = page_range[-extremes:]\n\n    # Get the current pages (arounds).\n    current_start = max(current_page - arounds - 1, 0)\n    current_end = min(current_page + arounds, num_pages)\n    current = page_range[current_start:current_end]\n\n    # Mix first with current pages.\n    to_add = current\n    if extremes:\n        diff = current[0] - first[-1]\n        if diff > 1:\n            pages.append(None)\n        elif diff < 1:\n            to_add = current[abs(diff) + 1 :]\n    pages.extend(to_add)\n\n    # Mix current with last pages.\n    if extremes:\n        diff = last[0] - current[-1]\n        to_add = last\n        if diff > 1:\n            pages.append(None)\n        elif diff < 1:\n            to_add = last[abs(diff) + 1 :]\n        pages.extend(to_add)\n\n    if current_page != num_pages:\n        pages.append('next')\n        if arrows:\n            pages.append('last')\n    return pages\n\n\ndef _iter_factors(starting_factor=1):\n    \"\"\"Generator yielding something like 1, 3, 10, 30, 100, 300 etc.\n\n    The series starts from starting_factor.\n    \"\"\"\n    while True:\n        yield starting_factor\n        yield starting_factor * 3\n        starting_factor *= 10\n\n\ndef _make_elastic_range(begin, end):\n    \"\"\"Generate an S-curved range of pages.\n\n    Start from both left and right, adding exponentially growing indexes,\n    until the two trends collide.\n    \"\"\"\n    # Limit growth for huge numbers of pages.\n    starting_factor = max(1, (end - begin) // 100)\n    factor = _iter_factors(starting_factor)\n    left_half, right_half = [], []\n    left_val, right_val = begin, end\n\n    while left_val < right_val:\n        left_half.append(left_val)\n        right_half.append(right_val)\n        next_factor = next(factor)\n        left_val = begin + next_factor\n        right_val = end - next_factor\n    # If the trends happen to meet exactly at one point, retain it.\n    if left_val == right_val:\n        left_half.append(left_val)\n    right_half.reverse()\n    return left_half + right_half\n\n\ndef get_elastic_page_numbers(current_page, num_pages):\n    \"\"\"Alternative callable for page listing.\n\n    Produce an adaptive pagination, useful for big numbers of pages, by\n    splitting the num_pages ranges in two parts at current_page. Each part\n    will have its own S-curve.\n    \"\"\"\n    if num_pages <= 10:\n        return list(range(1, num_pages + 1))\n    if current_page == 1:\n        pages = [1]\n    else:\n        pages = ['first', 'previous']\n        pages.extend(_make_elastic_range(1, current_page))\n    if current_page != num_pages:\n        pages.extend(_make_elastic_range(current_page, num_pages)[1:])\n        pages.extend(['next', 'last'])\n    return pages\n\n\ndef get_querystring_for_page(request, page_number, querystring_key, default_number=1):\n    \"\"\"Return a querystring pointing to *page_number*.\"\"\"\n    querydict = request.GET.copy()\n    querydict[querystring_key] = page_number\n    # For the default page number (usually 1) the querystring is not required.\n    if page_number == default_number:\n        del querydict[querystring_key]\n    if 'querystring_key' in querydict:\n        del querydict['querystring_key']\n    if querydict:\n        return '?' + querydict.urlencode()\n    return ''\n\n\ndef normalize_page_number(page_number, page_range):\n    \"\"\"Handle a negative *page_number*.\n\n    Return a positive page number contained in *page_range*.\n    If the negative index is out of range, return the page number 1.\n    \"\"\"\n    try:\n        return page_range[page_number]\n    except IndexError:\n        return page_range[0]\n"
  },
  {
    "path": "el_pagination/views.py",
    "content": "\"\"\"Django EL Pagination class-based views.\"\"\"\n\nfrom django.core.exceptions import ImproperlyConfigured\nfrom django.http import Http404\nfrom django.utils.encoding import smart_str\nfrom django.utils.translation import gettext as _\nfrom django.views.generic.base import View\nfrom django.views.generic.list import MultipleObjectTemplateResponseMixin\n\nfrom el_pagination.settings import PAGE_LABEL\n\n\nclass MultipleObjectMixin:\n    allow_empty = True\n    context_object_name = None\n    model = None\n    queryset = None\n\n    def get_queryset(self):\n        \"\"\"Get the list of items for this view.\n\n        This must be an iterable, and may be a queryset\n        (in which qs-specific behavior will be enabled).\n\n        See original in ``django.views.generic.list.MultipleObjectMixin``.\n        \"\"\"\n        if self.queryset is not None:\n            queryset = self.queryset\n            if hasattr(queryset, '_clone'):\n                queryset = queryset._clone()  # pylint: disable=protected-access\n        elif self.model is not None:\n            queryset = (\n                self.model._default_manager.all()  # pylint: disable=protected-access\n            )\n        else:\n            msg = '{0} must define ``queryset`` or ``model``'\n            raise ImproperlyConfigured(msg.format(self.__class__.__name__))\n        return queryset\n\n    def get_allow_empty(self):\n        \"\"\"Returns True if the view should display empty lists.\n\n        Return False if a 404 should be raised instead.\n\n        See original in ``django.views.generic.list.MultipleObjectMixin``.\n        \"\"\"\n        return self.allow_empty\n\n    def get_context_object_name(self, object_list):\n        \"\"\"Get the name of the item to be used in the context.\n\n        See original in ``django.views.generic.list.MultipleObjectMixin``.\n        \"\"\"\n        if self.context_object_name:\n            return self.context_object_name\n        if hasattr(object_list, 'model'):\n            object_name = object_list.model._meta.object_name.lower()\n            return smart_str(f'{object_name}_list')\n        return None\n\n    def get_context_data(self, **kwargs):\n        \"\"\"Get the context for this view.\n\n        Also adds the *page_template* variable in the context.\n\n        If the *page_template* is not given as a kwarg of the *as_view*\n        method then it is generated using app label, model name\n        (obviously if the list is a queryset), *self.template_name_suffix*\n        and *self.page_template_suffix*.\n\n        For instance, if the list is a queryset of *blog.Entry*,\n        the template will be ``blog/entry_list_page.html``.\n        \"\"\"\n        queryset = kwargs.pop('object_list')\n        page_template = kwargs.pop('page_template')\n\n        context_object_name = self.get_context_object_name(queryset)\n        context = {'object_list': queryset, 'view': self}\n        context.update(kwargs)\n        if context_object_name is not None:\n            context[context_object_name] = queryset\n\n        if page_template is None:\n            if hasattr(queryset, 'model'):\n                page_template = self.get_page_template(**kwargs)\n            else:\n                raise ImproperlyConfigured('AjaxListView requires a page_template')\n        context['page_template'] = self.page_template = page_template\n\n        return context\n\n\nclass BaseListView(MultipleObjectMixin, View):\n    object_list = None\n\n    def get(self, request, *args, **kwargs):\n        self.object_list = self.get_queryset()\n        allow_empty = self.get_allow_empty()\n        if not allow_empty and len(self.object_list) == 0:\n            msg = _('Empty list and ``%(class_name)s.allow_empty`` is False.')\n            raise Http404(msg % {'class_name': self.__class__.__name__})\n        context = self.get_context_data(\n            object_list=self.object_list,\n            page_template=self.page_template,\n        )\n        return self.render_to_response(context)  # pylint: disable=no-member\n\n\nclass InvalidPaginationListView:\n    def get(self, request, *args, **kwargs):\n        \"\"\"Wraps super().get(...) in order to return 404 status code if\n        the page parameter is invalid\n        \"\"\"\n        response = super().get(request, args, kwargs)  # pylint: disable=no-member\n        try:\n            response.render()\n        except Http404:\n            request.GET = request.GET.copy()\n            request.GET['page'] = '1'\n            response = super().get(request, args, kwargs)  # pylint: disable=no-member\n            response.status_code = 404\n\n        return response\n\n\nclass AjaxMultipleObjectTemplateResponseMixin(MultipleObjectTemplateResponseMixin):\n    key = PAGE_LABEL\n    page_template = None\n    page_template_suffix = '_page'\n    template_name_suffix = '_list'\n\n    def get_page_template(self, **kwargs):\n        \"\"\"Return the template name used for this request.\n\n        Only called if *page_template* is not given as a kwarg of\n        *self.as_view*.\n        \"\"\"\n        opts = self.object_list.model._meta\n        object_name = opts.object_name.lower()\n        t_name_suffix = self.template_name_suffix\n        page_template_suffix = self.page_template_suffix\n        return (\n            f'{opts.app_label}/{object_name}{t_name_suffix}{page_template_suffix}.html'\n        )\n\n    def get_template_names(self):\n        \"\"\"Switch the templates for Ajax requests.\"\"\"\n        request = self.request\n        key = 'querystring_key'\n        querystring_key = request.GET.get(key, request.POST.get(key, PAGE_LABEL))\n        if (\n            request.headers.get('x-requested-with') == 'XMLHttpRequest'\n            and querystring_key == self.key\n        ):\n            return [self.page_template or self.get_page_template()]\n        return super().get_template_names()\n\n\nclass AjaxListView(AjaxMultipleObjectTemplateResponseMixin, BaseListView):\n    \"\"\"Allows Ajax pagination of a list of objects.\n\n    You can use this class-based view in place of *ListView* in order to\n    recreate the behaviour of the *page_template* decorator.\n\n    For instance, assume you have this code (taken from Django docs)::\n\n        from django.conf.urls.defaults import *\n        from django.views.generic import ListView\n\n        from books.models import Publisher\n\n        urlpatterns = patterns('',\n            (r'^publishers/$', ListView.as_view(model=Publisher)),\n        )\n\n    You want to Ajax paginate publishers, so, as seen, you need to switch\n    the template if the request is Ajax and put the page template\n    into the context as a variable named *page_template*.\n\n    This is straightforward, you only need to replace the view class, e.g.::\n\n        from django.conf.urls.defaults import *\n\n        from books.models import Publisher\n\n        from el_pagination.views import AjaxListView\n\n        urlpatterns = patterns('',\n            (r'^publishers/$', AjaxListView.as_view(model=Publisher)),\n        )\n\n    NOTE: Django >= 1.3 is required to use this view.\n    \"\"\"\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[build-system]\nrequires = [\"setuptools>=45\", \"wheel\"]\nbuild-backend = \"setuptools.build_meta\"\n\n[project]\nname = \"django-el-pagination\"\ndynamic = [\"version\"]\ndescription = \"Django pagination tools supporting Ajax, multiple and lazy pagination, Twitter-style and Digg-style pagination.\"\nreadme = \"README.rst\"\nrequires-python = \">=3.8\"\nlicense = {file = \"LICENSE\"}\nauthors = [\n    {name = \"Francesco Banconi\"},\n    {name = \"Oleksandr Shtalinberg\", email = \"O.Shtalinberg@gmail.com\"}\n]\ndependencies = [\n    \"django>=3.2.0\",\n]\nkeywords = [\"django\", \"pagination\", \"ajax\", \"endless\",]\nclassifiers = [\n    \"Development Status :: 5 - Production/Stable\",\n    \"Environment :: Web Environment\",\n    \"Framework :: Django\",\n    \"Framework :: Django :: 3.2\",\n    \"Framework :: Django :: 4.2\",\n    \"Framework :: Django :: 5.0\",\n    \"Framework :: Django :: 5.1\",\n    \"Framework :: Django :: 5.2\",\n    \"Intended Audience :: Developers\",\n    \"License :: OSI Approved :: MIT License\",\n    \"Operating System :: OS Independent\",\n    \"Programming Language :: Python\",\n    \"Programming Language :: Python :: 3\",\n    \"Programming Language :: Python :: 3.8\",\n    \"Programming Language :: Python :: 3.9\",\n    \"Programming Language :: Python :: 3.10\",\n    \"Programming Language :: Python :: 3.11\",\n    \"Programming Language :: Python :: 3.12\",\n    \"Programming Language :: Python :: 3.13\",\n    \"Topic :: Utilities\",\n]\n\n[project.urls]\nHomepage = \"http://github.com/shtalinberg/django-el-pagination\"\nDocumentation = \"http://django-el-pagination.readthedocs.org/\"\nRepository = \"https://github.com/shtalinberg/django-el-pagination.git\"\n\"Bug Tracker\" = \"https://github.com/shtalinberg/django-el-pagination/issues\"\n\n[tool.setuptools]\npackages = [\"el_pagination\", \"el_pagination.templatetags\"]\ninclude-package-data = true\nzip-safe = false\n\n[tool.setuptools.dynamic]\nversion = {attr = \"el_pagination.get_version\"}\n\n[tool.setuptools.package-data]\nel_pagination = [\n    \"templates/el_pagination/*.html\",\n    \"static/el-pagination/js/*.js\",\n    \"locale/*/LC_MESSAGES/*.mo\",\n    \"locale/*/LC_MESSAGES/*.po\",\n    \"templatetags/*.py\",\n]\n\n[tool.black]\nline_length = 88\nskip-string-normalization = true\ntarget-version = ['py38']\ninclude = '\\.pyi?$'\nextend-exclude = '''\n/(\n  # The following are specific for Django.\n  | tests\n  | migrations\n  | \\.venv\n)/\n'''\n\n[tool.isort]\nprofile = \"black\"\nblocked_extensions = [\n    \"rst\",\"html\",\"js\",\"svg\",\"txt\",\"css\",\"scss\",\"png\",\"snap\",\"tsx\"\n]\ncombine_as_imports = true\ndefault_section = \"THIRDPARTY\"\nforce_grid_wrap = 0\ninclude_trailing_comma = true\nuse_parentheses = true\nknown_django = \"django\"\nsections=[\"FUTURE\",\"STDLIB\",\"DJANGO\",\"THIRDPARTY\",\"FIRSTPARTY\",\"LOCALFOLDER\"]\nskip = [\"migrations\",\".git\",\"__pycache__\",\"LC_MESSAGES\"]\nsrc_paths = [\"el_pagination\",\"tests\"]\nline_length = 88\nmulti_line_output = 5\nno_lines_before=\"LOCALFOLDER\"\n"
  },
  {
    "path": "release-requirements.txt",
    "content": "build>=1.0.0\ntwine>=4.0.0"
  },
  {
    "path": "setup.cfg",
    "content": "[bdist_wheel]\nuniversal = 0\n\n[flake8]\nexclude = docs/*,.tox,.git,build,dist\nignore = E123,E128,E402,W503,E731,W601\nmax-line-length = 119\n\n[isort]\nnot_skip=__init__.py\natomic=True\nmulti_line_output=5\ninclude_trailing_comma=True\nbalanced=True\n\n[coverage:run]\nsource = el_pagination\nomit = */tests/*\nbranch = True\n\n[metadata]\nlicense_files = LICENSE\n"
  },
  {
    "path": "setup.py",
    "content": "from setuptools import setup\n\nif __name__ == \"__main__\":\n    setup()\n"
  },
  {
    "path": "tests/__init__.py",
    "content": ""
  },
  {
    "path": "tests/develop.py",
    "content": "\"\"\"Create a development and testing environment using a virtualenv.\"\"\"\nimport os\nimport subprocess\nimport sys\n\nTESTS = os.path.abspath(os.path.dirname(__file__))\nREQUIREMENTS = os.path.join(TESTS, 'requirements.pip')\nWITH_VENV = os.path.join(TESTS, 'with_venv.sh')\nVENV = os.path.abspath(os.path.join(TESTS, '..', '.venv'))\n\n\ndef call(*args):\n    \"\"\"Simple ``subprocess.call`` wrapper.\"\"\"\n    if subprocess.call(args):\n        raise SystemExit('Error running {0}.'.format(args))\n\n\ndef pip_install(*args):\n    \"\"\"Install packages using pip inside the venv.\"\"\"\n    call(WITH_VENV, '.venv', 'pip', 'install', *args)\n\n\nif __name__ == '__main__':\n    call(sys.executable, '-m', 'venv', VENV)\n    pip_install('-r', REQUIREMENTS)\n"
  },
  {
    "path": "tests/manage.py",
    "content": "#!/usr/bin/env python\n\"\"\"Django's command-line utility for administrative tasks.\"\"\"\nimport os\nimport sys\n\n\ndef main():\n    \"\"\"Run administrative tasks.\"\"\"\n    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'project.settings')\n    try:\n        from django.core.management import execute_from_command_line\n    except ImportError as exc:\n        raise ImportError(\n            \"Couldn't import Django. Are you sure it's installed and \"\n            \"available on your PYTHONPATH environment variable? Did you \"\n            \"forget to activate a virtual environment?\"\n        ) from exc\n    execute_from_command_line(sys.argv)\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "tests/project/__init__.py",
    "content": ""
  },
  {
    "path": "tests/project/context_processors.py",
    "content": "\"\"\"Navigation bar context processor.\"\"\"\n\nimport platform\n\nimport django\nfrom django.urls import reverse\n\nimport el_pagination\n\nVOICES = (\n    # Name and label pairs.\n    ('complete', 'Complete example'),\n    ('digg', 'Digg-style'),\n    ('twitter', 'Twitter-style'),\n    ('onscroll', 'On scroll'),\n    ('feed-wrapper', 'Feed wrapper'),\n    ('multiple', 'Multiple'),\n    ('callbacks', 'Callbacks'),\n    ('chunks', 'On scroll/chunks'),\n    ('digg-table', 'Digg-style table'),\n    ('twitter-table', 'Twitter-style table'),\n    ('onscroll-table', 'On scroll table'),\n)\n\n\ndef navbar(request):\n    \"\"\"Generate a list of voices for the navigation bar.\"\"\"\n    voice_list = []\n    current_path = request.path\n    for name, label in VOICES:\n        path = reverse(name)\n        voice_list.append(\n            {\n                'label': label,\n                'path': path,\n                'is_active': path == current_path,\n            }\n        )\n    return {'navbar': voice_list}\n\n\ndef versions(request):\n    \"\"\"Add to context the version numbers of relevant apps.\"\"\"\n    values = (\n        ('Python', platform.python_version()),\n        ('Django', django.get_version()),\n        ('EL Pagination', el_pagination.get_version()),\n    )\n    return {'versions': values}\n"
  },
  {
    "path": "tests/project/models.py",
    "content": "from django.db import models\n\n\ndef make_model_instances(number):\n    \"\"\"Make a ``number`` of test model instances and return a queryset.\"\"\"\n    for _ in range(number):\n        TestModel.objects.create()\n    return TestModel.objects.all().order_by('pk')\n\n\nclass TestModel(models.Model):\n    \"\"\"A model used in tests.\"\"\"\n\n    class Meta:\n        app_label = 'el_pagination'\n\n    def __str__(self):\n        return f'TestModel: {self.id}'\n"
  },
  {
    "path": "tests/project/settings.py",
    "content": "import os\nimport sys\n\n\"\"\"Settings file for the Django project used for tests.\"\"\"\n\nDEBUG = True\nALLOWED_HOSTS = ['*']\n# Disable 1.9 arguments '--parallel' and try exclude  “Address already in use” at “setUpClass”\nos.environ['DJANGO_TEST_PROCESSES'] = \"1\"\nos.environ['DJANGO_LIVE_TEST_SERVER_ADDRESS'] = \"localhost:8000-8010,8080,9200-9300\"\n\nPROJECT_NAME = 'project'\n\n# Base paths.\nROOT = os.path.abspath(os.path.dirname(__file__))\nPROJECT = os.path.join(ROOT, PROJECT_NAME)\n\n# Django configuration.\nDATABASES = {\n    'default': {\n        'ENGINE': 'django.db.backends.sqlite3',\n        'NAME': ':memory:',\n    }\n}\nDEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'\n\nINSTALLED_APPS = (\n    'django.contrib.staticfiles',\n    'el_pagination',\n    PROJECT_NAME,\n)\ngettext = lambda s: s\n\nLANGUAGES = (('en', gettext('English')),)\nLANGUAGE_CODE = os.getenv('EL_PAGINATION_LANGUAGE_CODE', 'en')\nROOT_URLCONF = PROJECT_NAME + '.urls'\nSECRET_KEY = os.getenv('EL_PAGINATION_SECRET_KEY', 'secret')\nSITE_ID = 1\nSTATIC_ROOT = os.path.join(PROJECT, 'static')\nSTATIC_URL = '/static/'\nUSE_TZ = True\n\nSTATICFILES_FINDERS = (\n    'django.contrib.staticfiles.finders.FileSystemFinder',\n    'django.contrib.staticfiles.finders.AppDirectoriesFinder',\n)\n\nTEMPLATES = [\n    {\n        'BACKEND': 'django.template.backends.django.DjangoTemplates',\n        'DIRS': [\n            os.path.join(PROJECT, 'templates'),\n        ],\n        'APP_DIRS': True,\n        'OPTIONS': {\n            'debug': DEBUG,\n            'context_processors': [\n                'django.template.context_processors.debug',\n                'django.template.context_processors.request',\n                'django.template.context_processors.media',\n                'django.template.context_processors.static',\n                PROJECT_NAME + '.context_processors.navbar',\n                PROJECT_NAME + '.context_processors.versions',\n            ],\n        },\n    },\n]\n\nMIDDLEWARE = ('django.middleware.common.CommonMiddleware',)\n\ntry:\n    from settings_local import *  # noqa\n\n    INSTALLED_APPS = INSTALLED_APPS + INSTALLED_APPS_LOCAL  # noqa\nexcept ImportError:\n    sys.stderr.write('settings_local.py not loaded\\n')\n\nTEMPLATES[0]['OPTIONS']['debug'] = DEBUG\n"
  },
  {
    "path": "tests/project/static/pagination.css",
    "content": "/* Customized css for the pagination elements. */\n\n.pagination div,\n.pagination td,\n.endless_container {\n  display: inline-block;\n  *display: inline;\n  margin-bottom: 0;\n  margin-left: 0;\n  -webkit-border-radius: 3px;\n     -moz-border-radius: 3px;\n          border-radius: 3px;\n  *zoom: 1;\n  -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);\n     -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);\n          box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);\n}\n\n.pagination div > a,\n.pagination td > a,\n.pagination span,\n.endless_container a {\n  float: left;\n  padding: 0 14px;\n  line-height: 38px;\n  background-color: #ffffff;\n  border: 1px solid #dddddd;\n  border-left-width: 0;\n}\n\n.pagination div > a:hover,\n.pagination td > a:hover,\n.pagination .endless_page_current,\n .endless_container a:hover {\n  background-color: #eeeeee;\n}\n\n.pagination div > a:first-child,\n.pagination div > span:first-child,\n.pagination td > a:first-child,\n.pagination td > span:first-child,\n.endless_container a {\n  border-left-width: 1px;\n  -webkit-border-radius: 3px 0 0 3px;\n     -moz-border-radius: 3px 0 0 3px;\n          border-radius: 3px 0 0 3px;\n}\n\n.endless_container {\n  width: 50%;\n}\n\n.endless_container a {\n  text-align: center;\n  width: inherit;\n  text-decoration: none;\n}\n\ntd {\n  padding: 20px;\n}\n"
  },
  {
    "path": "tests/project/templates/404.html",
    "content": ""
  },
  {
    "path": "tests/project/templates/500.html",
    "content": ""
  },
  {
    "path": "tests/project/templates/base.html",
    "content": "<!DOCTYPE html>{% load static %}\n<html>\n  <head>\n    <meta content='text/html; charset=utf-8' http-equiv='Content-Type' />\n    <title>{% block title %}Testing project{% endblock %} - Django Endless Pagination</title>\n    <link href=\"http://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/2.3.2/css/bootstrap.min.css\" rel=\"stylesheet\">\n    <link href=\"{% static \"pagination.css\" %}\" rel=\"stylesheet\">\n  </head>\n  <body>\n    <div class=\"container\">\n      <dl class=\"dl-horizontal pull-right\">\n        {% for name, value in versions %}\n            <dt class=\"muted\">\n              {{ name }}\n            </dt>\n            <dd><span class=\"label label-info\">v{{ value }}</span></dd>\n        {% endfor %}\n      </dl>\n      <div class=\"page-header\">\n        <h1>Django EL(Endless) Pagination <small>Test Project</small></h1>\n      </div>\n      <div class=\"navbar\">\n        <div class=\"navbar-inner\">\n          <a class=\"brand\" href=\"{% url \"home\" %}\">Home</a>\n          <ul class=\"nav\">\n            {% for voice in navbar %}\n              <li{% if voice.is_active %} class=\"active\"{% endif %}>\n                <a href=\"{{ voice.path }}\">{{ voice.label }}</a>\n              </li>\n            {% endfor %}\n          </ul>\n        </div>\n      </div>\n      <div class=\"row\">\n        {% block content %}{% endblock %}\n      </div>\n    </div>\n    {% block js %}\n      <script src=\"http://code.jquery.com/jquery-latest.min.js\"></script>\n      <script src=\"http://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/2.3.2/js/bootstrap.min.js\"></script>\n      <script src=\"{% static \"el-pagination/js/el-pagination.js\" %}\"></script>\n    {% endblock %}\n  </body>\n</html>\n"
  },
  {
    "path": "tests/project/templates/callbacks/index.html",
    "content": "{% extends \"base.html\" %}\n\n{% block content %}\n  <div id=\"endless\" class=\"span8\">\n    {% include page_template %}\n  </div>\n  <div class=\"span4\">\n    <div class=\"well\">\n      <h4>Notifications</h4>\n      <div id=\"notifications\">None so far...</div>\n    </div>\n  </div>\n{% endblock %}\n\n{% block js %}\n  {{ block.super }}\n  <script>\n    // Get a reference of the notifications element.\n    var notifications = $('#notifications');\n\n    // Add a notification: the element containing the value will have *id*.\n    var notify = function(id, key, value) {\n      var key_element = $('<strong />').html(key + ': ');\n      var value_element = $('<span />').attr('id', id).html(value);\n      var notification = $('<p />').append(key_element).append(value_element);\n      notifications.append(notification);\n    };\n\n    // Paginate!\n    $.endlessPaginate({\n      onClick: function(context) {\n        notifications.html('');\n        notify('onclick', 'First object on click', $('#endless h4').html());\n        notify('onclick-label', 'Clicked label', $(this).text());\n        notify('onclick-url', 'URL', context.url);\n        notify('onclick-key', 'Querystring key', context.key);\n      },\n      onCompleted: function(context, fragment) {\n        notify(\n          'oncompleted', 'First object on completed', $('#endless h4').html());\n        notify('oncompleted-label', 'Clicked label', $(this).text());\n        notify('oncompleted-url', 'URL', context.url);\n        notify('oncompleted-key', 'Querystring key', context.key);\n        notify(\n          'fragment', 'Last object in fragment',\n          $(fragment).find('h4').last().html());\n      },\n      pageSelector: '#endless'\n    });\n  </script>\n{% endblock %}\n"
  },
  {
    "path": "tests/project/templates/callbacks/page.html",
    "content": "{% load el_pagination_tags %}\n\n{% paginate 3 objects %}\n{% for object in objects %}\n  <div class=\"well object\">\n    <h4>{{ object.title }}</h4>\n    {{ object.contents }}\n  </div>\n{% endfor %}\n{% get_pages %}\n<div>\n    Objects <strong>{{ pages.current_start_index }}</strong> ...\n    <strong>{{ pages.current_end_index }}</strong>\n    of <strong>{{ pages.total_count }}</strong>\n</div>\n<div class=\"pagination pagination-centered\">\n  <div>{{ pages.get_rendered }}</div>\n</div>\n"
  },
  {
    "path": "tests/project/templates/chunks/index.html",
    "content": "{% extends \"base.html\" %}\n\n{% block content %}\n  <div class=\"objects span5\">\n    {% include \"chunks/objects_page.html\" %}\n  </div>\n  <div class=\"items span5\">\n    {% include \"chunks/items_page.html\" %}\n  </div>\n  <div class=\"span2\">\n    <strong>Objects</strong> are paginated on scroll using 3 pages chunks.\n    <hr>\n    <strong>Items</strong> are paginated on scroll using 4 pages chunks.\n  </div>\n{% endblock %}\n\n{% block js %}\n  {{ block.super }}\n  <script>\n    $('.objects').endlessPaginate({\n      paginateOnScroll: true,\n      paginateOnScrollChunkSize: 3\n    });\n    $('.items').endlessPaginate({\n      paginateOnScroll: true,\n      paginateOnScrollChunkSize: 4\n    });\n  </script>\n{% endblock %}\n"
  },
  {
    "path": "tests/project/templates/chunks/items_page.html",
    "content": "{% load el_pagination_tags %}\n\n{% paginate 5 items using \"items-page\" %}\n{% for item in items %}\n  <div class=\"well item\">\n    <h4>{{ item.title }}</h4>\n    {{ item.contents }}\n  </div>\n{% endfor %}\n{% show_more \"More results\" %}\n"
  },
  {
    "path": "tests/project/templates/chunks/objects_page.html",
    "content": "{% load el_pagination_tags %}\n\n{% paginate 5 objects %}\n{% for object in objects %}\n  <div class=\"well object\">\n    <h4>{{ object.title }}</h4>\n    {{ object.contents }}\n  </div>\n{% endfor %}\n{% show_more \"More results\" %}\n\n"
  },
  {
    "path": "tests/project/templates/complete/articles_page.html",
    "content": "{% load el_pagination_tags %}\n\n{% lazy_paginate 3 articles using \"articles-page\" %}\n{% for article in articles %}\n  <div class=\"well article\">\n    <h4>{{ article.title }}</h4>\n    {{ article.contents }}\n  </div>\n{% endfor %}\n{% show_more \"More results\" %}\n"
  },
  {
    "path": "tests/project/templates/complete/entries_page.html",
    "content": "{% load el_pagination_tags %}\n\n{% lazy_paginate 1,3 entries using \"entries-page\" %}\n{% for entry in entries %}\n  <div class=\"well entry\">\n    <h4>{{ entry.title }}</h4>\n    {{ entry.contents }}\n  </div>\n{% endfor %}\n{% show_more %}\n"
  },
  {
    "path": "tests/project/templates/complete/index.html",
    "content": "{% extends \"base.html\" %}\n\n{% block content %}\n  <div class=\"span5\">\n    {% include \"complete/objects_page.html\" %}\n    {% comment %}\n    <div id=\"objects_simple\">\n        {% include \"complete/objects_simple_page.html\" %}\n    </div>\n    <div id=\"objects\">\n        {% include \"complete/objects_page.html\" %}\n    </div>\n    {% endcomment %}\n  </div>\n  <div id=\"items\" class=\"span5\">\n    {% include \"complete/items_page.html\" %}\n  </div>\n  <div class=\"span2\">\n    This complete example shows several pagination styles.\n    <hr>\n    <strong>Objects</strong> are paginated using Digg-style, defaulting to the\n    last page, with no Ajax involved.\n    <hr>\n    <strong>Items</strong> are paginated using Digg-style with Ajax support.\n  </div>\n  <div id=\"entries\" class=\"span5\">\n    {% include \"complete/entries_page.html\" %}\n  </div>\n  <div id=\"articles\" class=\"span5\">\n    {% include \"complete/articles_page.html\" %}\n  </div>\n  <div class=\"span2\">\n    <strong>Entries</strong> are paginated using Twitter-style with Ajax\n    enabled. The first page contains just one entry. Subsequent pages\n    contain three entries.\n    <hr>\n    <strong>Articles</strong> are paginated using Twitter-style. The subsequent\n    pages are loaded on scroll.\n  </div>\n{% endblock %}\n\n{% block js %}\n  {{ block.super }}\n  <script>\n    // Paginate items.\n    $('#items').endlessPaginate({pageSelector: '#items'});\n    // Paginate entries.\n    $('#entries').endlessPaginate();\n    // Paginate articles.\n    $('#articles').endlessPaginate({paginateOnScroll: true});\n  </script>\n{% endblock %}\n"
  },
  {
    "path": "tests/project/templates/complete/items_page.html",
    "content": "{% load el_pagination_tags %}\n\n{% paginate 3 items using \"items-page\" %}\n{% for item in items %}\n  <div class=\"well item\">\n    <h4>{{ item.title }}</h4>\n    {{ item.contents }}\n  </div>\n{% endfor %}\n<div class=\"pagination pagination-centered\">\n  <div>{% show_pages %}</div>\n</div>\n"
  },
  {
    "path": "tests/project/templates/complete/objects_page.html",
    "content": "{% load el_pagination_tags %}\n\n{% paginate 3 objects starting from page -1 using \"objects-page\" %}\n{% for object in objects %}\n  <div class=\"well object\">\n    <h4>{{ object.title }}</h4>\n    {{ object.contents }}\n  </div>\n{% endfor %}\n<div class=\"pagination pagination-centered\">\n  <div>{% show_pages %}</div>\n</div>\n"
  },
  {
    "path": "tests/project/templates/complete/objects_simple_page.html",
    "content": "{% load el_pagination_tags %}\n\n{% paginate 3 objects %}\n{% for object in objects %}\n  <div class=\"well object\">\n    <h4>{{ object.title }}</h4>\n    {{ object.contents }}\n  </div>\n{% endfor %}\n<div class=\"pagination pagination-centered\">\n  <div>{% show_pages %}</div>\n</div>\n"
  },
  {
    "path": "tests/project/templates/digg/index.html",
    "content": "{% extends \"base.html\" %}\n\n{% block content %}\n  <div class=\"endless_page_template span12\">\n    {% include page_template %}\n  </div>\n{% endblock %}\n\n{% block js %}\n  {{ block.super }}\n  <script>\n    $.endlessPaginate();\n  </script>\n{% endblock %}\n"
  },
  {
    "path": "tests/project/templates/digg/page.html",
    "content": "{% load el_pagination_tags %}\n\n{% paginate 5 objects %}\n{% for object in objects %}\n  <div class=\"well object\">\n    <h4>{{ object.title }}</h4>\n    {{ object.contents }}\n  </div>\n{% endfor %}\n<div class=\"pagination pagination-centered\">\n  <div>{% show_pages %}</div>\n</div>\n"
  },
  {
    "path": "tests/project/templates/digg/table/index.html",
    "content": "{% extends \"base.html\" %}\n\n{% block content %}\n  <table class=\"endless_page_template span12\">\n    {% include page_template %}\n  </table>\n{% endblock %}\n\n{% block js %}\n  {{ block.super }}\n  <script>\n    $.endlessPaginate();\n  </script>\n{% endblock %}\n"
  },
  {
    "path": "tests/project/templates/digg/table/page.html",
    "content": "{% load el_pagination_tags %}\n\n{% paginate 5 objects %}\n{% for object in objects %}\n  <tr class=\"well object\">\n    <td>\n      <h4>{{ object.title }}</h4>\n      {{ object.contents }}\n    </td>\n  </tr>\n{% endfor %}\n<tr class=\"pagination pagination-centered\">\n  <td>{% show_pages %}</td>\n</td>\n"
  },
  {
    "path": "tests/project/templates/feed_wrapper/index.html",
    "content": "{% extends \"base.html\" %}\n{% load el_pagination_tags %}\n\n{% block content %}\n<table class=\"table\">\n  <thead>\n    <tr>\n      <th>Object</th>\n    </tr>\n  </thead>\n  <tbody class=\"pagination-content\">\n    {% lazy_paginate 10 objects %}\n    {% include page_template %}\n  </tbody>\n</table>\n\n{% show_more \"More results\" %}\n{% endblock %}\n\n{% block js %}\n  {{ block.super }}\n  <script>\n  $.endlessPaginate({contentSelector: '.pagination-content'});\n  </script>\n{% endblock %}\n\n"
  },
  {
    "path": "tests/project/templates/feed_wrapper/page.html",
    "content": "{% load el_pagination_tags %}\n{% if request.is_ajax %}\n  {% lazy_paginate 10 objects %}\n{% endif %}\n{% for object in objects %}\n<tr>\n  <td>{{ object.title }}</td>\n</tr>\n{% endfor %}\n"
  },
  {
    "path": "tests/project/templates/home.html",
    "content": "{% extends \"base.html\" %}\n\n{% block content %}\n  <div class=\"span12\">\n    <p class=\"lead\">\n      This project is intended to be used as a testing environment for\n      Django EL(Endless) Pagination.\n    </p>\n    <p class=\"lead\">\n      This project also contains a basic collection\n      of examples on how to use this application, providing both the ability\n      to manually test Django EL(Endless) Pagination in the browser, and a demo\n      showing some of the application features in action.\n    </p>\n    <p class=\"lead\">\n      The documentation is\n      <a href=\"http://django-el-pagination.readthedocs.org/\">avaliable online</a>\n      or in the docs directory of the project.<br />\n      The source code for this app is hosted at\n      <a href=\"https://github.com/shtalinberg/django-el-pagination\">Github</a> and\n    </p>\n    <p>\n      The template for this project is realized using the\n      <a href=\"http://twitter.github.com/bootstrap/index.html\"\n        target=\"_blank\">Bootstrap framework</a>.\n      However, Bootstrap is not required in order to use\n      Django EL(Endless) Pagination.\n    </p>\n  </div>\n{% endblock %}\n"
  },
  {
    "path": "tests/project/templates/multiple/entries_page.html",
    "content": "{% load el_pagination_tags %}\n\n{% lazy_paginate 1,3 entries using \"entries-page\" %}\n{% for entry in entries %}\n  <div class=\"well entry\">\n    <h4>{{ entry.title }}</h4>\n    {{ entry.contents }}\n  </div>\n{% endfor %}\n{% show_more \"More results\" %}\n"
  },
  {
    "path": "tests/project/templates/multiple/index.html",
    "content": "{% extends \"base.html\" %}\n\n{% block content %}\n  <div class=\"endless_page_template span6\">\n    {% include \"multiple/objects_page.html\" %}\n  </div>\n  <div class=\"endless_page_template span6\">\n     {% include \"multiple/items_page.html\" %}\n  </div>\n  <div class=\"span12\">\n    {% include \"multiple/entries_page.html\" %}\n  </div>\n{% endblock %}\n\n{% block js %}\n  {{ block.super }}\n  <script>\n    $.endlessPaginate();\n  </script>\n{% endblock %}\n"
  },
  {
    "path": "tests/project/templates/multiple/items_page.html",
    "content": "{% load el_pagination_tags %}\n\n{% paginate 3 items using \"items-page\" %}\n{% for item in items %}\n  <div class=\"well item\">\n    <h4>{{ item.title }}</h4>\n    {{ item.contents }}\n  </div>\n{% endfor %}\n<div class=\"pagination pagination-centered\">\n  <div>{% show_pages %}</div>\n</div>\n"
  },
  {
    "path": "tests/project/templates/multiple/objects_page.html",
    "content": "{% load el_pagination_tags %}\n\n{% paginate 3 objects using \"objects-page\" %}\n{% for object in objects %}\n  <div class=\"well object\">\n    <h4>{{ object.title }}</h4>\n    {{ object.contents }}\n  </div>\n{% endfor %}\n<div class=\"pagination pagination-centered\">\n  {% get_pages as all_pages %}\n  <div>{{ all_pages.get_rendered }}</div>\n</div>\n"
  },
  {
    "path": "tests/project/templates/onscroll/index.html",
    "content": "{% extends \"base.html\" %}\n\n{% block content %}\n  <div class=\"span12\">\n    {% include page_template %}\n  </div>\n{% endblock %}\n\n{% block js %}\n  {{ block.super }}\n  <script>\n    $.endlessPaginate({paginateOnScroll: true});\n  </script>\n{% endblock %}\n"
  },
  {
    "path": "tests/project/templates/onscroll/page.html",
    "content": "{% load el_pagination_tags %}\n\n{% paginate 10 objects %}\n{% for object in objects %}\n  <div class=\"well object\">\n    <h4>{{ object.title }}</h4>\n    {{ object.contents }}\n  </div>\n{% endfor %}\n{% show_more \"More results\" %}\n"
  },
  {
    "path": "tests/project/templates/onscroll/table/index.html",
    "content": "{% extends \"base.html\" %}\n\n{% block content %}\n  <table class=\"span12\">\n    {% include page_template %}\n  </table>\n{% endblock %}\n\n{% block js %}\n  {{ block.super }}\n  <script>\n    $.endlessPaginate({paginateOnScroll: true});\n  </script>\n{% endblock %}\n"
  },
  {
    "path": "tests/project/templates/onscroll/table/page.html",
    "content": "{% load el_pagination_tags %}\n\n{% paginate 10 objects %}\n{% for object in objects %}\n  <tr class=\"well object\">\n    <td>\n      <h4>{{ object.title }}</h4>\n      {{ object.contents }}\n    </td>\n  </tr>\n{% endfor %}\n\n{% show_more_table \"More results\" %}\n"
  },
  {
    "path": "tests/project/templates/twitter/index.html",
    "content": "{% extends \"base.html\" %}\n\n{% block content %}\n  <div class=\"span12\">\n    {% include page_template %}\n  </div>\n{% endblock %}\n\n{% block js %}\n  {{ block.super }}\n  <script>\n    $.endlessPaginate();\n  </script>\n{% endblock %}\n"
  },
  {
    "path": "tests/project/templates/twitter/page.html",
    "content": "{% load el_pagination_tags %}\n\n{% paginate 5 objects %}\n{% for object in objects %}\n  <div class=\"well object\">\n    <h4>{{ object.title }}</h4>\n    {{ object.contents }}\n  </div>\n{% endfor %}\n{% show_more \"More results\" %}\n"
  },
  {
    "path": "tests/project/templates/twitter/table/index.html",
    "content": "{% extends \"base.html\" %}\n\n{% block content %}\n  <table class=\"span12\">\n    {% include page_template %}\n  </table>\n{% endblock %}\n\n{% block js %}\n  {{ block.super }}\n  <script>\n    $.endlessPaginate();\n  </script>\n{% endblock %}\n"
  },
  {
    "path": "tests/project/templates/twitter/table/page.html",
    "content": "{% load el_pagination_tags %}\n\n{% paginate 5 objects %}\n{% for object in objects %}\n  <tr class=\"well object\">\n    <td>\n    <h4>{{ object.title }}</h4>\n    {{ object.contents }}\n    </td>\n  </tr>\n{% endfor %}\n{% show_more_table \"More results\" %}\n"
  },
  {
    "path": "tests/project/urls.py",
    "content": "\"\"\"Test project URL patterns.\"\"\"\n\nfrom django.conf import settings\nfrom django.contrib.staticfiles.urls import staticfiles_urlpatterns\nfrom django.urls import include, re_path as url\nfrom django.views.generic import TemplateView\n\nfrom el_pagination.decorators import page_template, page_templates\nfrom project.views import generic\n\n# Avoid lint errors for the following Django idiom: flake8: noqa.\nurlpatterns = [\n    url(r'^$', TemplateView.as_view(template_name=\"home.html\"), name='home'),\n    url(\n        r'^complete/$',\n        page_templates(\n            {\n                'complete/objects_page.html': 'objects-page',\n                'complete/items_page.html': 'items-page',\n                'complete/entries_page.html': 'entries-page',\n                'complete/articles_page.html': 'articles-page',\n            }\n        )(generic),\n        {'template': 'complete/index.html', 'number': 21},\n        name='complete',\n    ),\n    url(\n        r'^digg/$',\n        page_template('digg/page.html')(generic),\n        {'template': 'digg/index.html'},\n        name='digg',\n    ),\n    url(\n        r'^digg/table$',\n        page_template('digg/table/page.html')(generic),\n        {'template': 'digg/table/index.html'},\n        name='digg-table',\n    ),\n    url(\n        r'^twitter/$',\n        page_template('twitter/page.html')(generic),\n        {'template': 'twitter/index.html'},\n        name='twitter',\n    ),\n    url(\n        r'^twitter/table$',\n        page_template('twitter/table/page.html')(generic),\n        {'template': 'twitter/table/index.html'},\n        name='twitter-table',\n    ),\n    url(\n        r'^onscroll/$',\n        page_template('onscroll/page.html')(generic),\n        {'template': 'onscroll/index.html'},\n        name='onscroll',\n    ),\n    url(\n        r'^onscroll/table$',\n        page_template('onscroll/table/page.html')(generic),\n        {'template': 'onscroll/table/index.html'},\n        name='onscroll-table',\n    ),\n    url(\n        r'^feed-wrapper/$',\n        page_template('feed_wrapper/page.html')(generic),\n        {'template': 'feed_wrapper/index.html'},\n        name='feed-wrapper',\n    ),\n    url(\n        r'^chunks/$',\n        page_templates(\n            {\n                'chunks/objects_page.html': None,\n                'chunks/items_page.html': 'items-page',\n            }\n        )(generic),\n        {'template': 'chunks/index.html', 'number': 50},\n        name='chunks',\n    ),\n    url(\n        r'^multiple/$',\n        page_templates(\n            {\n                'multiple/objects_page.html': 'objects-page',\n                'multiple/items_page.html': 'items-page',\n                'multiple/entries_page.html': 'entries-page',\n            }\n        )(generic),\n        {'template': 'multiple/index.html', 'number': 21},\n        name='multiple',\n    ),\n    url(\n        r'^callbacks/$',\n        page_template('callbacks/page.html')(generic),\n        {'template': 'callbacks/index.html'},\n        name='callbacks',\n    ),\n]\n\nif settings.DEBUG:\n    if 'debug_toolbar' in settings.INSTALLED_APPS:\n        import debug_toolbar\n\n        urlpatterns += [\n            url(r'^__debug__/', include(debug_toolbar.urls)),\n        ]\n\nurlpatterns += staticfiles_urlpatterns()\n"
  },
  {
    "path": "tests/project/views.py",
    "content": "\"\"\"Test project views.\"\"\"\n\n\n\nfrom django.shortcuts import render\nfrom django.views.generic import ListView\n\nLOREM = \"\"\"Lorem ipsum dolor sit amet, consectetur adipisicing elit,\n    sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n    Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris\n    nisi ut aliquip ex ea commodo consequat.\n\"\"\"\n\n\ndef _make(title, number):\n    \"\"\"Make a *number* of items.\"\"\"\n    return [\n        {'title': '{0} {1}'.format(title, i + 1), 'contents': LOREM}\n        for i in range(number)\n    ]\n\n\ndef generic(request, extra_context=None, template=None, number=50):\n    context = {\n        'objects': _make('Object', number),\n        'items': _make('Item', number),\n        'entries': _make('Entry', number),\n        'articles': _make('Article', number),\n    }\n    if extra_context is not None:\n        context.update(extra_context)\n    return render(request, template, context)\n\n\nclass SearchListView(ListView):\n    pass\n"
  },
  {
    "path": "tests/requirements.pip",
    "content": "# Django Endless Pagination test requirements.\n# Dependencies are installed by the ``make`` command.\ndjango>=3.2.0\ncodecov\ncoverage==7.2.2\nblack==24.10.0\nisort==5.13.2\nflake8==7.1.1\npylint==3.3.1\npylint-django==2.6.1\nipdb\nselenium<4.0\n#xvfbwrapper==0.2.9\ndjango-test-without-migrations==0.6\n\n# Docs\nsphinx>=5.0.0,<6.0.0\nsphinx-rtd-theme>=1.0.0\njinja2>=3.0.0\ndocutils>=0.17.1\nsphinxcontrib-applehelp>=1.0.4"
  },
  {
    "path": "tests/with_venv.sh",
    "content": "#!/bin/bash\n\nexport DJANGO_LIVE_TEST_SERVER_ADDRESS=\"localhost:8000-8010,8080,9200-9300\"\nexport DJANGO_TEST_PROCESSES=\"1\"\nTESTS=`dirname $0`\nVENV=$TESTS/../$1\nshift\n\nif [ ! -f \"$VENV/bin/activate\" ]; then\n    echo \"Virtual environment not found at $VENV\"\n    echo \"Please run 'make develop' first\"\n    exit 1\nfi\n\nsource \"$VENV/bin/activate\"\nexec \"$@\""
  },
  {
    "path": "tox.ini",
    "content": "[tox]\nenvlist = py3{8,9,10,11,12}-django42\n        py3{12}-django50\n        py3{10,11,12,13}-django51\n        py3{10,11,12,13}-django52\n        py3{12,13}-djdev\n        black\n        isort\n        flake8\n        docs\n\n# Default testenv\n[testenv]\npassenv =\n    CI\n    USE_SELENIUM\n\ndeps =\n    -r{toxinidir}/tests/requirements.pip\n    django-42: Django>=4.2,<4.3\n    django-50: Django>=5.0,<5.1\n    django-51: Django>=5.1,<5.2\n    django-52: Django>=5.2,<5.3\n    djdev: https://github.com/django/django/archive/master.tar.gz\ncommands =\n  {envpython} --version\n  {envpython} -Wd {envbindir}/coverage run --branch {toxinidir}/tests/manage.py test\n  coverage report -m\n\nsetenv =\n    DJANGO_SETTINGS_MODULE=project.settings\n    PYTHONPATH={toxinidir}\n    DJANGO_LIVE_TEST_SERVER_ADDRESS=localhost:8000-8010,8080,9200-9300\n    DJANGO_TEST_PROCESSES=1\n\nbasepython =\n    py313: python3.13\n    py312: python3.12\n    py311: python3.11\n    py310: python3.10\n    py39: python3.9\n    py38: python3.8\n\n[testenv:black]\nbasepython = python3\nusedevelop = false\ndeps = black\nchangedir = {toxinidir}\ncommands = black --check --diff .\n\n[testenv:flake8]\nbasepython = python3\nusedevelop = false\ndeps = flake8 >= 3.7.0\nchangedir = {toxinidir}\ncommands = flake8 .\n\n[testenv:isort]\nbasepython = python3\nusedevelop = false\ndeps = isort >= 5.1.0\nchangedir = {toxinidir}\ncommands = isort --check-only --diff django tests scripts\n\n###########################\n# Run docs builder\n###########################\n\n[testenv:docs]\ndeps =\n    sphinx\n    sphinx_rtd_theme\nchangedir=doc\ncommands =\n    sphinx-build -W -b html -d {envtmpdir}/doctrees doc doc/_build/html\n\n\n###########################\n# Run docs linkcheck\n###########################\n\n[testenv:docs-linkcheck]\ndeps = {[testenv:docs]deps}\ncommands =\n    sphinx-build -b html -d {envtmpdir}/doctrees doc doc/_build/html\n    sphinx-build -b linkcheck doc doc/_build/html\n\n\n[pep8]\nexclude = migrations,.tox,doc,docs,tests,setup.py\n\n[gh-actions]\npython =\n  3.8: py38\n  3.9: py39\n  3.10: py310\n  3.11: py311\n  3.12: py312\n  3.13: py313\n"
  }
]