├── example
├── conf
│ ├── __init__.py
│ ├── wsgi.py
│ ├── urls.py
│ └── settings.py
├── app
│ └── example
│ │ ├── migrations
│ │ ├── __init__.py
│ │ └── 0001_initial.py
│ │ ├── models.py
│ │ └── admin.py
├── requirements.txt
├── django-admin-inline-paginator-plus.png
├── manage.py
├── makefile
└── fixtures
│ └── bkp.json
├── django_admin_inline_paginator_plus
├── __init__.py
├── templatetags
│ ├── __init__.py
│ └── paginated_inline.py
├── apps.py
├── templates
│ └── admin
│ │ ├── stacked_paginated.html
│ │ ├── tabular_paginated.html
│ │ ├── paginated_base.html
│ │ ├── stacked_paginator.html
│ │ └── tabular_paginator.html
├── static
│ └── django_admin_inline_paginator_plus
│ │ └── paginator.css
└── admin.py
├── MANIFEST.in
├── .editorconfig
├── .github
└── .github
│ └── PULL_REQUEST_TEMPLATE.md
├── pytest.ini
├── tests
├── __init__.py
├── apps_test.py
└── admin_unit_test.py
├── .pre-commit-config.yaml
├── LICENSE
├── tox.ini
├── CHANGELOG.md
├── pyproject.toml
├── README.md
└── .gitignore
/example/conf/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/example/app/example/migrations/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/django_admin_inline_paginator_plus/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/django_admin_inline_paginator_plus/templatetags/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/example/requirements.txt:
--------------------------------------------------------------------------------
1 | django>=3.2
2 | django-extensions # just for `shell_plus` command
3 | -e .. # install 'django_admin_inline_paginator_plus'
4 |
--------------------------------------------------------------------------------
/example/django-admin-inline-paginator-plus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DmytroLitvinov/django-admin-inline-paginator-plus/HEAD/example/django-admin-inline-paginator-plus.png
--------------------------------------------------------------------------------
/django_admin_inline_paginator_plus/apps.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from django.apps import AppConfig
3 | from django.utils.translation import gettext_lazy as _
4 |
5 |
6 | class DjangoAdminInlinePaginatorPlusConfig(AppConfig):
7 | name = 'django_admin_inline_paginator_plus'
8 | verbose_name = _('Django Admin Inline Paginator Plus')
9 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include LICENSE
2 | include README.md
3 |
4 | recursive-include tests *
5 | recursive-exclude * __pycache__
6 | recursive-exclude * *.py[co]
7 | recursive-include django_admin_inline_paginator_plus/templates *
8 | recursive-include django_admin_inline_paginator_plus/static *
9 | recursive-include django_admin_inline_paginator_plus/locale *
10 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # https://editorconfig.org/
2 |
3 | root = true
4 |
5 | [*]
6 | indent_style = space
7 | indent_size = 4
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 | end_of_line = lf
11 | charset = utf-8
12 |
13 | [*.html]
14 | indent_size = 2
15 |
16 | [Makefile]
17 | indent_style = tab
18 |
19 | [*.bat]
20 | indent_style = tab
21 |
--------------------------------------------------------------------------------
/.github/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | # Description
2 |
3 | Please include a summary of the change and which issue is fixed. Please also
4 | include relevant motivation and context. Your commit message should include
5 | this information as well.
6 |
7 | Fixes # (issue)
8 |
9 | # Checklist:
10 |
11 | - [ ] I have added the relevant tests for this change.
12 | - [ ] I have added an item to the Pending section of ``CHANGELOG.md``.
13 |
--------------------------------------------------------------------------------
/django_admin_inline_paginator_plus/templates/admin/stacked_paginated.html:
--------------------------------------------------------------------------------
1 | {% extends 'admin/paginated_base.html' %}
2 |
3 | {% block content %}
4 |
5 | {% include 'admin/edit_inline/stacked.html' %}
6 |
7 |
10 |
11 | {% endblock content %}
12 |
--------------------------------------------------------------------------------
/django_admin_inline_paginator_plus/templates/admin/tabular_paginated.html:
--------------------------------------------------------------------------------
1 | {% extends 'admin/paginated_base.html' %}
2 |
3 | {% block content %}
4 |
8 | {% include 'admin/edit_inline/tabular.html' %}
9 |
10 |
13 |
14 | {% endblock content %}
15 |
--------------------------------------------------------------------------------
/example/conf/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for example project.
3 |
4 | It exposes the WSGI callable as a module-level variable named ``application``.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/2.2/howto/deployment/wsgi/
8 | """
9 |
10 | import os
11 |
12 | from django.core.wsgi import get_wsgi_application
13 |
14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'conf.settings')
15 |
16 | application = get_wsgi_application()
17 |
--------------------------------------------------------------------------------
/django_admin_inline_paginator_plus/static/django_admin_inline_paginator_plus/paginator.css:
--------------------------------------------------------------------------------
1 | .btn-page {
2 | border: none;
3 | padding: 5px 10px;
4 | text-align: center;
5 | text-decoration: none;
6 | display: inline-block;
7 | font-size: 12px;
8 | margin: 4px 2px;
9 | cursor: pointer;
10 | }
11 |
12 | .page-selected {
13 | background-color: #ffffff;
14 | color: #666;
15 | }
16 |
17 | .page-available {
18 | background-color: #008cba;
19 | color: white !important;
20 | }
21 |
22 | .results {
23 | background-color: #ffffff;
24 | color: #666;
25 | }
26 |
--------------------------------------------------------------------------------
/pytest.ini:
--------------------------------------------------------------------------------
1 | [pytest]
2 | xfail_strict=true
3 | testpaths=tests/
4 |
5 | [pytest-watch]
6 | runner= pytest --failed-first --maxfail=1
7 |
8 | [MASTER]
9 | ignore = migrations,tests,manage.py
10 | load-plugins =
11 | pylint_common,
12 | pylint_django
13 |
14 | [MESSAGES CONTROL]
15 | disable=
16 | django-not-configured,
17 | missing-function-docstring,
18 | missing-class-docstring,
19 | missing-module-docstring,
20 | locally-disabled,
21 | too-few-public-methods
22 |
23 | [FORMAT]
24 | max-module-lines=1000
25 | max-line-length=120
26 |
27 | [DESIGN]
28 | max-args=10
29 |
--------------------------------------------------------------------------------
/example/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | """Django's command-line utility for administrative tasks."""
3 |
4 | import os
5 | import sys
6 |
7 |
8 | def main():
9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'conf.settings')
10 | try:
11 | from django.core.management import execute_from_command_line
12 | except ImportError as exc:
13 | raise ImportError(
14 | "Couldn't import Django. Are you sure it's installed and "
15 | 'available on your PYTHONPATH environment variable? Did you '
16 | 'forget to activate a virtual environment?'
17 | ) from exc
18 | execute_from_command_line(sys.argv)
19 |
20 |
21 | if __name__ == '__main__':
22 | main()
23 |
--------------------------------------------------------------------------------
/django_admin_inline_paginator_plus/templatetags/paginated_inline.py:
--------------------------------------------------------------------------------
1 | import urllib.parse
2 |
3 | from django import template
4 | from django.utils.safestring import SafeString
5 |
6 | register = template.Library()
7 |
8 |
9 | @register.simple_tag
10 | def modify_pagination_path(full_path: str, key: str, value: str) -> str:
11 | get_params = full_path
12 | if get_params.find('?') > -1:
13 | get_params = get_params[get_params.find('?') + 1 :]
14 | if get_params.find('#') > -1:
15 | get_params = get_params[: get_params.find('#')]
16 |
17 | params = urllib.parse.parse_qs(get_params)
18 | params[key] = [str(value)]
19 | return urllib.parse.urlencode(params, doseq=True)
20 |
21 |
22 | @register.simple_tag
23 | def hx_vals(key: str, value):
24 | return SafeString(f'hx-vals=\'{{"{key}": {value}}}\'')
25 |
--------------------------------------------------------------------------------
/example/conf/urls.py:
--------------------------------------------------------------------------------
1 | """example URL Configuration
2 |
3 | The `urlpatterns` list routes URLs to views. For more information please see:
4 | https://docs.djangoproject.com/en/2.2/topics/http/urls/
5 | Examples:
6 | Function views
7 | 1. Add an import: from my_app import views
8 | 2. Add a URL to urlpatterns: path('', views.home, name='home')
9 | Class-based views
10 | 1. Add an import: from other_app.views import Home
11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
12 | Including another URLconf
13 | 1. Import the include() function: from django.urls import include, path
14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
15 | """
16 |
17 | from django.contrib import admin
18 | from django.urls import path
19 |
20 | urlpatterns = [
21 | path('admin/', admin.site.urls),
22 | ]
23 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | import django
4 | from django.conf import settings
5 |
6 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
7 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
8 |
9 | settings.configure()
10 |
11 | settings.INSTALLED_APPS = [
12 | # Django apps
13 | 'django.contrib.admin',
14 | 'django.contrib.contenttypes',
15 | 'django.contrib.staticfiles',
16 | # Third part apps
17 | 'django_admin_inline_paginator_plus',
18 | 'tests',
19 | ]
20 |
21 | settings.TEMPLATES = [
22 | {
23 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
24 | 'DIRS': [
25 | os.path.join(BASE_DIR, 'templates'),
26 | ],
27 | 'APP_DIRS': True,
28 | },
29 | ]
30 |
31 | settings.STATIC_URL = '/'
32 | django.setup()
33 |
--------------------------------------------------------------------------------
/tests/apps_test.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from django.apps import AppConfig
4 | from django.utils.translation import gettext_lazy as _
5 |
6 | from django_admin_inline_paginator_plus.apps import DjangoAdminInlinePaginatorPlusConfig
7 |
8 |
9 | class TestDjangoAppConfig(unittest.TestCase):
10 | def test_valid_subclass_appconfig(self):
11 | self.assertEqual(issubclass(DjangoAdminInlinePaginatorPlusConfig, AppConfig), True)
12 |
13 | def test_valid_name(self):
14 | name = DjangoAdminInlinePaginatorPlusConfig.name
15 | self.assertEqual(isinstance(name, str), True)
16 | self.assertEqual(name, 'django_admin_inline_paginator_plus')
17 |
18 | def test_valid_verbose_name(self):
19 | verbose_name = DjangoAdminInlinePaginatorPlusConfig.verbose_name
20 | self.assertEqual(verbose_name, _('Django Admin Inline Paginator Plus'))
21 |
--------------------------------------------------------------------------------
/django_admin_inline_paginator_plus/templates/admin/paginated_base.html:
--------------------------------------------------------------------------------
1 | {% if inline_admin_formset.formset.htmx_enabled %}
2 |
3 | {% endif %}
4 |
5 |
18 |
19 | {% block content %}
20 | {% endblock content %}
21 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | repos:
2 | - repo: https://github.com/pre-commit/pre-commit-hooks
3 | rev: v4.6.0
4 | hooks:
5 | - id: check-toml
6 | - id: check-yaml
7 | - id: end-of-file-fixer
8 | - id: trailing-whitespace
9 | - id: mixed-line-ending
10 | - repo: https://github.com/adamchainz/django-upgrade
11 | rev: 1.19.0
12 | hooks:
13 | - id: django-upgrade
14 | args: [--target-version, "4.2"]
15 | - repo: https://github.com/astral-sh/ruff-pre-commit
16 | rev: 'v0.5.5'
17 | hooks:
18 | - id: ruff
19 | args: [--fix, --exit-non-zero-on-fix]
20 | - id: ruff-format
21 | #- repo: https://github.com/tox-dev/pyproject-fmt
22 | # rev: 2.1.4
23 | # hooks:
24 | # - id: pyproject-fmt
25 | #- repo: https://github.com/abravalheri/validate-pyproject
26 | # rev: v0.18
27 | # hooks:
28 | # - id: validate-pyproject
29 |
--------------------------------------------------------------------------------
/example/makefile:
--------------------------------------------------------------------------------
1 | MAKEFLAGS += --silent # No print command executed
2 | ARGS = $(filter-out $@,$(MAKECMDGOALS))
3 |
4 | #####
5 | ## Utils
6 | ###
7 |
8 | check_virtualenv:
9 | # if is not a docker, activate virtualenv
10 | if [ ! -f /.dockerenv ] && [ -z "${VIRTUAL_ENV}" ] ; then \
11 | echo "\033[0;31mError: No Virtualenv activated.${NC}"; \
12 | exit 1; \
13 | fi
14 |
15 | #####
16 | ## Run
17 | ###
18 |
19 | before_run: check_virtualenv
20 | pip install -r requirements.txt
21 | python manage.py migrate
22 |
23 | run: before_run
24 | python manage.py runserver 0:8000
25 |
26 | #####
27 | ## Shortcuts
28 | ###
29 |
30 | dj: check_virtualenv
31 | python manage.py "${ARGS}"
32 |
33 | dump_data: check_virtualenv
34 | python manage.py dumpdata --indent 4 --exclude admin.logentry --exclude auth.permission --exclude auth.group --exclude contenttypes.contenttype --exclude sessions.session> fixtures/bkp.json
35 |
36 | load_data: check_virtualenv
37 | python manage.py loaddata fixtures/bkp.json
38 |
--------------------------------------------------------------------------------
/example/app/example/models.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from django.db.models import CASCADE, BooleanField, CharField, ForeignKey, Model
3 |
4 |
5 | class Country(Model):
6 | name = CharField(max_length=100)
7 | active = BooleanField(default=True)
8 |
9 | def __str__(self):
10 | return self.name
11 |
12 | class Meta:
13 | verbose_name = 'Country'
14 | verbose_name_plural = 'Countries'
15 |
16 |
17 | class State(Model):
18 | country = ForeignKey('example.Country', on_delete=CASCADE)
19 | name = CharField(max_length=100)
20 | active = BooleanField(default=True)
21 |
22 | def __str__(self):
23 | return self.name
24 |
25 | class Meta:
26 | verbose_name = 'State'
27 | verbose_name_plural = 'States'
28 |
29 |
30 | class Region(Model):
31 | country = ForeignKey('example.Country', on_delete=CASCADE)
32 | name = CharField(max_length=100)
33 | active = BooleanField(default=True)
34 |
35 | def __str__(self):
36 | return self.name
37 |
38 | class Meta:
39 | verbose_name = 'Region'
40 | verbose_name_plural = 'Regions'
41 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Dmytro Litvinov
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | [tox]
2 | envlist = py39-django{42}
3 | py310-django{42,50,51,52}
4 | py311-django{42,50,51,52}
5 | py312-django{42,50,51,52}
6 |
7 | [gh-actions]
8 | python =
9 | 3.9: py39
10 | 3.10: py310
11 | 3.11: py311
12 | 3.12: py312
13 | 3.13: py313
14 |
15 | [testenv]
16 | commands =
17 | python \
18 | -W error::ResourceWarning \
19 | -W error::DeprecationWarning \
20 | -W error::PendingDeprecationWarning \
21 | -m pytest \
22 | --cov=django_admin_inline_paginator_plus \
23 | --cov-config=tox.ini \
24 | --cov-fail-under=35 \
25 | --cov-report=term-missing \
26 | --cov-report=xml:coverage.xml \
27 | --durations=10 \
28 | --cov-append
29 | extras = dev
30 | deps =
31 | pytest
32 | pytest-cov
33 | django42: Django>=4.2,<5.0
34 | django50: Django>=5.0a1,<5.1
35 | django51: Django>=5.1a1,<5.2
36 | django52: Django>=5.2a1,<6.0
37 |
38 | [coverage:run]
39 | relative_files = True
40 | source = django_admin_inline_paginator_plus/
41 | branch = True
42 |
43 | [testenv:report]
44 | deps = coverage
45 | skip_install = true
46 | commands =
47 | coverage report
48 | coverage html
49 |
--------------------------------------------------------------------------------
/example/app/example/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib.admin import ModelAdmin, register
2 | from django_admin_inline_paginator_plus.admin import (
3 | StackedInlinePaginated,
4 | TabularInlinePaginated,
5 | )
6 |
7 | from .models import Country, Region, State
8 |
9 |
10 | class StateAdminInline(TabularInlinePaginated):
11 | model = State
12 | fields = ('name', 'active')
13 | per_page = 5
14 | pagination_key = 'state_page'
15 |
16 |
17 | class CollapsedStateAdminInline(StateAdminInline):
18 | verbose_name = 'State Collapsed'
19 | verbose_name_plural = 'States Collapsed'
20 | pagination_key = 'state_collapsed_page'
21 | classes = ['collapse']
22 |
23 |
24 | class RegionAdminInline(StackedInlinePaginated):
25 | model = Region
26 | fields = ('name', 'active')
27 | per_page = 2
28 | pagination_key = 'region_page'
29 |
30 |
31 | @register(Country)
32 | class CountryAdmin(ModelAdmin):
33 | model = Country
34 | list_display = ('name', 'active')
35 | fields = ('name', 'active')
36 | inlines = (StateAdminInline, CollapsedStateAdminInline, RegionAdminInline)
37 | list_filter = ('active',)
38 |
39 |
40 | @register(State)
41 | class StateAdmin(ModelAdmin):
42 | pass
43 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 | All notable changes to this project will be documented in this file.
3 |
4 |
5 | ## [0.1.4] - 27-03-2025
6 |
7 | * ### Changed
8 | - Nothing new - just improve README and CHANGELOG's link and tested versions for PyPI package
9 |
10 | ## [0.1.3] - 22-02-2025
11 |
12 | * ### Fixed
13 | - Fix URL params in htmx, restore no JS support. More details in [pr #4](https://github.com/DmytroLitvinov/django-admin-inline-paginator-plus/pull/4)
14 | - Thanks to @pmdevita for these recent releases 👍
15 |
16 | ## [0.1.2] - 22-02-2025
17 |
18 | * ### Fixed
19 | - Fix deleting an item from a subsequent page of TabularInlinePaginated when GET params are presented. More details in [issue #3](https://github.com/DmytroLitvinov/django-admin-inline-paginator-plus/issues/3)
20 |
21 | ## [0.1.1] - 26-07-2024
22 |
23 | * ### Added
24 | - Add classifiers for Django versions
25 | * ### Changed
26 | - Change Development status from Beta to Production/Stable
27 |
28 | ## [0.1.0] - 10-07-2024
29 |
30 | New forked version `django-admin-inline-paginator-plus` 🎉
31 |
32 | * ### Added
33 | - Add StackedInlinePaginated
34 | - Add `htmx` for AJAX-paginated support
35 | * ### Changed
36 | * ### Fixed
37 | * ### Credits
38 | - Thanks to @jazzband for the original `django-admin-inline-paginator` project
39 |
--------------------------------------------------------------------------------
/tests/admin_unit_test.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from django.contrib.admin.views.main import ChangeList
4 |
5 | from django_admin_inline_paginator_plus.admin import (
6 | InlineChangeList,
7 | PaginationFormSetBase,
8 | )
9 |
10 |
11 | class TestInlineChangeList(unittest.TestCase):
12 | """Test cases for InlineChangeList admin"""
13 |
14 | def test_default_values(self):
15 | self.assertEqual(InlineChangeList.can_show_all, True)
16 | self.assertEqual(InlineChangeList.multi_page, True)
17 | self.assertEqual(InlineChangeList.get_query_string, ChangeList.__dict__['get_query_string'])
18 |
19 | def test_init_values(self):
20 | """Test case for correct initialization class"""
21 | pass
22 |
23 | # cl = InlineChangeList(request, page_num, paginator)
24 | # cl.page_num = page_num
25 | # cl.paginator = paginator
26 | # cl.result_count = paginator.count
27 |
28 | # cl.show_all = 'all' in request.GET
29 | # cl.params = dict(request.GET.items())
30 |
31 |
32 | class TestPaginationFormSetBase(unittest.TestCase):
33 | def test_default_values(self):
34 | """Test case to check if the default was a value set"""
35 | self.assertEqual(PaginationFormSetBase.queryset, None)
36 | self.assertEqual(PaginationFormSetBase.request, None)
37 | self.assertEqual(PaginationFormSetBase.per_page, 20)
38 |
39 | def test_get_page_num(self):
40 | """Test case to check correct getting page number"""
41 | pass
42 |
43 | def test_get_page(self):
44 | """Test case to check correct getting page"""
45 | pass
46 |
47 | def test_mount_paginator(self):
48 | """Test case for method mount_paginator"""
49 | pass
50 |
51 | def test_mount_queryset(self):
52 | """Test case for method mount_queryset"""
53 | pass
54 |
55 | def test_init(self):
56 | """Test case for correct initialization class"""
57 | pass
58 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = ["setuptools>=61.0"]
3 | build-backend = "setuptools.build_meta"
4 |
5 | [project]
6 | name = "django-admin-inline-paginator-plus"
7 | version = "0.1.4"
8 | requires-python = ">=3.8"
9 | description = "The 'Django Admin Inline Paginator Plus' is simple way to paginate your inlines in Django admin"
10 | readme = "README.md"
11 | keywords = ["django", "admin", "paginator", "inlines", "pagination"]
12 | authors = [
13 | {name = "Shinneider Libanio da Silva", email = "shinneider-libanio@hotmail.com"},
14 | {name = "Dmytro Litvinov", email = "me@dmytrolitvinov.com"}
15 | ]
16 | maintainers = [
17 | {name = "Dmytro Litvinov", email = "me@dmytrolitvinov.com"}
18 | ]
19 | classifiers = [
20 | "Development Status :: 5 - Production/Stable",
21 | "Framework :: Django :: 4.2",
22 | "Framework :: Django :: 5.0",
23 | "Framework :: Django :: 5.1",
24 | "Framework :: Django :: 5.2",
25 | "Environment :: Web Environment",
26 | "Framework :: Django",
27 | "Intended Audience :: Developers",
28 | "License :: OSI Approved :: MIT License",
29 | "Natural Language :: English",
30 | "Operating System :: OS Independent",
31 | "Programming Language :: Python",
32 | "Programming Language :: Python :: 3 :: Only",
33 | "Programming Language :: Python :: 3.9",
34 | "Programming Language :: Python :: 3.10",
35 | "Programming Language :: Python :: 3.11",
36 | "Programming Language :: Python :: 3.12",
37 | "Programming Language :: Python :: 3.13",
38 | "Topic :: Internet :: WWW/HTTP",
39 | "Topic :: Software Development :: Libraries :: Python Modules"
40 | ]
41 | license = {text = "MIT"}
42 | dependencies = [
43 | "django"
44 | ]
45 |
46 | [project.optional-dependencies]
47 | dev = [
48 | "coverage", # testing
49 | "mypy", # linting
50 | "pytest", # testing
51 | "ruff" # linting
52 | ]
53 |
54 | [project.urls]
55 | Homepage = "https://github.com/DmytroLitvinov/django-admin-inline-paginator-plus"
56 | Issues = "https://github.com/DmytroLitvinov/django-admin-inline-paginator-plus/issues"
57 | Changelog = "https://github.com/DmytroLitvinov/django-admin-inline-paginator-plus/blob/master/CHANGELOG.md"
58 |
59 | [tool.setuptools.packages.find]
60 | include = ["django_admin_inline_paginator_plus*"]
61 |
62 | # Ruff
63 | # ----
64 | [tool.ruff]
65 | src = ["django_admin_inline_paginator_plus"]
66 | line-length = 120
67 | indent-width = 4
68 |
69 | [tool.ruff.format]
70 | quote-style = "single"
71 | indent-style = "space"
72 | skip-magic-trailing-comma = false
73 | line-ending = "auto"
74 |
75 | # Mypy
76 | # ----
77 |
78 | [tool.mypy]
79 | files = "."
80 |
81 | # Use strict defaults
82 | strict = true
83 | warn_unreachable = true
84 | warn_no_return = true
85 |
86 | [[tool.mypy.overrides]]
87 | # Don't require test functions to include types
88 | module = "tests.*"
89 | allow_untyped_defs = true
90 | disable_error_code = "attr-defined"
91 |
--------------------------------------------------------------------------------
/example/app/example/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.24 on 2021-06-08 02:20
2 |
3 | from django.db import migrations, models
4 | import django.db.models.deletion
5 |
6 |
7 | class Migration(migrations.Migration):
8 | initial = True
9 |
10 | dependencies = []
11 |
12 | operations = [
13 | migrations.CreateModel(
14 | name='Country',
15 | fields=[
16 | (
17 | 'id',
18 | models.AutoField(
19 | auto_created=True,
20 | primary_key=True,
21 | serialize=False,
22 | verbose_name='ID',
23 | ),
24 | ),
25 | ('name', models.CharField(max_length=100)),
26 | ('active', models.BooleanField(default=True)),
27 | ],
28 | options={
29 | 'verbose_name': 'Country',
30 | 'verbose_name_plural': 'Countries',
31 | },
32 | ),
33 | migrations.CreateModel(
34 | name='State',
35 | fields=[
36 | (
37 | 'id',
38 | models.AutoField(
39 | auto_created=True,
40 | primary_key=True,
41 | serialize=False,
42 | verbose_name='ID',
43 | ),
44 | ),
45 | ('name', models.CharField(max_length=100)),
46 | ('active', models.BooleanField(default=True)),
47 | (
48 | 'country',
49 | models.ForeignKey(
50 | on_delete=django.db.models.deletion.CASCADE,
51 | to='example.Country',
52 | ),
53 | ),
54 | ],
55 | options={
56 | 'verbose_name': 'State',
57 | 'verbose_name_plural': 'States',
58 | },
59 | ),
60 | migrations.CreateModel(
61 | name='Region',
62 | fields=[
63 | (
64 | 'id',
65 | models.AutoField(
66 | auto_created=True,
67 | primary_key=True,
68 | serialize=False,
69 | verbose_name='ID',
70 | ),
71 | ),
72 | ('name', models.CharField(max_length=100)),
73 | ('active', models.BooleanField(default=True)),
74 | (
75 | 'country',
76 | models.ForeignKey(
77 | on_delete=django.db.models.deletion.CASCADE,
78 | to='example.Country',
79 | ),
80 | ),
81 | ],
82 | options={
83 | 'verbose_name': 'Region',
84 | 'verbose_name_plural': 'Regions',
85 | },
86 | ),
87 | ]
88 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Django Admin Inline Paginator Plus ⏩
2 | =====================================
3 |
4 | **🍴 This is a forked and updated version based on original library [django-admin-inline-paginator](https://github.com/shinneider/django-admin-inline-paginator).**
5 |
6 | > *As for 10.07.2024 nobody took responsibility, so I decided to take it since we need additional functionlity like AJAX for pagination.*
7 |
8 | The **"Django Admin Inline Paginator Plus"** is simple way to paginate your inline in django admin
9 |
10 | To keep Django ecosystem fresh and updated, please share your love and support, click `Star` 🫶
11 |
12 | ## Features
13 | - **Easy Inline Pagination:** Quickly paginate inlines in the Django admin.
14 | - **AJAX Support:** Smooth and dynamic pagination without page reloads with `htmx`.
15 | - **Multiple Inline Pagination:** Manage multiple paginated inlines seamlessly.
16 |
17 |
18 | Here's a screenshot of the paginated inlines in action:
19 |
20 | 
21 |
22 |
23 | # Install:
24 |
25 | Install the package via pip:
26 |
27 | ```bash
28 | pip install django-admin-inline-paginator-plus
29 | ```
30 |
31 | # Usage:
32 |
33 | 1. Add to your INSTALLED_APPS, in settings.py:
34 |
35 | ```python
36 | INSTALLED_APPS = [
37 | ...
38 | 'django_admin_inline_paginator_plus',
39 | ...
40 | ]
41 | ```
42 | 2. Create your model inline:
43 |
44 | You can use `TabularInlinePaginated` ot `StackedInlinePaginated`. In our example we use `TabularInlinePaginated`.
45 |
46 | ```python
47 | from django_admin_inline_paginator_plus.admin import TabularInlinePaginated
48 |
49 | class ModelWithFKAdminInline(TabularInlinePaginated):
50 | model = ModelWithFK
51 | fields = (...)
52 | per_page = 5
53 | ```
54 |
55 | 3. Create main model admin and use inline:
56 |
57 | ```python
58 | @register(YourModel)
59 | class YourModelAdmin(ModelAdmin):
60 | model = YourModel
61 | fields = (...)
62 | inlines = (ModelWithFKAdminInline, )
63 | ```
64 |
65 | # Advanced Usage:
66 |
67 | 1. Paginate multiples inlines:
68 |
69 | ```python
70 | from django_admin_inline_paginator_plus.admin import TabularInlinePaginated, StackedInlinePaginated
71 |
72 | class ModelWithFKInline(TabularInlinePaginated):
73 | model = ModelWithFK
74 | fields = ('name', 'active')
75 | per_page = 2
76 | pagination_key = 'page-model' # make sure it's unique for page inline
77 |
78 | class AnotherModelWithFKInline(StackedInlinePaginated):
79 | model = AnotherModelWithFK
80 | fields = ('name', 'active')
81 | per_page = 2
82 | pagination_key = 'page-another-model' # make sure it's unique for page inline
83 | ```
84 |
85 | 2. Use inlines from step 1. and add to your main model admin:
86 |
87 | ```python
88 | @register(YourModel)
89 | class YourModelAdmin(ModelAdmin):
90 | model = YourModel
91 | fields = (...)
92 | inlines = (ModelWithFKAdminInline, AnotherModelWithFKInline)
93 | ```
94 |
--------------------------------------------------------------------------------
/django_admin_inline_paginator_plus/admin.py:
--------------------------------------------------------------------------------
1 | from typing import Optional
2 |
3 | from django.contrib.admin import StackedInline, TabularInline
4 | from django.contrib.admin.views.main import ChangeList
5 | from django.contrib.contenttypes.admin import GenericTabularInline
6 | from django.core.paginator import Paginator
7 | from django.db.models import QuerySet
8 | from django.http import HttpRequest
9 |
10 |
11 | class InlineChangeList:
12 | """
13 | Used by template to construct the paginator
14 | """
15 |
16 | can_show_all = True
17 | multi_page = True
18 | get_query_string = ChangeList.__dict__['get_query_string']
19 |
20 | def __init__(self, request: HttpRequest, page_num: int, paginator: Paginator):
21 | self.show_all = 'all' in request.GET
22 | self.page_num = page_num
23 | self.paginator = paginator
24 | self.result_count = paginator.count
25 | self.params = dict(request.GET.items())
26 |
27 |
28 | class PaginationFormSetBase:
29 | queryset: Optional[QuerySet] = None
30 | request: Optional[HttpRequest] = None
31 | per_page = 20
32 | pagination_key = 'page'
33 | htmx_enabled = True
34 |
35 | def __init__(self, *args, **kwargs):
36 | super().__init__(*args, **kwargs)
37 | self.mount_paginator()
38 | self.mount_queryset()
39 |
40 | def get_page_num(self) -> int:
41 | assert self.request is not None
42 | page = self.request.GET.get(self.pagination_key)
43 | if page and page.isnumeric() and page > '0':
44 | return int(page)
45 | page = self.request.POST.get(f"_paginator-plus-{self.prefix}")
46 | if page and page.isnumeric() and page > '0':
47 | return int(page)
48 |
49 | return 1
50 |
51 | def get_page(self, paginator: Paginator, page: int):
52 | if page <= paginator.num_pages:
53 | return paginator.page(page)
54 |
55 | return paginator.page(1)
56 |
57 | def mount_paginator(self, page_num: int = None):
58 | assert self.queryset is not None and self.request is not None
59 |
60 | page_num = self.get_page_num() if not page_num else page_num
61 | self.paginator = Paginator(self.queryset, self.per_page)
62 | self.page = self.get_page(self.paginator, page_num)
63 | self.cl = InlineChangeList(self.request, page_num, self.paginator)
64 |
65 | def mount_queryset(self):
66 | if self.cl.show_all:
67 | self._queryset = self.queryset
68 |
69 | self._queryset = self.page.object_list
70 |
71 |
72 | class InlinePaginated:
73 | pagination_key = 'page'
74 | template = 'admin/tabular_paginated.html'
75 | per_page = 20
76 | extra = 0
77 | htmx_enabled = True
78 |
79 | def get_formset(self, request, obj=None, **kwargs):
80 | formset_class = super().get_formset(request, obj, **kwargs)
81 |
82 | class PaginationFormSet(PaginationFormSetBase, formset_class):
83 | pagination_key = self.pagination_key
84 |
85 | PaginationFormSet.request = request
86 | PaginationFormSet.per_page = self.per_page
87 | PaginationFormSet.htmx_enabled = self.htmx_enabled
88 | return PaginationFormSet
89 |
90 |
91 | class StackedInlinePaginated(InlinePaginated, StackedInline):
92 | template = 'admin/stacked_paginated.html'
93 |
94 |
95 | class TabularInlinePaginated(InlinePaginated, TabularInline):
96 | pass
97 |
98 |
99 | class GenericTabularInlinePaginated(InlinePaginated, GenericTabularInline):
100 | pass
101 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | share/python-wheels/
24 | *.egg-info/
25 | .installed.cfg
26 | *.egg
27 | MANIFEST
28 | # PyInstaller
29 | # Usually these files are written by a python script from a template
30 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
31 | *.manifest
32 | *.spec
33 |
34 | # Installer logs
35 | pip-log.txt
36 | pip-delete-this-directory.txt
37 |
38 | # Unit test / coverage reports
39 | htmlcov/
40 | .tox/
41 | .nox/
42 | .coverage
43 | .coverage.*
44 | .cache
45 | nosetests.xml
46 | coverage.xml
47 | *.cover
48 | *.py,cover
49 | .hypothesis/
50 | .pytest_cache/
51 | cover/
52 |
53 | # Translations
54 | *.mo
55 | *.pot
56 |
57 | # Django stuff:
58 | *.log
59 | local_settings.py
60 | db.sqlite3
61 | db.sqlite3-journal
62 |
63 | # Flask stuff:
64 | instance/
65 | .webassets-cache
66 |
67 | # Scrapy stuff:
68 | .scrapy
69 |
70 | # Sphinx documentation
71 | docs/_build/
72 |
73 | # PyBuilder
74 | .pybuilder/
75 | target/
76 |
77 | # Jupyter Notebook
78 | .ipynb_checkpoints
79 |
80 | # IPython
81 | profile_default/
82 | ipython_config.py
83 |
84 | # pyenv
85 | # For a library or package, you might want to ignore these files since the code is
86 | # intended to run in multiple environments; otherwise, check them in:
87 | # .python-version
88 |
89 | # pipenv
90 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
91 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
92 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
93 | # install all needed dependencies.
94 | #Pipfile.lock
95 |
96 | # poetry
97 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
98 | # This is especially recommended for binary packages to ensure reproducibility, and is more
99 | # commonly ignored for libraries.
100 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
101 | #poetry.lock
102 |
103 | # pdm
104 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
105 | #pdm.lock
106 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
107 | # in version control.
108 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control
109 | .pdm.toml
110 | .pdm-python
111 | .pdm-build/
112 |
113 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
114 | __pypackages__/
115 |
116 | # Celery stuff
117 | celerybeat-schedule
118 | celerybeat.pid
119 |
120 | # SageMath parsed files
121 | *.sage.py
122 |
123 | # Environments
124 | .env
125 | .venv
126 | env/
127 | venv/
128 | ENV/
129 | env.bak/
130 | venv.bak/
131 |
132 | # Spyder project settings
133 | .spyderproject
134 | .spyproject
135 |
136 | # Rope project settings
137 | .ropeproject
138 |
139 | # mkdocs documentation
140 | /site
141 |
142 | # mypy
143 | .mypy_cache/
144 | .dmypy.json
145 | dmypy.json
146 |
147 | # Pyre type checker
148 | .pyre/
149 |
150 | # pytype static type analyzer
151 | .pytype/
152 |
153 | # Cython debug symbols
154 | cython_debug/
155 |
156 | # PyCharm
157 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
158 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
159 | # and can be added to the global gitignore or merged into this file. For a more nuclear
160 | # option (not recommended) you can uncomment the following to ignore the entire idea folder.
161 | .idea/
162 |
163 | # Others
164 | .DS_STORE
165 |
--------------------------------------------------------------------------------
/example/conf/settings.py:
--------------------------------------------------------------------------------
1 | """
2 | Django settings for example project.
3 |
4 | Generated by 'django-admin startproject' using Django 2.2.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/2.2/topics/settings/
8 |
9 | For the full list of settings and their values, see
10 | https://docs.djangoproject.com/en/2.2/ref/settings/
11 | """
12 |
13 | import os
14 | from typing import List
15 |
16 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
17 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
18 |
19 |
20 | # Quick-start development settings - unsuitable for production
21 | # See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/
22 |
23 | # SECURITY WARNING: keep the secret key used in production secret!
24 | SECRET_KEY = 'wa@as#_q*^g$i1u4bvl*_=8v=s6=(_4)$&g3d73g&z%$$gj94*'
25 |
26 | # SECURITY WARNING: don't run with debug turned on in production!
27 | DEBUG = True
28 |
29 | ALLOWED_HOSTS: List[str] = []
30 |
31 |
32 | # Application definition
33 |
34 | INSTALLED_APPS = [
35 | # Django apps
36 | 'django.contrib.admin',
37 | 'django.contrib.auth',
38 | 'django.contrib.contenttypes',
39 | 'django.contrib.sessions',
40 | 'django.contrib.messages',
41 | 'django.contrib.staticfiles',
42 | # Third part apps
43 | 'django_admin_inline_paginator_plus', # our tested library
44 | 'django_extensions', # for `python manage.py shell_plus` command
45 | # Developed apps
46 | 'app.example',
47 | ]
48 |
49 | MIDDLEWARE = [
50 | 'django.middleware.security.SecurityMiddleware',
51 | 'django.contrib.sessions.middleware.SessionMiddleware',
52 | 'django.middleware.common.CommonMiddleware',
53 | 'django.middleware.csrf.CsrfViewMiddleware',
54 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
55 | 'django.contrib.messages.middleware.MessageMiddleware',
56 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
57 | ]
58 |
59 | ROOT_URLCONF = 'conf.urls'
60 |
61 | TEMPLATES = [
62 | {
63 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
64 | 'DIRS': [
65 | os.path.join(BASE_DIR, 'templates'),
66 | ],
67 | 'APP_DIRS': True,
68 | 'OPTIONS': {
69 | 'context_processors': [
70 | 'django.template.context_processors.debug',
71 | 'django.template.context_processors.request',
72 | 'django.contrib.auth.context_processors.auth',
73 | 'django.contrib.messages.context_processors.messages',
74 | ],
75 | },
76 | },
77 | ]
78 |
79 | WSGI_APPLICATION = 'conf.wsgi.application'
80 |
81 |
82 | # Database
83 | # https://docs.djangoproject.com/en/2.2/ref/settings/#databases
84 |
85 | DATABASES = {
86 | 'default': {
87 | 'ENGINE': 'django.db.backends.sqlite3',
88 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
89 | }
90 | }
91 |
92 |
93 | # Password validation
94 | # https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators
95 |
96 | AUTH_PASSWORD_VALIDATORS = [
97 | {
98 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
99 | },
100 | {
101 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
102 | },
103 | {
104 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
105 | },
106 | {
107 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
108 | },
109 | ]
110 |
111 |
112 | # Internationalization
113 | # https://docs.djangoproject.com/en/2.2/topics/i18n/
114 |
115 | LANGUAGE_CODE = 'en-us'
116 |
117 | TIME_ZONE = 'UTC'
118 |
119 | USE_I18N = True
120 |
121 |
122 | USE_TZ = True
123 |
124 |
125 | # Static files (CSS, JavaScript, Images)
126 | # https://docs.djangoproject.com/en/2.2/howto/static-files/
127 |
128 | STATIC_URL = '/static/'
129 |
--------------------------------------------------------------------------------
/django_admin_inline_paginator_plus/templates/admin/stacked_paginator.html:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 | {% load static %}
3 | {% load paginated_inline %}
4 |
5 |
6 |
7 |
13 | {% with inline_admin_formset.formset.page as page_obj %}
14 |
15 |
16 | {% if page_obj.has_previous %}
17 |
23 | {% endif %}
24 |
25 | {% if page_obj.number|add:"-5" > 0 %}
26 |
32 | {% endif %}
33 |
34 | {% if page_obj.number|add:"-5" > 1 %}
35 | …
36 | {% endif %}
37 |
38 | {% for page_num in page_obj.paginator.page_range %}
39 | {% if page_obj.number == page_num %}
40 | {{ page_num }}
41 | {% else %}
42 | {% if page_num > page_obj.number|add:"-5" and page_num < page_obj.number|add:"5" %}
43 |
49 | {% endif %}
50 | {% endif %}
51 | {% endfor %}
52 |
53 | {% if page_obj.number|add:"5" < page_obj.paginator.num_pages %}
54 | …
55 | {% endif %}
56 |
57 | {% if page_obj.number|add:"4" < page_obj.paginator.num_pages %}
58 |
64 | {% endif %}
65 |
66 | {% if page_obj.has_next %}
67 |
73 | {% endif %}
74 | {{ page_obj.paginator.count }} {% translate 'Results' %}
75 |
76 | {% endwith %}
77 |
78 |
--------------------------------------------------------------------------------
/django_admin_inline_paginator_plus/templates/admin/tabular_paginator.html:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 | {% load static %}
3 | {% load paginated_inline %}
4 |
5 |
6 |
7 |
8 |
14 | {% with inline_admin_formset.formset.page as page_obj %}
15 |
16 |
17 | {% if page_obj.has_previous %}
18 |
27 | {% endif %}
28 |
29 | {% if page_obj.number|add:"-5" > 0 %}
30 |
38 | {% endif %}
39 |
40 | {% if page_obj.number|add:"-5" > 1 %}
41 | …
42 | {% endif %}
43 |
44 | {% for page_num in page_obj.paginator.page_range %}
45 | {% if page_obj.number == page_num %}
46 | {{ page_num }}
47 | {% else %}
48 | {% if page_num > page_obj.number|add:"-5" and page_num < page_obj.number|add:"5" %}
49 |
57 | {% endif %}
58 | {% endif %}
59 | {% endfor %}
60 |
61 | {% if page_obj.number|add:"5" < page_obj.paginator.num_pages %}
62 | …
63 | {% endif %}
64 |
65 | {% if page_obj.number|add:"4" < page_obj.paginator.num_pages %}
66 |
74 | {% endif %}
75 |
76 | {% if page_obj.has_next %}
77 |
86 | {% endif %}
87 | {{ page_obj.paginator.count }} {% translate 'Results' %}
88 |
89 | {% endwith %}
90 |
91 |
--------------------------------------------------------------------------------
/example/fixtures/bkp.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "model": "example.country",
4 | "pk": 1,
5 | "fields": {
6 | "name": "Brasil",
7 | "active": true
8 | }
9 | },
10 | {
11 | "model": "example.state",
12 | "pk": 1,
13 | "fields": {
14 | "country": 1,
15 | "name": "Acre",
16 | "active": true
17 | }
18 | },
19 | {
20 | "model": "example.state",
21 | "pk": 2,
22 | "fields": {
23 | "country": 1,
24 | "name": "Alagoas",
25 | "active": true
26 | }
27 | },
28 | {
29 | "model": "example.state",
30 | "pk": 3,
31 | "fields": {
32 | "country": 1,
33 | "name": "Amap\u00e1",
34 | "active": true
35 | }
36 | },
37 | {
38 | "model": "example.state",
39 | "pk": 4,
40 | "fields": {
41 | "country": 1,
42 | "name": "Amazonas",
43 | "active": true
44 | }
45 | },
46 | {
47 | "model": "example.state",
48 | "pk": 5,
49 | "fields": {
50 | "country": 1,
51 | "name": "Bahia",
52 | "active": true
53 | }
54 | },
55 | {
56 | "model": "example.state",
57 | "pk": 6,
58 | "fields": {
59 | "country": 1,
60 | "name": "Cear\u00e1",
61 | "active": true
62 | }
63 | },
64 | {
65 | "model": "example.state",
66 | "pk": 7,
67 | "fields": {
68 | "country": 1,
69 | "name": "Distrito Federal",
70 | "active": true
71 | }
72 | },
73 | {
74 | "model": "example.state",
75 | "pk": 8,
76 | "fields": {
77 | "country": 1,
78 | "name": "Esp\u00edrito Santo",
79 | "active": true
80 | }
81 | },
82 | {
83 | "model": "example.state",
84 | "pk": 9,
85 | "fields": {
86 | "country": 1,
87 | "name": "Goi\u00e1s",
88 | "active": true
89 | }
90 | },
91 | {
92 | "model": "example.state",
93 | "pk": 10,
94 | "fields": {
95 | "country": 1,
96 | "name": "Maranh\u00e3o",
97 | "active": true
98 | }
99 | },
100 | {
101 | "model": "example.state",
102 | "pk": 11,
103 | "fields": {
104 | "country": 1,
105 | "name": "Mato Grosso",
106 | "active": true
107 | }
108 | },
109 | {
110 | "model": "example.state",
111 | "pk": 12,
112 | "fields": {
113 | "country": 1,
114 | "name": "Mato Grosso do Sul",
115 | "active": true
116 | }
117 | },
118 | {
119 | "model": "example.state",
120 | "pk": 13,
121 | "fields": {
122 | "country": 1,
123 | "name": "Minas Gerais",
124 | "active": true
125 | }
126 | },
127 | {
128 | "model": "example.state",
129 | "pk": 14,
130 | "fields": {
131 | "country": 1,
132 | "name": "Par\u00e1",
133 | "active": true
134 | }
135 | },
136 | {
137 | "model": "example.state",
138 | "pk": 15,
139 | "fields": {
140 | "country": 1,
141 | "name": "Para\u00edba",
142 | "active": true
143 | }
144 | },
145 | {
146 | "model": "example.state",
147 | "pk": 16,
148 | "fields": {
149 | "country": 1,
150 | "name": "Paran\u00e1",
151 | "active": true
152 | }
153 | },
154 | {
155 | "model": "example.state",
156 | "pk": 17,
157 | "fields": {
158 | "country": 1,
159 | "name": "Pernambuco",
160 | "active": true
161 | }
162 | },
163 | {
164 | "model": "example.state",
165 | "pk": 18,
166 | "fields": {
167 | "country": 1,
168 | "name": "Piau\u00ed",
169 | "active": true
170 | }
171 | },
172 | {
173 | "model": "example.state",
174 | "pk": 19,
175 | "fields": {
176 | "country": 1,
177 | "name": "Rio de Janeiro",
178 | "active": true
179 | }
180 | },
181 | {
182 | "model": "example.state",
183 | "pk": 20,
184 | "fields": {
185 | "country": 1,
186 | "name": "Rio Grande do Norte",
187 | "active": true
188 | }
189 | },
190 | {
191 | "model": "example.state",
192 | "pk": 21,
193 | "fields": {
194 | "country": 1,
195 | "name": "Rio Grande do Sul",
196 | "active": true
197 | }
198 | },
199 | {
200 | "model": "example.state",
201 | "pk": 22,
202 | "fields": {
203 | "country": 1,
204 | "name": "Rond\u00f4nia",
205 | "active": true
206 | }
207 | },
208 | {
209 | "model": "example.state",
210 | "pk": 23,
211 | "fields": {
212 | "country": 1,
213 | "name": "Roraima",
214 | "active": true
215 | }
216 | },
217 | {
218 | "model": "example.state",
219 | "pk": 24,
220 | "fields": {
221 | "country": 1,
222 | "name": "Santa Catarina",
223 | "active": true
224 | }
225 | },
226 | {
227 | "model": "example.state",
228 | "pk": 25,
229 | "fields": {
230 | "country": 1,
231 | "name": "S\u00e3o Paulo",
232 | "active": true
233 | }
234 | },
235 | {
236 | "model": "example.state",
237 | "pk": 26,
238 | "fields": {
239 | "country": 1,
240 | "name": "Sergipe",
241 | "active": true
242 | }
243 | },
244 | {
245 | "model": "example.state",
246 | "pk": 27,
247 | "fields": {
248 | "country": 1,
249 | "name": "Tocantins",
250 | "active": true
251 | }
252 | },
253 | {
254 | "model": "example.region",
255 | "pk": 1,
256 | "fields": {
257 | "country": 1,
258 | "name": "Norte",
259 | "active": true
260 | }
261 | },
262 | {
263 | "model": "example.region",
264 | "pk": 2,
265 | "fields": {
266 | "country": 1,
267 | "name": "Nordeste",
268 | "active": true
269 | }
270 | },
271 | {
272 | "model": "example.region",
273 | "pk": 3,
274 | "fields": {
275 | "country": 1,
276 | "name": "Centro-Oeste",
277 | "active": true
278 | }
279 | },
280 | {
281 | "model": "example.region",
282 | "pk": 4,
283 | "fields": {
284 | "country": 1,
285 | "name": "Sudeste",
286 | "active": true
287 | }
288 | },
289 | {
290 | "model": "example.region",
291 | "pk": 5,
292 | "fields": {
293 | "country": 1,
294 | "name": "Sul",
295 | "active": true
296 | }
297 | },
298 | {
299 | "model": "auth.user",
300 | "pk": 1,
301 | "fields": {
302 | "password": "pbkdf2_sha256$150000$JmlShwDi4gAa$7ctE0wVewpljxqhaxK95Jd8wxfTyUZskjd/8eND2OWQ=",
303 | "last_login": "2021-06-08T01:44:47.640Z",
304 | "is_superuser": true,
305 | "username": "admin",
306 | "first_name": "",
307 | "last_name": "",
308 | "email": "admin@admin.com",
309 | "is_staff": true,
310 | "is_active": true,
311 | "date_joined": "2021-06-08T01:44:37.956Z",
312 | "groups": [],
313 | "user_permissions": []
314 | }
315 | }
316 | ]
317 |
--------------------------------------------------------------------------------