├── .github
└── workflows
│ ├── code-quality.yml
│ ├── on-push.yml
│ ├── on-tag.yml
│ ├── pypi.yml
│ ├── sonar.yml
│ └── tests.yml
├── .gitignore
├── .pylintrc
├── CHANGELOG.md
├── LICENSE
├── MANIFEST.in
├── README.md
├── django_admin_inline_paginator
├── __init__.py
├── admin.py
├── apps.py
├── static
│ └── django_admin_inline_paginator
│ │ └── paginator.css
├── templates
│ └── admin
│ │ ├── tabular_paginated.html
│ │ └── tabular_paginator.html
└── templatetags
│ ├── __init__.py
│ └── paginated_inline.py
├── example
├── app
│ └── example
│ │ ├── admin.py
│ │ ├── migrations
│ │ ├── 0001_initial.py
│ │ └── __init__.py
│ │ └── models.py
├── conf
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── fixtures
│ └── bkp.json
├── makefile
├── manage.py
└── requirements.txt
├── profile-bandit.yml
├── pytest.ini
├── setup.cfg
├── setup.py
├── tests
├── __init__.py
├── admin_unit_test.py
└── apps_test.py
└── tox.ini
/.github/workflows/code-quality.yml:
--------------------------------------------------------------------------------
1 | name: Run Code Quality Tools
2 |
3 | on:
4 | workflow_call:
5 |
6 | jobs:
7 | code-quality:
8 | name: Code Quality
9 | runs-on: ubuntu-latest
10 |
11 | steps:
12 | - uses: actions/checkout@v2
13 | - name: Set up Python 3.10
14 | uses: actions/setup-python@v3
15 | with:
16 | python-version: "3.x"
17 |
18 | - name: Install dependencies
19 | run: |
20 | pip install ."[dev]" ."[code-quality]"
21 |
22 | - name: Run isort
23 | run: isort ./django_admin_inline_paginator --check-only --diff
24 |
25 | - name: Run xenon (Cyclomatic Complexity)
26 | run: xenon --max-absolute B --max-modules A --max-average A ./django_admin_inline_paginator
27 |
28 | - name: Run bandit
29 | run: bandit -c profile-bandit.yml ./django_admin_inline_paginator -r -ll
--------------------------------------------------------------------------------
/.github/workflows/on-push.yml:
--------------------------------------------------------------------------------
1 | name: Code Quality Tools
2 |
3 | on:
4 | push:
5 | branches:
6 | - '**'
7 | pull_request:
8 |
9 | jobs:
10 | code-quality:
11 | uses: ./.github/workflows/code-quality.yml
12 |
13 | tests:
14 | uses: ./.github/workflows/tests.yml
15 | needs: [code-quality]
16 |
17 | # sonar:
18 | # uses: ./.github/workflows/sonar.yml
19 | # needs: [tests]
20 | # secrets:
21 | # SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
--------------------------------------------------------------------------------
/.github/workflows/on-tag.yml:
--------------------------------------------------------------------------------
1 | name: Build and publish to PyPI
2 |
3 | on:
4 | push:
5 | tags:
6 | - '**'
7 |
8 | jobs:
9 | code-quality:
10 | uses: ./.github/workflows/code-quality.yml
11 |
12 | tests:
13 | uses: ./.github/workflows/tests.yml
14 | needs: [code-quality]
15 |
16 | # sonar:
17 | # uses: ./.github/workflows/sonar.yml
18 | # needs: [tests]
19 | # secrets:
20 | # SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
21 |
22 | build-publish:
23 | uses: ./.github/workflows/pypi.yml
24 | needs: [tests]
25 | secrets:
26 | PYPI_API_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
--------------------------------------------------------------------------------
/.github/workflows/pypi.yml:
--------------------------------------------------------------------------------
1 | name: Build Package and Upload to PyPI
2 |
3 | on:
4 | workflow_call:
5 | secrets:
6 | PYPI_API_TOKEN:
7 | required: true
8 |
9 | jobs:
10 | build-publish:
11 | name: Package to PyPI
12 | runs-on: ubuntu-latest
13 |
14 | steps:
15 | - uses: actions/checkout@v2
16 | - name: Set up Python 3.10
17 | uses: actions/setup-python@v3
18 | with:
19 | python-version: "3.x"
20 |
21 | - name: Install dependencies
22 | run: |
23 | python -m pip install --upgrade pip
24 | pip install setuptools wheel twine
25 |
26 | - name: Build a binary wheel and a source tarball
27 | run: python3 setup.py sdist bdist_wheel --version=$GITHUB_REF_NAME
28 |
29 | - name: Publish distribution 📦 to PyPI
30 | uses: pypa/gh-action-pypi-publish@release/v1
31 | with:
32 | password: ${{ secrets.PYPI_API_TOKEN }}
--------------------------------------------------------------------------------
/.github/workflows/sonar.yml:
--------------------------------------------------------------------------------
1 | name: Run SonarQube
2 |
3 | on:
4 | workflow_call:
5 | secrets:
6 | SONAR_TOKEN:
7 | required: true
8 |
9 | jobs:
10 | sonar:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v2
14 | with:
15 | # Disabling shallow clone is recommended for improving relevancy of reporting
16 | fetch-depth: 0
17 |
18 | - uses: actions/download-artifact@v2
19 | with:
20 | name: test-coverage
21 |
22 | - name: SonarCloud Scan
23 | uses: sonarsource/sonarcloud-github-action@master
24 | env:
25 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
26 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
--------------------------------------------------------------------------------
/.github/workflows/tests.yml:
--------------------------------------------------------------------------------
1 | name: Run Tests
2 |
3 | on:
4 | workflow_call:
5 |
6 | jobs:
7 | tests:
8 | name: Tests
9 | runs-on: ubuntu-20.04
10 | strategy:
11 | matrix:
12 | python-version: ['3.6', '3.7', '3.8', '3.9', '3.10', '3.11']
13 |
14 | steps:
15 | - uses: actions/checkout@v3
16 | - name: Set up Python ${{ matrix.python-version }}
17 | uses: actions/setup-python@v4
18 | with:
19 | python-version: ${{ matrix.python-version }}
20 | - name: Install dependencies
21 | run: |
22 | pip install tox-gh-actions
23 | pip install ."[dev]"
24 |
25 | - name: Test with tox
26 | run: tox
27 |
28 | - uses: actions/upload-artifact@v2
29 | with:
30 | name: test-coverage
31 | path: coverage.xml
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # IDE Folders
2 | /.idea/*
3 | /.vscode/*
4 |
5 | # Python Files
6 | *.pyc
7 |
8 | # SQL Files
9 | *.db
10 | *.sqlite3
11 | *.sql
12 |
13 | # Test's
14 | /htmlcov/*
15 | .coverage/
16 | .coverage*
17 | coverage.xml
18 | pylint.txt
19 | /.tox/*
20 |
21 | # SonarQube
22 | .scannerwork
23 |
24 | # Others
25 | /venv/*
26 | /dist/*
27 | /django_admin_inline_paginator.egg-info/*
--------------------------------------------------------------------------------
/.pylintrc:
--------------------------------------------------------------------------------
1 | [MASTER]
2 | ignore = migrations,tests,manage.py
3 | django-settings-module=config.settings
4 | load-plugins =
5 | pylint_common,
6 | pylint_django
7 |
8 | [MESSAGES CONTROL]
9 | disable=
10 | django-not-configured,
11 | missing-function-docstring,
12 | missing-class-docstring,
13 | missing-module-docstring,
14 | locally-disabled,
15 | too-few-public-methods
16 |
17 | [FORMAT]
18 | max-module-lines=1000
19 | max-line-length=120
20 |
21 | [DESIGN]
22 | max-args=10
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 | All notable changes to this project will be documented in this file.
3 |
4 |
5 | ## [0.3.0] - 2021-12-26
6 | Support for type hint and compatibility with django 4.0
7 |
8 | * ### Added
9 | - [ISSUE-14](https://github.com/shinneider/django-admin-inline-paginator/issues/14) Add python type hint.
10 | - [ISSUE-18](https://github.com/shinneider/django-admin-inline-paginator/issues/18) Remove deprecated ugettext for support for django 4.
11 |
12 | * ### Changed
13 | * ### Fixed
14 | * ### Credits (Contributors)
15 | - [@aitorres](https://github.com/aitorres)
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2020 Shinneider
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 copies
9 | of the Software, and to permit persons to whom the Software is furnished to
10 | do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
16 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
17 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
18 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include LICENSE
2 | include README.md
3 |
4 | recursive-include django_admin_inline_paginator/templates *
5 | recursive-include django_admin_inline_paginator/static *
6 | recursive-include django_admin_inline_paginator/locale *
7 | global-exclude __pycache__
8 | global-exclude *.py[co]
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Django Admin Inline Paginator
2 | =============================
3 |
4 | The "Django Admin Inline Paginator" is simple way to paginate your inline in django admin
5 |
6 | If you use or like the project, click `Star` and `Watch` to generate metrics and i evaluate project continuity.
7 |
8 | # Install:
9 |
10 | ```
11 | pip install django-admin-inline-paginator
12 | ```
13 |
14 | # Usage:
15 |
16 | 1. Add to your INSTALLED_APPS, in settings.py:
17 |
18 | ```
19 | INSTALLED_APPS = [
20 | ...
21 | 'django_admin_inline_paginator',
22 | ...
23 | ]
24 | ```
25 | 2. Create your model inline:
26 |
27 | ```
28 | from django_admin_inline_paginator.admin import TabularInlinePaginated
29 |
30 | class ModelWithFKAdminInline(TabularInlinePaginated):
31 | fields = (...)
32 | per_page = 1
33 | model = ModelWithFK
34 | ```
35 | 3. Create main model admin and use inline:
36 |
37 | ```
38 | @register(YourModel)
39 | class YourModelAdmin(ModelAdmin):
40 | fields = (...)
41 | inlines = (ModelWithFKAdminInline, )
42 | model = YourModel
43 | ```
44 |
45 | # Advanced Usage:
46 |
47 | 1. Paginate multiples inlines:
48 |
49 | ```
50 | class ModelWithFKInline(TabularInlinePaginated):
51 | fields = ('name', 'active')
52 | per_page = 2
53 | model = ModelWithFK
54 | pagination_key = 'page-model' # make sure it's unique for page inline
55 |
56 | class AnotherModelWithFKInline(TabularInlinePaginated):
57 | fields = ('name', 'active')
58 | per_page = 2
59 | model = AnotherModelWithFK
60 | pagination_key = 'page-another-model' # make sure it's unique for page inline
61 | ```
62 |
63 | 2. Use previous inlines
64 |
65 | ```
66 | @register(YourModel)
67 | class YourModelAdmin(ModelAdmin):
68 | fields = (...)
69 | inlines = (ModelWithFKAdminInline, AnotherModelWithFKInline)
70 | model = YourModel
71 | ```
72 |
73 | # Images:
74 |
75 | 
76 |
77 | # Need a Maintainer
78 | In the last months i don't have much time, health problemas, change of country and others problems.
79 | i have some surgeries for first part of 2022, and all of my current project don't use django-admin.
80 | for these reasons, i need a help for a project continuation!!
81 |
--------------------------------------------------------------------------------
/django_admin_inline_paginator/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | try:
3 | import django
4 |
5 | if django.VERSION < (3, 2): # pragma: no cover
6 | default_app_config = 'django_admin_inline_paginator.apps.DjangoAdminInlinePaginatorConfig'
7 | except ImportError:
8 | pass
9 |
--------------------------------------------------------------------------------
/django_admin_inline_paginator/admin.py:
--------------------------------------------------------------------------------
1 | from typing import Optional
2 |
3 | from django.contrib.admin import 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 | can_show_all = True
16 | multi_page = True
17 | get_query_string = ChangeList.__dict__['get_query_string']
18 |
19 | def __init__(self, request: HttpRequest, page_num: int, paginator: Paginator):
20 | self.show_all = 'all' in request.GET
21 | self.page_num = page_num
22 | self.paginator = paginator
23 | self.result_count = paginator.count
24 | self.params = dict(request.GET.items())
25 |
26 |
27 | class PaginationFormSetBase:
28 | queryset: Optional[QuerySet] = None
29 | request: Optional[HttpRequest] = None
30 | per_page = 20
31 | pagination_key = 'page'
32 |
33 | def get_page_num(self) -> int:
34 | assert self.request is not None
35 | page = self.request.GET.get(self.pagination_key, '1')
36 | if page.isnumeric() and page > '0':
37 | return int(page)
38 |
39 | return 1
40 |
41 | def get_page(self, paginator: Paginator, page: int):
42 | if page <= paginator.num_pages:
43 | return paginator.page(page)
44 |
45 | return paginator.page(1)
46 |
47 | def mount_paginator(self, page_num: int = None):
48 | assert self.queryset is not None and self.request is not None
49 | page_num = self.get_page_num() if not page_num else page_num
50 | self.paginator = Paginator(self.queryset, self.per_page)
51 | self.page = self.get_page(self.paginator, page_num)
52 | self.cl = InlineChangeList(self.request, page_num, self.paginator)
53 |
54 | def mount_queryset(self):
55 | if self.cl.show_all:
56 | self._queryset = self.queryset
57 |
58 | self._queryset = self.page.object_list
59 |
60 | def __init__(self, *args, **kwargs):
61 | super(PaginationFormSetBase, self).__init__(*args, **kwargs)
62 | self.mount_paginator()
63 | self.mount_queryset()
64 |
65 | class InlinePaginated:
66 | pagination_key = 'page'
67 | template = 'admin/tabular_paginated.html'
68 | per_page = 20
69 | extra = 0
70 |
71 | def get_formset(self, request, obj=None, **kwargs):
72 | formset_class = super().get_formset(request, obj, **kwargs)
73 |
74 | class PaginationFormSet(PaginationFormSetBase, formset_class):
75 | pagination_key = self.pagination_key
76 |
77 | PaginationFormSet.request = request
78 | PaginationFormSet.per_page = self.per_page
79 | return PaginationFormSet
80 |
81 | class TabularInlinePaginated(InlinePaginated, TabularInline):
82 | pass
83 |
84 | class GenericTabularInlinePaginated(InlinePaginated, GenericTabularInline):
85 | pass
86 |
--------------------------------------------------------------------------------
/django_admin_inline_paginator/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 DjangoAdminInlinePaginatorConfig(AppConfig):
7 | name = 'django_admin_inline_paginator'
8 | verbose_name = _('Django Admin Inline Paginator')
9 |
--------------------------------------------------------------------------------
/django_admin_inline_paginator/static/django_admin_inline_paginator/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 |
--------------------------------------------------------------------------------
/django_admin_inline_paginator/templates/admin/tabular_paginated.html:
--------------------------------------------------------------------------------
1 | {% include 'admin/edit_inline/tabular.html' %}
2 |
5 |
--------------------------------------------------------------------------------
/django_admin_inline_paginator/templates/admin/tabular_paginator.html:
--------------------------------------------------------------------------------
1 | {% load i18n static %}
2 | {% load paginated_inline %}
3 |
4 |
5 |
6 |
7 | {% with inline_admin_formset.formset.page as page_obj %}
8 |
9 | {% if page_obj.has_previous %}
10 | {% trans 'previous' %}
11 | {% endif %}
12 |
13 | {% if page_obj.number|add:"-5" > 0 %}
14 | 1
15 | {% endif %}
16 |
17 | {% if page_obj.number|add:"-5" > 1 %}
18 | …
19 | {% endif %}
20 |
21 | {% for page_num in page_obj.paginator.page_range %}
22 | {% if page_obj.number == page_num %}
23 | {{ page_num }}
24 | {% else %}
25 | {% if page_num > page_obj.number|add:"-5" and page_num < page_obj.number|add:"5" %}
26 | {{ page_num }}
27 | {% endif %}
28 | {% endif %}
29 | {% endfor %}
30 |
31 | {% if page_obj.number|add:"5" < page_obj.paginator.num_pages %}
32 | …
33 | {% endif %}
34 |
35 | {% if page_obj.number|add:"4" < page_obj.paginator.num_pages %}
36 | {{ page_obj.paginator.num_pages }}
37 | {% endif %}
38 |
39 | {% if page_obj.has_next %}
40 | {% trans 'next' %}
41 | {% endif %}
42 | {{ page_obj.paginator.count }} Results
43 |
44 | {% endwith %}
45 |
46 |
--------------------------------------------------------------------------------
/django_admin_inline_paginator/templatetags/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shinneider/django-admin-inline-paginator/4faa517abba80dd5116268e7a230742e553b4451/django_admin_inline_paginator/templatetags/__init__.py
--------------------------------------------------------------------------------
/django_admin_inline_paginator/templatetags/paginated_inline.py:
--------------------------------------------------------------------------------
1 | import urllib.parse
2 |
3 | from django import template
4 |
5 | register = template.Library()
6 |
7 |
8 | @register.simple_tag
9 | def modify_pagination_path(full_path: str, key: str, value: str) -> str:
10 | get_params = full_path
11 | if get_params.find('?') > -1:
12 | get_params = get_params[get_params.find('?')+1:]
13 | if get_params.find('#') > -1:
14 | get_params = get_params[:get_params.find('#')]
15 |
16 | params = urllib.parse.parse_qs(get_params)
17 | params[key] = [str(value)]
18 | return urllib.parse.urlencode(params, doseq=True)
19 |
--------------------------------------------------------------------------------
/example/app/example/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib.admin import ModelAdmin, register
2 | from django_admin_inline_paginator.admin import TabularInlinePaginated
3 |
4 | from .models import Country, Region, State
5 |
6 |
7 | class StateAdminInline(TabularInlinePaginated):
8 | fields = ('name', 'active')
9 | per_page = 5
10 | model = State
11 |
12 |
13 | class RegionAdminInline(TabularInlinePaginated):
14 | fields = ('name', 'active')
15 | per_page = 2
16 | model = Region
17 | pagination_key = 'rpage'
18 |
19 |
20 | @register(Country)
21 | class CountryAdmin(ModelAdmin):
22 | fields = ('name', 'active')
23 | inlines = (StateAdminInline, RegionAdminInline)
24 | model = Country
25 |
--------------------------------------------------------------------------------
/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 |
9 | initial = True
10 |
11 | dependencies = [
12 | ]
13 |
14 | operations = [
15 | migrations.CreateModel(
16 | name='Country',
17 | fields=[
18 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
19 | ('name', models.CharField(max_length=100)),
20 | ('active', models.BooleanField(default=True)),
21 | ],
22 | options={
23 | 'verbose_name': 'Country',
24 | 'verbose_name_plural': 'Countries',
25 | },
26 | ),
27 | migrations.CreateModel(
28 | name='State',
29 | fields=[
30 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
31 | ('name', models.CharField(max_length=100)),
32 | ('active', models.BooleanField(default=True)),
33 | ('country', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='example.Country')),
34 | ],
35 | options={
36 | 'verbose_name': 'State',
37 | 'verbose_name_plural': 'States',
38 | },
39 | ),
40 | migrations.CreateModel(
41 | name='Region',
42 | fields=[
43 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
44 | ('name', models.CharField(max_length=100)),
45 | ('active', models.BooleanField(default=True)),
46 | ('country', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='example.Country')),
47 | ],
48 | options={
49 | 'verbose_name': 'Region',
50 | 'verbose_name_plural': 'Regions',
51 | },
52 | ),
53 | ]
54 |
--------------------------------------------------------------------------------
/example/app/example/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shinneider/django-admin-inline-paginator/4faa517abba80dd5116268e7a230742e553b4451/example/app/example/migrations/__init__.py
--------------------------------------------------------------------------------
/example/app/example/models.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from django.db.models import (CASCADE, BooleanField, CharField, ForeignKey,
3 | Model)
4 |
5 |
6 | class Country(Model):
7 | name = CharField(max_length=100)
8 | active = BooleanField(default=True)
9 |
10 | def __str__(self):
11 | return self.name
12 |
13 | class Meta:
14 | verbose_name = 'Country'
15 | verbose_name_plural = 'Countries'
16 |
17 |
18 | class State(Model):
19 | country = ForeignKey('example.Country', on_delete=CASCADE)
20 | name = CharField(max_length=100)
21 | active = BooleanField(default=True)
22 |
23 | def __str__(self):
24 | return self.name
25 |
26 | class Meta:
27 | verbose_name = 'State'
28 | verbose_name_plural = 'States'
29 |
30 |
31 | class Region(Model):
32 | country = ForeignKey('example.Country', on_delete=CASCADE)
33 | name = CharField(max_length=100)
34 | active = BooleanField(default=True)
35 |
36 | def __str__(self):
37 | return self.name
38 |
39 | class Meta:
40 | verbose_name = 'Region'
41 | verbose_name_plural = 'Regions'
42 |
--------------------------------------------------------------------------------
/example/conf/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shinneider/django-admin-inline-paginator/4faa517abba80dd5116268e7a230742e553b4451/example/conf/__init__.py
--------------------------------------------------------------------------------
/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 |
43 | # Third part apps
44 | 'django_admin_inline_paginator',
45 |
46 | # Developed apps
47 | 'app.example',
48 | ]
49 |
50 | MIDDLEWARE = [
51 | 'django.middleware.security.SecurityMiddleware',
52 | 'django.contrib.sessions.middleware.SessionMiddleware',
53 | 'django.middleware.common.CommonMiddleware',
54 | 'django.middleware.csrf.CsrfViewMiddleware',
55 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
56 | 'django.contrib.messages.middleware.MessageMiddleware',
57 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
58 | ]
59 |
60 | ROOT_URLCONF = 'conf.urls'
61 |
62 | TEMPLATES = [
63 | {
64 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
65 | 'DIRS': [
66 | os.path.join(BASE_DIR, 'templates'),
67 | ],
68 | 'APP_DIRS': True,
69 | 'OPTIONS': {
70 | 'context_processors': [
71 | 'django.template.context_processors.debug',
72 | 'django.template.context_processors.request',
73 | 'django.contrib.auth.context_processors.auth',
74 | 'django.contrib.messages.context_processors.messages',
75 | ],
76 | },
77 | },
78 | ]
79 |
80 | WSGI_APPLICATION = 'conf.wsgi.application'
81 |
82 |
83 | # Database
84 | # https://docs.djangoproject.com/en/2.2/ref/settings/#databases
85 |
86 | DATABASES = {
87 | 'default': {
88 | 'ENGINE': 'django.db.backends.sqlite3',
89 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
90 | }
91 | }
92 |
93 |
94 | # Password validation
95 | # https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators
96 |
97 | AUTH_PASSWORD_VALIDATORS = [
98 | {
99 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
100 | },
101 | {
102 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
103 | },
104 | {
105 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
106 | },
107 | {
108 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
109 | },
110 | ]
111 |
112 |
113 | # Internationalization
114 | # https://docs.djangoproject.com/en/2.2/topics/i18n/
115 |
116 | LANGUAGE_CODE = 'en-us'
117 |
118 | TIME_ZONE = 'UTC'
119 |
120 | USE_I18N = True
121 |
122 | USE_L10N = True
123 |
124 | USE_TZ = True
125 |
126 |
127 | # Static files (CSS, JavaScript, Images)
128 | # https://docs.djangoproject.com/en/2.2/howto/static-files/
129 |
130 | STATIC_URL = '/static/'
131 |
--------------------------------------------------------------------------------
/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 | from django.contrib import admin
17 | from django.urls import path
18 |
19 | urlpatterns = [
20 | path('admin/', admin.site.urls),
21 | ]
22 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------
/example/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | """Django's command-line utility for administrative tasks."""
3 | import os
4 | import sys
5 |
6 |
7 | def main():
8 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'conf.settings')
9 | try:
10 | from django.core.management import execute_from_command_line
11 | except ImportError as exc:
12 | raise ImportError(
13 | "Couldn't import Django. Are you sure it's installed and "
14 | "available on your PYTHONPATH environment variable? Did you "
15 | "forget to activate a virtual environment?"
16 | ) from exc
17 | execute_from_command_line(sys.argv)
18 |
19 |
20 | if __name__ == '__main__':
21 | main()
22 |
--------------------------------------------------------------------------------
/example/requirements.txt:
--------------------------------------------------------------------------------
1 | django==2.2.*
2 | ..
3 |
--------------------------------------------------------------------------------
/profile-bandit.yml:
--------------------------------------------------------------------------------
1 | exclude_dirs:
2 | - "*/venv/*"
3 | - "*/.venv/*"
4 | - "*/env/*"
5 | - "*/.env/*"
6 | - "*/scripts/*"
7 | # skips:
8 | # - 'B102'
9 | # - 'B611'
10 |
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [metadata]
2 | description-file = README.md
3 |
4 | [pycodestyle]
5 | max-line-length = 120
6 | exclude = */.git/*,*/venv/*,*/migrations/*,.env
7 |
8 | [isort]
9 | sections = FUTURE,STDLIB,DJANGO,THIRDPARTY,FIRSTPARTY,LOCALFOLDER
10 | known_django = django
11 | skip = venv,env,node_modules,migrations,.env,.venv
12 | skip_glob = */.git/*,*/venv/*,*/migrations/*
13 | line_length = 120
14 |
15 | [coverage:run]
16 | omit =
17 | */venv/*
18 | */env/*
19 | */.venv/*
20 | */.env/*
21 | **/__pycache__/**
22 | **/tests/**
23 | **/migrations/**
24 | config/**
25 | .vscode/**
26 | */.local/*
27 | */usr/lib/*
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | import sys
2 | from io import open
3 |
4 | from setuptools import find_packages, setup
5 |
6 |
7 | extras_require = {
8 | 'dev': [
9 | 'django',
10 | 'django_mock_queries',
11 | 'pylint',
12 | 'pytest-pylint',
13 | 'pytest',
14 | 'pytest-cov',
15 | 'pytest-watch',
16 | 'tox'
17 | ],
18 | 'code-quality': [
19 | 'isort',
20 | 'bandit',
21 | 'xenon',
22 | ],
23 | }
24 |
25 |
26 | def get_version():
27 | version = '0.0'
28 | for arg in sys.argv:
29 | if arg.startswith('--version'):
30 | version = arg.split("=")[1]
31 | sys.argv.remove(arg)
32 | break
33 |
34 | return version if version[0] != 'v' else version[1:]
35 |
36 |
37 | setup(
38 | name='django-admin-inline-paginator',
39 | version=get_version(),
40 | description='The "Django Admin Inline Paginator" is simple way to paginate your inline in django admin',
41 | long_description=open('README.md', encoding='utf-8').read(),
42 | long_description_content_type='text/markdown',
43 | author='Shinneider Libanio da Silva',
44 | author_email='shinneider-libanio@hotmail.com',
45 | url='https://github.com/shinneider/django-admin-inline-paginator',
46 | license='MIT',
47 | packages=find_packages(exclude=['tests*']),
48 | include_package_data=True,
49 | python_requires=">=3.3",
50 | extras_require=extras_require,
51 | install_requires=[
52 | 'django',
53 | ],
54 | classifiers=[
55 | 'Development Status :: 4 - Beta',
56 | 'Environment :: Web Environment',
57 | 'Framework :: Django',
58 | 'Intended Audience :: Developers',
59 | 'License :: OSI Approved :: MIT License',
60 | 'Operating System :: OS Independent',
61 | 'Programming Language :: Python',
62 | 'Programming Language :: Python :: 3',
63 | 'Programming Language :: Python :: 3.5',
64 | 'Programming Language :: Python :: 3.6',
65 | 'Programming Language :: Python :: 3.7',
66 | 'Programming Language :: Python :: 3.8',
67 | 'Topic :: Internet :: WWW/HTTP',
68 | 'Topic :: Software Development :: Libraries :: Python Modules',
69 | ]
70 | )
71 |
--------------------------------------------------------------------------------
/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 |
17 | # Third part apps
18 | 'django_admin_inline_paginator',
19 | 'tests'
20 | ]
21 |
22 | settings.TEMPLATES = [
23 | {
24 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
25 | 'DIRS': [
26 | os.path.join(BASE_DIR, 'templates'),
27 | ],
28 | 'APP_DIRS': True,
29 | },
30 | ]
31 |
32 | settings.STATIC_URL = '/'
33 | django.setup()
34 |
--------------------------------------------------------------------------------
/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.admin import InlineChangeList, PaginationFormSetBase
6 |
7 |
8 | class TestInlineChangeList(unittest.TestCase):
9 |
10 | def test_default_values(self):
11 | self.assertEqual(InlineChangeList.can_show_all, True)
12 | self.assertEqual(InlineChangeList.multi_page, True)
13 | self.assertEqual(InlineChangeList.get_query_string, ChangeList.__dict__['get_query_string'])
14 |
15 | def test_init_values(self):
16 | pass
17 | # cl = InlineChangeList(request, page_num, paginator)
18 | # cl.page_num = page_num
19 | # cl.paginator = paginator
20 | # cl.result_count = paginator.count
21 |
22 | # cl.show_all = 'all' in request.GET
23 | # cl.params = dict(request.GET.items())
24 |
25 |
26 | class TestPaginationFormSetBase(unittest.TestCase):
27 |
28 | def test_default_values(self):
29 | self.assertEqual(PaginationFormSetBase.queryset, None)
30 | self.assertEqual(PaginationFormSetBase.request, None)
31 | self.assertEqual(PaginationFormSetBase.per_page, 20)
32 |
33 | def test_get_page_num(self):
34 | pass
35 |
36 | def test_get_page(self):
37 | pass
38 |
39 | def test_mount_paginator(self):
40 | pass
41 |
42 | def test_mount_queryset(self):
43 | pass
44 |
45 | def test_init(self):
46 | pass
47 |
--------------------------------------------------------------------------------
/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.apps import DjangoAdminInlinePaginatorConfig
7 |
8 |
9 | class TestDjangoAppConfig(unittest.TestCase):
10 |
11 | def test_valid_subclass_appconfig(self):
12 | self.assertEqual(issubclass(DjangoAdminInlinePaginatorConfig, AppConfig), True)
13 |
14 | def test_valid_name(self):
15 | name = DjangoAdminInlinePaginatorConfig.name
16 | self.assertEqual(isinstance(name, str), True)
17 | self.assertEqual(name, 'django_admin_inline_paginator')
18 |
19 | def test_valid_verbose_name(self):
20 | verbose_name = DjangoAdminInlinePaginatorConfig.verbose_name
21 | self.assertEqual(verbose_name, _('Django Admin Inline Paginator'))
22 |
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | [tox]
2 | envlist = py{36,37}-django{22,30,31,32}
3 | py{38,39}-django{22,30,31,32,40,41,42}
4 | py{310}-django{32,40,41,42}
5 | py{311}-django{41,42}
6 |
7 | [gh-actions]
8 | python =
9 | 3.6: py36
10 | 3.7: py37
11 | 3.8: py38
12 | 3.9: py39
13 | 3.10: py310
14 | 3.11: py311
15 |
16 | [testenv]
17 | commands =
18 | pytest --cov=django_admin_inline_paginator \
19 | --cov-config=tox.ini \
20 | --cov-fail-under=35 \
21 | --cov-report=term-missing \
22 | --cov-report=xml:coverage.xml \
23 | --durations=10 \
24 | --cov-append
25 | extras = dev
26 | deps =
27 | pytest
28 | pytest-cov
29 | django22: Django>=2.2,<2.3
30 | django30: Django>=3.0,<3.1
31 | django31: Django>=3.1,<3.2
32 | django32: Django>=3.2,<4.0
33 | django40: Django>=4.0,<4.1
34 | django41: Django>=4.1,<4.2
35 | django42: Django>=4.2,<5.0
36 |
37 | [coverage:run]
38 | relative_files = True
39 | source = django_admin_inline_paginator/
40 | branch = True
41 |
42 | [testenv:report]
43 | deps = coverage
44 | skip_install = true
45 | commands =
46 | coverage report
47 | coverage html
--------------------------------------------------------------------------------