├── .github └── workflows │ ├── build.yml │ ├── lint.yml │ └── pypi.yml ├── .gitignore ├── .yamllint ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.rst ├── docs └── images │ └── screenshot.png ├── pyproject.toml ├── rangefilter ├── __init__.py ├── apps.py ├── filters.py ├── locale │ ├── cs_CZ │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── da │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── de │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── el │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── en_US │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── es │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── es_AR │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── fa │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── fr │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── it │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── ja │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── pl │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── pt_BR │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── pt_PT │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── ru │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── tr │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── uk │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── zh_CN │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ └── zh_Hans │ │ └── LC_MESSAGES │ │ ├── django.mo │ │ └── django.po ├── models.py ├── templates │ └── rangefilter │ │ ├── date_filter.html │ │ ├── date_filter_1_8.html │ │ ├── date_filter_4_0.html │ │ ├── date_range_quick_select_list_filter.html │ │ ├── numeric_filter.html │ │ └── numeric_filter_4_0.html └── templatetags │ ├── __init__.py │ └── rangefilter_compat.py ├── requirements-dev.txt ├── setup.py └── tests ├── __init__.py ├── migrations ├── 0001_initial.py └── __init__.py ├── models.py ├── settings.py └── tests.py /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "build" 3 | 4 | on: # yamllint disable-line rule:truthy 5 | pull_request: 6 | push: 7 | branches: 8 | - master 9 | 10 | jobs: 11 | build: 12 | name: Python ${{ matrix.python-version }} | Django ${{ matrix.django-version}} | Ubuntu 13 | runs-on: ubuntu-20.04 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | include: 18 | - python-version: 3.6 19 | django-version: "1.11.*" 20 | - python-version: 3.6 21 | django-version: "2.0.*" 22 | - python-version: 3.6 23 | django-version: "2.1.*" 24 | - python-version: 3.6 25 | django-version: "2.2.*" 26 | - python-version: 3.6 27 | django-version: "3.0.*" 28 | - python-version: 3.6 29 | django-version: "3.1.*" 30 | - python-version: 3.6 31 | django-version: "3.2.*" 32 | 33 | - python-version: 3.7 34 | django-version: "2.0.*" 35 | - python-version: 3.7 36 | django-version: "2.1.*" 37 | - python-version: 3.7 38 | django-version: "2.2.*" 39 | - python-version: 3.7 40 | django-version: "3.0.*" 41 | - python-version: 3.7 42 | django-version: "3.1.*" 43 | - python-version: 3.7 44 | django-version: "3.2.*" 45 | 46 | - python-version: 3.8 47 | django-version: "2.2.*" 48 | - python-version: 3.8 49 | django-version: "3.0.*" 50 | - python-version: 3.8 51 | django-version: "3.1.*" 52 | - python-version: 3.8 53 | django-version: "3.2.*" 54 | - python-version: 3.8 55 | django-version: "4.0.*" 56 | 57 | - python-version: 3.9 58 | django-version: "3.0.*" 59 | - python-version: 3.9 60 | django-version: "3.1.*" 61 | - python-version: 3.9 62 | django-version: "3.2.*" 63 | - python-version: 3.9 64 | django-version: "4.0.*" 65 | - python-version: 3.9 66 | django-version: "4.1.*" 67 | 68 | - python-version: "3.10" 69 | django-version: "3.0.*" 70 | - python-version: "3.10" 71 | django-version: "3.1.*" 72 | - python-version: "3.10" 73 | django-version: "3.2.*" 74 | - python-version: "3.10" 75 | django-version: "4.0.*" 76 | - python-version: "3.10" 77 | django-version: "4.1.*" 78 | - python-version: "3.10" 79 | django-version: "5.0.*" 80 | - python-version: "3.10" 81 | django-version: "5.1.*" 82 | 83 | - python-version: "3.11" 84 | django-version: "4.2.*" 85 | - python-version: "3.11" 86 | django-version: "5.0.*" 87 | coverage: true 88 | - python-version: "3.11" 89 | django-version: "5.1.*" 90 | 91 | - python-version: "3.12" 92 | django-version: "4.2.*,>=4.2.8" 93 | - python-version: "3.12" 94 | django-version: "5.0.*" 95 | - python-version: "3.12" 96 | django-version: "5.1.*" 97 | 98 | steps: 99 | - name: Checkout 100 | uses: actions/checkout@v3 101 | 102 | - name: Setup Python ${{ matrix.python-version }} 103 | uses: actions/setup-python@v4 104 | with: 105 | python-version: ${{ matrix.python-version }} 106 | 107 | - name: Install Django ${{ matrix.django-version }} 108 | run: | 109 | pip install Django==${{ matrix.django-version }} 110 | pip install coverage 111 | pip install pytz 112 | 113 | - name: Run tests 114 | run: | 115 | make test 116 | 117 | - if: ${{ matrix.coverage }} 118 | run: | 119 | make coverage 120 | 121 | - if: ${{ matrix.coverage }} 122 | name: Upload coverage to Codecov 123 | uses: codecov/codecov-action@v3 124 | with: 125 | token: ${{ secrets.CODECOV_TOKEN }} 126 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Lint" 3 | 4 | on: # yamllint disable-line rule:truthy 5 | pull_request: 6 | push: 7 | branches: 8 | - master 9 | 10 | jobs: 11 | lint: 12 | name: Lint 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v2 18 | 19 | - name: Setup Python 3.11 20 | uses: actions/setup-python@v1 21 | with: 22 | python-version: 3.11 23 | 24 | - name: Upgrade Setuptools 25 | run: pip install --upgrade setuptools wheel 26 | 27 | - name: Install requirements 28 | run: pip install -r requirements-dev.txt 29 | 30 | - name: Run lint 31 | run: make lint 32 | -------------------------------------------------------------------------------- /.github/workflows/pypi.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "PyPI Release" 3 | 4 | on: # yamllint disable-line rule:truthy 5 | push: 6 | tags: 7 | - "v*" 8 | 9 | jobs: 10 | publish: 11 | name: PyPI Release 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v2 17 | 18 | - name: Setup Python 3.9 19 | uses: actions/setup-python@v1 20 | with: 21 | python-version: 3.9 22 | 23 | - name: Upgrade Setuptools 24 | run: pip install --upgrade setuptools wheel 25 | 26 | - name: Build Distribution 27 | run: python setup.py sdist bdist_wheel --universal 28 | 29 | - name: Publish to PyPI 30 | uses: pypa/gh-action-pypi-publish@master 31 | with: 32 | user: __token__ 33 | password: ${{ secrets.pypi_password }} 34 | -------------------------------------------------------------------------------- /.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 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.pot 50 | 51 | # Django stuff: 52 | *.log 53 | local_settings.py 54 | 55 | # Flask stuff: 56 | instance/ 57 | .webassets-cache 58 | 59 | # Scrapy stuff: 60 | .scrapy 61 | 62 | # Sphinx documentation 63 | docs/_build/ 64 | 65 | # PyBuilder 66 | target/ 67 | 68 | # IPython Notebook 69 | .ipynb_checkpoints 70 | 71 | # pyenv 72 | .python-version 73 | 74 | # celery beat schedule file 75 | celerybeat-schedule 76 | 77 | # dotenv 78 | .env 79 | 80 | # virtualenv 81 | venv/ 82 | ENV/ 83 | 84 | # Spyder project settings 85 | .spyderproject 86 | 87 | # Rope project settings 88 | .ropeproject 89 | 90 | # Translate 91 | .tx 92 | -------------------------------------------------------------------------------- /.yamllint: -------------------------------------------------------------------------------- 1 | --- 2 | extends: default 3 | 4 | rules: 5 | line-length: disable 6 | 7 | ignore: | 8 | *.venv/ 9 | *.mypy_cache/ 10 | *.eggs/ 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [Unreleased] 2 | 3 | ## [0.13.2] - 2024-08-24 4 | ### Fix 5 | - Fix prevent datetimeshortcuts from rendering multiple times (#129) (thx @vxsx) 6 | 7 | ## [0.13.1] - 2024-07-23 8 | ### Fix 9 | - Fix initial value in NumericRangeFilter form field (#124) (thx @gretapataki) 10 | 11 | ## [0.13.0] - 2024-05-21 12 | ### Added 13 | - Compatibility Django 5 14 | - Add support for collapsable filter (Django >= 4.1) (#120). Change the default range template 15 | 16 | ## [0.12.5] - 2024-05-18 17 | ### Fix 18 | - Fix quick select template property #118 19 | 20 | ## [0.12.4] - 2024-04-13 21 | ### Fix 22 | - Typo 23 | 24 | ## [0.12.3] - 2024-04-13 25 | ### Fix 26 | - Typo 27 | 28 | ## [0.12.2] - 2024-04-13 29 | ### Fix 30 | - DateTimeRangeFilter: microsecond is optional #115 31 | 32 | ## [0.12.1] - 2024-02-12 33 | ### Added 34 | - Use current timezone instead of default #114 35 | 36 | ### Fix 37 | - DateTimeRangeFilter: upper time bound includes microseconds #115 38 | 39 | ## [0.12.0] - 2023-12-09 40 | ### Added 41 | - Compatibility Django 5 42 | - Added Greek translation (thx @tagmat) #111 43 | 44 | ### Fix 45 | - NumericRangeFilter: Fix SCP 46 | 47 | ## [0.11.2] - 2023-09-10 48 | ### Fix 49 | - NumericRangeFilter: Fix filter for the value zero (#105) (thx @autoantwort) 50 | 51 | ## [0.11.1] - 2023-08-29 52 | ### Fix 53 | - Fix DateRangeQuickSelectListFilter (thx @robertpro) 54 | 55 | ## [0.11.0] - 2023-08-19 56 | ### Added 57 | - Add Add DateRangeQuickSelectListFilter (#103) (thx @robertpro) 58 | 59 | ## [0.10.0] - 2023-04-08 60 | ### Added 61 | - Add builders. Now you can use Builders to create filters: 62 | `DateRangeFilterBuilder`, `DateTimeRangeFilterBuilder`, `NumericRangeFilterBuilder`. 63 | Its useful for definition custom title or initial (default) values. 64 | - Compatibility Django 4.2 65 | 66 | ## [0.9.0] - 2022-09-25 67 | ### Added 68 | - Add filter for integer and float #85 69 | 70 | ## [0.8.8] - 2022-09-01 71 | ### Fix 72 | - Fix using settings.USE_TZ = False on django4.0+ #85 73 | 74 | ## [0.8.7] - 2022-08-13 75 | ### Added 76 | - Use admin CSS vars for colors (thx @mharju) 77 | - Added Farsi translation (thx @shahmohammadims) 78 | 79 | ## [0.8.6] - 2022-08-07 80 | ### Added 81 | - Compatibility Django 4.1 82 | 83 | ## [0.8.5] - 2022-07-03 84 | ### Fix 85 | - Fix use of admin_static causes validate_templates fails #78 86 | 87 | ## [0.8.4] - 2022-02-19 88 | ### Fix 89 | - `nonce` breaks admin inlines styles #74 (thx @fabiocaccamo) 90 | - Fix tests 91 | 92 | ## [0.8.3] - 2021-12-19 93 | ### Added 94 | - Compatibility Django 4.0 95 | 96 | ## [0.8.2] - 2021-10-31 97 | ### Fix 98 | - Fixing case with two widgets on one page #67 (thx @snnwolf) 99 | 100 | ## [0.8.1] - 2021-08-10 101 | ### Fix 102 | - Fixed problem with `ManifestStaticFilesStorage` #63 103 | 104 | ## [0.8.0] - 2021-04-10 105 | ### Added 106 | - Compatibility Django 3.2 107 | 108 | ### Changed 109 | - Module `filter` rename to `filters` 110 | 111 | ### Fix 112 | - Fixed static loads 113 | 114 | ## [0.7.0] - 2021-02-22 115 | ### Added 116 | - Added possibility to set a title of filter #58 117 | 118 | ### Fix 119 | - Avoided leading ?& GET parameters #59 (thx @jaredahern) 120 | 121 | ### Changed 122 | - Changed title of filter from `By {field_name}` to `{field_name}` 123 | 124 | ## [0.6.4] - 2021-01-19 125 | ### Added 126 | - Added Greek translation 127 | - Added Italian translation 128 | - Moved tests to Github Actions 129 | 130 | ## [0.6.3] - 2020-10-01 131 | ### Added 132 | - Added Polish translation #45 (thx @sqlmiles) 133 | - Added Japanese translation 134 | 135 | ### Fix 136 | - Fix Portuguese translation #53 137 | 138 | ## [0.6.2] - 2020-08-07 139 | ### Added 140 | - Compatibility Django 3.1 141 | 142 | ## [0.6.1] - 2020-06-05 143 | ### Added 144 | - Added Danish translation #45 (thx @tiktuk) 145 | 146 | ### Changed 147 | - Refactor tests 148 | 149 | ## [0.6.0] - 2020-05-04 150 | ### Added 151 | - Added setter for initial field value (#44) 152 | 153 | ## [0.5.4] - 2020-02-10 154 | ### Added 155 | - Added Simplified Chinese translation (thx @daimon99) 156 | 157 | ## [0.5.3] - 2019-12-16 158 | ### Added 159 | - Added Brazilian portuguese translation (thx @sandrofolk) 160 | - Use proper Template comment tag #39 (thx @nitinnain) 161 | 162 | ## [0.5.2] - 2019-12-04 163 | ### Added 164 | - Compatibility Django 3.0 165 | 166 | ## [0.5.1] - 2019-10-15 167 | ### Changed 168 | - Fix inline CSS that overrided base a admin CSS (#36) 169 | 170 | ## [0.5.0] - 2019-07-04 171 | ### Added 172 | - Add csp compliance through django-csp (thanks @jsumnerPhD) 173 | 174 | ### Fixed 175 | - Fix problem with locale 176 | 177 | ## [0.4.0] - 2019-04-19 178 | ### Changed 179 | - Changed name lookup field `{field}__gte` -> `{field}__range__gte` 180 | - Changed name lookup field `{field}__lte` -> `{field}__range__lte` 181 | 182 | ## [0.3.16] - 2019-04-14 183 | ### Changed 184 | - Fix problem when thousand separator is used (#18) 185 | 186 | ## [0.3.15] - 2019-04-05 187 | ### Added 188 | - Added Spanish translation 189 | - Compatibility Django 2.2 190 | 191 | ## [0.3.14] - 2019-03-25 192 | ### Added 193 | - Added French translation 194 | - Added German translation 195 | 196 | ## [0.3.13] - 2019-03-21 197 | ### Added 198 | - Added Czech translation 199 | 200 | ## [0.3.12] - 2019-01-31 201 | ### Added 202 | - Added Russian translation 203 | 204 | ## [0.3.11] - 2019-01-30 205 | ### Changed 206 | - Avoid loading admin_static in templates under Django>=1.10 (#27) 207 | 208 | ## [0.3.10] - 2018-12-05 209 | ### Changed 210 | - Fix calendar position on mobile (#23) 211 | 212 | ## [0.3.9] - 2018-10-31 213 | ### Changed 214 | - Fix calendar icons displayed for Django 2.1 215 | 216 | ## [0.3.8] - 2018-10-12 217 | ### Added 218 | - Compatibility Django 2.1 219 | 220 | ## [0.3.7] - 2018-06-29 221 | ### Changed 222 | - Fix system name with non-unicode char (#18) 223 | 224 | ## [0.3.6] - 2018-04-27 225 | ### Changed 226 | - Change padding on the buttons (#16) 227 | 228 | ## [0.3.5] - 2018-03-17 229 | ### Added 230 | - Compatibility Django 2.0 231 | 232 | ## [0.3.4] - 2018-03-17 233 | ### Changed 234 | - Add get_timezone 235 | - Drop support Django < 1.8 236 | 237 | [Unreleased]: https://github.com/silentsokolov/django-admin-rangefilter/compare/0.13.2...HEAD 238 | [0.13.2]: https://github.com/silentsokolov/django-admin-rangefilter/compare/v0.13.1...v0.13.2 239 | [0.13.1]: https://github.com/silentsokolov/django-admin-rangefilter/compare/v0.13.0...v0.13.1 240 | [0.13.0]: https://github.com/silentsokolov/django-admin-rangefilter/compare/v0.12.5...v0.13.0 241 | [0.12.5]: https://github.com/silentsokolov/django-admin-rangefilter/compare/v0.12.4...v0.12.5 242 | [0.12.4]: https://github.com/silentsokolov/django-admin-rangefilter/compare/v0.12.3...v0.12.4 243 | [0.12.3]: https://github.com/silentsokolov/django-admin-rangefilter/compare/v0.12.2...v0.12.3 244 | [0.12.2]: https://github.com/silentsokolov/django-admin-rangefilter/compare/v0.12.1...v0.12.2 245 | [0.12.1]: https://github.com/silentsokolov/django-admin-rangefilter/compare/v0.12.0...v0.12.1 246 | [0.12.0]: https://github.com/silentsokolov/django-admin-rangefilter/compare/v0.11.2...v0.12.0 247 | [0.11.2]: https://github.com/silentsokolov/django-admin-rangefilter/compare/v0.11.1...v0.11.2 248 | [0.11.1]: https://github.com/silentsokolov/django-admin-rangefilter/compare/v0.11.0...v0.11.1 249 | [0.11.0]: https://github.com/silentsokolov/django-admin-rangefilter/compare/v0.10.0...v0.11.0 250 | [0.10.0]: https://github.com/silentsokolov/django-admin-rangefilter/compare/v0.9.0...v0.10.0 251 | [0.9.0]: https://github.com/silentsokolov/django-admin-rangefilter/compare/v0.8.8...v0.9.0 252 | [0.8.8]: https://github.com/silentsokolov/django-admin-rangefilter/compare/v0.8.7...v0.8.8 253 | [0.8.7]: https://github.com/silentsokolov/django-admin-rangefilter/compare/v0.8.6...v0.8.7 254 | [0.8.6]: https://github.com/silentsokolov/django-admin-rangefilter/compare/v0.8.5...v0.8.6 255 | [0.8.5]: https://github.com/silentsokolov/django-admin-rangefilter/compare/v0.8.4...v0.8.5 256 | [0.8.4]: https://github.com/silentsokolov/django-admin-rangefilter/compare/v0.8.3...v0.8.4 257 | [0.8.3]: https://github.com/silentsokolov/django-admin-rangefilter/compare/v0.8.2...v0.8.3 258 | [0.8.2]: https://github.com/silentsokolov/django-admin-rangefilter/compare/v0.8.1...v0.8.2 259 | [0.8.1]: https://github.com/silentsokolov/django-admin-rangefilter/compare/v0.8.0...v0.8.1 260 | [0.8.0]: https://github.com/silentsokolov/django-admin-rangefilter/compare/v0.7.0...v0.8.0 261 | [0.7.0]: https://github.com/silentsokolov/django-admin-rangefilter/compare/v0.6.4...v0.7.0 262 | [0.6.4]: https://github.com/silentsokolov/django-admin-rangefilter/compare/v0.6.3...v0.6.4 263 | [0.6.3]: https://github.com/silentsokolov/django-admin-rangefilter/compare/v0.6.2...v0.6.3 264 | [0.6.2]: https://github.com/silentsokolov/django-admin-rangefilter/compare/v0.6.1...v0.6.2 265 | [0.6.1]: https://github.com/silentsokolov/django-admin-rangefilter/compare/v0.6.0...v0.6.1 266 | [0.6.0]: https://github.com/silentsokolov/django-admin-rangefilter/compare/v0.5.4...v0.6.0 267 | [0.5.4]: https://github.com/silentsokolov/django-admin-rangefilter/compare/v0.5.3...v0.5.4 268 | [0.5.3]: https://github.com/silentsokolov/django-admin-rangefilter/compare/v0.5.2...v0.5.3 269 | [0.5.2]: https://github.com/silentsokolov/django-admin-rangefilter/compare/v0.5.1...v0.5.2 270 | [0.5.1]: https://github.com/silentsokolov/django-admin-rangefilter/compare/v0.5.0...v0.5.1 271 | [0.5.0]: https://github.com/silentsokolov/django-admin-rangefilter/compare/v0.4.0...v0.5.0 272 | [0.4.0]: https://github.com/silentsokolov/django-admin-rangefilter/compare/v0.3.16...v0.4.0 273 | [0.3.16]: https://github.com/silentsokolov/django-admin-rangefilter/compare/v0.3.15...v0.3.16 274 | [0.3.15]: https://github.com/silentsokolov/django-admin-rangefilter/compare/v0.3.14...v0.3.15 275 | [0.3.14]: https://github.com/silentsokolov/django-admin-rangefilter/compare/v0.3.13...v0.3.14 276 | [0.3.13]: https://github.com/silentsokolov/django-admin-rangefilter/compare/v0.3.12...v0.3.13 277 | [0.3.12]: https://github.com/silentsokolov/django-admin-rangefilter/compare/v0.3.11...v0.3.12 278 | [0.3.11]: https://github.com/silentsokolov/django-admin-rangefilter/compare/v0.3.10...v0.3.11 279 | [0.3.10]: https://github.com/silentsokolov/django-admin-rangefilter/compare/v0.3.9...v0.3.10 280 | [0.3.9]: https://github.com/silentsokolov/django-admin-rangefilter/compare/v0.3.8...v0.3.9 281 | [0.3.8]: https://github.com/silentsokolov/django-admin-rangefilter/compare/v0.3.7...v0.3.8 282 | [0.3.7]: https://github.com/silentsokolov/django-admin-rangefilter/compare/v0.3.6...v0.3.7 283 | [0.3.6]: https://github.com/silentsokolov/django-admin-rangefilter/compare/v0.3.5...v0.3.6 284 | [0.3.5]: https://github.com/silentsokolov/django-admin-rangefilter/compare/v0.3.4...v0.3.5 285 | [0.3.4]: https://github.com/silentsokolov/django-admin-rangefilter/compare/v0.3.3...v0.3.4 286 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thank you for your interest in contributing to django-admin-rangefilter! 4 | 5 | 6 | ### Add translations 7 | 8 | If you does not know how to translate a Django app, start by reading [Django's translation docs](https://docs.djangoproject.com/en/3.1/topics/i18n/translation/#localization-how-to-create-language-files). -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Dmitriy Sokolov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE CHANGELOG.md README.rst 2 | recursive-include tests/* * 3 | recursive-include rangefilter/static *.js *.css *.png *.eot *.svg *.ttf *.woff 4 | recursive-include rangefilter/templates *.html 5 | recursive-include rangefilter/locale *.mo 6 | global-exclude __pycache__ 7 | global-exclude *.py[co] 8 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: check-black check-isort check-pylint static-analysis test sdist wheel release pre-release clean 2 | 3 | PATH_DADMIN := $(shell which django-admin) 4 | 5 | sdist: 6 | python setup.py sdist 7 | 8 | wheel: 9 | python setup.py bdist_wheel --universal 10 | 11 | release: clean sdist wheel 12 | twine upload dist/* 13 | 14 | pre-release: sdist wheel 15 | twine upload --repository-url https://test.pypi.org/legacy/ dist/* 16 | 17 | clean: 18 | find . | grep -E '(__pycache__|\.pyc|\.pyo$)' | xargs rm -rf 19 | rm -rf build 20 | rm -rf dist 21 | rm -rf *.egg-info 22 | 23 | check-black: 24 | @echo "--> Running black checks" 25 | @black --check --diff . 26 | 27 | check-isort: 28 | @echo "--> Running isort checks" 29 | @isort --check-only . 30 | 31 | check-pylint: 32 | @echo "--> Running pylint checks" 33 | @pylint `git ls-files '*.py'` 34 | 35 | check-yamllint: 36 | @echo "--> Running yamllint checks" 37 | @yamllint . 38 | 39 | lint: check-black check-isort check-pylint check-yamllint 40 | 41 | # Format code 42 | .PHONY: fmt 43 | 44 | fmt: 45 | @echo "--> Running isort" 46 | @isort . 47 | @echo "--> Running black" 48 | @black . 49 | 50 | # Test 51 | .PHONY: test 52 | 53 | test: 54 | @echo "--> Running tests" 55 | PYTHONWARNINGS=all PYTHONPATH=".:tests:${PYTHONPATH}" django-admin test --settings=tests.settings 56 | 57 | coverage: 58 | @echo "--> Running coverage" 59 | PYTHONWARNINGS=all PYTHONPATH=".:tests:${PYTHONPATH}" coverage run --source='.' $(PATH_DADMIN) test --settings=tests.settings 60 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | .. image:: https://github.com/silentsokolov/django-admin-rangefilter/workflows/build/badge.svg?branch=master 2 | :target: https://github.com/silentsokolov/django-admin-rangefilter/actions?query=workflow%3Abuild 3 | 4 | .. image:: https://codecov.io/gh/silentsokolov/django-admin-rangefilter/branch/master/graph/badge.svg 5 | :target: https://codecov.io/gh/silentsokolov/django-admin-rangefilter 6 | 7 | django-admin-rangefilter 8 | ======================== 9 | 10 | A Django app that adds a filter by date range and numeric range to the admin UI. 11 | 12 | .. image:: https://raw.githubusercontent.com/silentsokolov/django-admin-rangefilter/master/docs/images/screenshot.png 13 | 14 | 15 | Requirements 16 | ------------ 17 | 18 | * Python 3.6+ 19 | * Django 1.11+ 20 | 21 | 22 | Installation 23 | ------------ 24 | 25 | Use your favorite Python package manager to install the app from PyPI, e.g. 26 | 27 | Example: 28 | 29 | ``pip install django-admin-rangefilter`` 30 | 31 | 32 | Add ``rangefilter`` to ``INSTALLED_APPS``: 33 | 34 | Example: 35 | 36 | .. code:: python 37 | 38 | INSTALLED_APPS = ( 39 | ... 40 | 'rangefilter', 41 | ... 42 | ) 43 | 44 | 45 | Example usage 46 | ------------- 47 | 48 | In admin 49 | ~~~~~~~~ 50 | 51 | .. code:: python 52 | 53 | from datetime import datetime 54 | 55 | from django.contrib import admin 56 | from rangefilter.filters import ( 57 | DateRangeFilterBuilder, 58 | DateTimeRangeFilterBuilder, 59 | NumericRangeFilterBuilder, 60 | DateRangeQuickSelectListFilterBuilder, 61 | ) 62 | 63 | from .models import Post 64 | 65 | 66 | @admin.register(Post) 67 | class PostAdmin(admin.ModelAdmin): 68 | list_filter = ( 69 | ("created_at", DateRangeFilterBuilder()), 70 | ( 71 | "updated_at", 72 | DateTimeRangeFilterBuilder( 73 | title="Custom title", 74 | default_start=datetime(2020, 1, 1), 75 | default_end=datetime(2030, 1, 1), 76 | ), 77 | ), 78 | ("num_value", NumericRangeFilterBuilder()), 79 | ("created_at", DateRangeQuickSelectListFilterBuilder()), # Range + QuickSelect Filter 80 | ) 81 | 82 | 83 | Support Content-Security-Policy 84 | ------------------------------- 85 | 86 | For Django 1.8+, if `django-csp `_ is installed, nonces will be added to style and script tags. 87 | -------------------------------------------------------------------------------- /docs/images/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silentsokolov/django-admin-rangefilter/50c04fa9cb3d3254a482b0dde224c9c22845af28/docs/images/screenshot.png -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.black] 2 | line-length = 100 3 | target-version = ['py36', 'py38', 'py39', 'py310', 'py311', 'py312'] 4 | exclude = '(\.eggs|\.git|\.mypy_cache|\.venv|venv|env|_build|build|build|dist|eggs)' 5 | 6 | [tool.isort] 7 | line_length = 100 8 | profile = "black" 9 | use_parentheses = true 10 | skip = '.eggs/,.mypy_cache/,.venv/,venv/,env/,eggs/' 11 | 12 | [tool.pylint] 13 | [tool.pylint.master] 14 | py-version = 3.9 15 | 16 | [tool.pylint.messages-control] 17 | disable=[ 18 | 'C', 19 | 'R', 20 | 21 | # Redundant with mypy 22 | 'typecheck', 23 | 24 | # There are many places where we want to catch a maximally generic exception. 25 | 'bare-except', 26 | 'broad-except', 27 | 28 | # Pylint is by default very strict on logging string interpolations, but the 29 | # (performance-motivated) rules do not make sense for infrequent log messages (like error reports) 30 | # and make messages less readable. 31 | 'logging-fstring-interpolation', 32 | 'logging-format-interpolation', 33 | 'logging-not-lazy', 34 | 'too-many-arguments', 35 | 'duplicate-code', 36 | ] 37 | 38 | [tool.ruff] 39 | exclude = [ 40 | '.git', 41 | '.eggs', 42 | '.mypy_cache', 43 | '.ruff_cache', 44 | '.venv', 45 | 'venv', 46 | 'dist', 47 | '_build', 48 | 'build', 49 | ] 50 | 51 | line-length = 100 52 | target-version = "py311" 53 | 54 | [tool.ruff.format] 55 | indent-style = "space" 56 | docstring-code-format = true 57 | -------------------------------------------------------------------------------- /rangefilter/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import django 4 | 5 | __author__ = "Dmitriy Sokolov" 6 | __version__ = "0.13.2" 7 | 8 | if django.VERSION < (3, 2): 9 | default_app_config = "rangefilter.apps.RangeFilterConfig" 10 | 11 | 12 | VERSION = __version__ 13 | -------------------------------------------------------------------------------- /rangefilter/apps.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import django 4 | from django.apps import AppConfig 5 | 6 | if django.VERSION >= (2, 0, 0): 7 | from django.utils.translation import gettext_lazy as _ 8 | else: 9 | from django.utils.translation import ugettext_lazy as _ # pylint: disable=E0611 10 | 11 | 12 | class RangeFilterConfig(AppConfig): 13 | name = "rangefilter" 14 | verbose_name = _("Range Filter") 15 | -------------------------------------------------------------------------------- /rangefilter/filters.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import datetime 4 | 5 | import django 6 | 7 | try: 8 | import pytz 9 | except ImportError: 10 | pytz = None 11 | 12 | from collections import OrderedDict 13 | 14 | from django import forms 15 | from django.conf import settings 16 | from django.contrib import admin 17 | from django.contrib.admin.widgets import AdminDateWidget 18 | from django.contrib.admin.widgets import AdminSplitDateTime as BaseAdminSplitDateTime 19 | from django.template.defaultfilters import slugify 20 | from django.templatetags.static import StaticNode 21 | from django.utils import timezone 22 | from django.utils.encoding import force_str 23 | from django.utils.html import format_html 24 | 25 | if django.VERSION >= (2, 0, 0): 26 | from django.utils.translation import gettext_lazy as _ 27 | else: 28 | from django.utils.translation import ugettext_lazy as _ # pylint: disable=E0611 29 | 30 | USE_END_MICROSECOND = getattr(settings, "RANGEFILTERS_USE_FILTER_END_MICROSECOND", False) 31 | 32 | 33 | class OnceCallMedia(object): 34 | _is_rendered = False 35 | 36 | def __str__(self): 37 | return str([str(s) for s in self._js]) 38 | 39 | def __repr__(self): 40 | return "OnceCallMedia(js=%r)" % ([str(s) for s in self._js]) 41 | 42 | def __call__(self): 43 | if self._is_rendered: 44 | return [] 45 | 46 | self._is_rendered = True 47 | return self._js 48 | 49 | def get_js(self): 50 | return [ 51 | StaticNode.handle_simple("admin/js/calendar.js"), 52 | StaticNode.handle_simple("admin/js/admin/DateTimeShortcuts.js"), 53 | ] 54 | 55 | _js = property(get_js) 56 | 57 | 58 | class AdminSplitDateTime(BaseAdminSplitDateTime): 59 | def format_output(self, rendered_widgets): 60 | return format_html( 61 | '

{}

{}

', 62 | rendered_widgets[0], 63 | rendered_widgets[1], 64 | ) 65 | 66 | 67 | class BaseRangeFilter(admin.filters.FieldListFilter): # pylint: disable=abstract-method 68 | def __init__(self, field, request, params, model, model_admin, field_path): 69 | self.lookup_kwarg_gte = "{0}__range__gte".format(field_path) 70 | self.lookup_kwarg_lte = "{0}__range__lte".format(field_path) 71 | 72 | super(BaseRangeFilter, self).__init__( 73 | field, request, params, model, model_admin, field_path 74 | ) 75 | 76 | self.default_gte, self.default_lte = self._get_default_values( 77 | request, model_admin, field_path 78 | ) 79 | self.title = self._get_default_title(request, model_admin, field_path) 80 | 81 | self.request = request 82 | self.model_admin = model_admin 83 | self.form = self.get_form(request) 84 | 85 | def _get_default_title(self, request, model_admin, field_path): 86 | if hasattr(self, "__from_builder"): 87 | return self.default_title or self.title 88 | 89 | title_method_name = "get_rangefilter_{0}_title".format(field_path) 90 | title_method = getattr(model_admin, title_method_name, None) 91 | 92 | if not callable(title_method): 93 | return self.title 94 | 95 | return title_method(request, field_path) 96 | 97 | def _get_default_values(self, request, model_admin, field_path): 98 | if hasattr(self, "__from_builder"): 99 | return self.default_start, self.default_end 100 | 101 | default_method_name = "get_rangefilter_{0}_default".format(field_path) 102 | default_method = getattr(model_admin, default_method_name, None) 103 | 104 | if not callable(default_method): 105 | return None, None 106 | 107 | return default_method(request) 108 | 109 | def get_form(self, _request): 110 | raise NotImplementedError() 111 | 112 | 113 | class DateRangeFilter(BaseRangeFilter): 114 | _request_key = "DJANGO_RANGEFILTER_ADMIN_JS_LIST" 115 | 116 | def choices(self, changelist): 117 | yield { 118 | # slugify converts any non-unicode characters to empty characters 119 | # but system_name is required, if title converts to empty string use id 120 | # https://github.com/silentsokolov/django-admin-rangefilter/issues/18 121 | "system_name": force_str( 122 | slugify(self.title) if slugify(self.title) else id(self.title) 123 | ), 124 | "query_string": changelist.get_query_string({}, remove=self.expected_parameters()), 125 | } 126 | 127 | def queryset(self, request, queryset): 128 | if self.form.is_valid(): 129 | validated_data = dict(self.form.cleaned_data.items()) 130 | if validated_data: 131 | return queryset.filter(**self._make_query_filter(request, validated_data)) 132 | return queryset 133 | 134 | def expected_parameters(self): 135 | return [self.lookup_kwarg_gte, self.lookup_kwarg_lte] 136 | 137 | def get_facet_counts(self, pk_attname, filtered_qs): 138 | return {} 139 | 140 | def get_template(self): 141 | if django.VERSION[:2] <= (1, 8): 142 | return "rangefilter/date_filter_1_8.html" 143 | 144 | if django.VERSION[:2] <= (4, 0): 145 | return "rangefilter/date_filter_4_0.html" 146 | 147 | return "rangefilter/date_filter.html" 148 | 149 | template = property(get_template) 150 | 151 | def get_form(self, _request): 152 | form_class = self._get_form_class() 153 | 154 | if django.VERSION[:2] >= (5, 0): 155 | for name, value in self.used_parameters.items(): 156 | if isinstance(value, list): 157 | self.used_parameters[name] = value[-1] 158 | 159 | return form_class(self.used_parameters or None) 160 | 161 | def get_timezone(self, _request): 162 | return timezone.get_current_timezone() 163 | 164 | @staticmethod 165 | def make_dt_aware(value, tzname): 166 | if settings.USE_TZ: 167 | if django.VERSION <= (4, 0, 0) and pytz is not None: 168 | default_tz = tzname 169 | if value.tzinfo is not None: 170 | value = default_tz.normalize(value) 171 | else: 172 | value = default_tz.localize(value) 173 | else: 174 | value = value.replace(tzinfo=tzname) 175 | return value 176 | 177 | def _make_query_filter(self, request, validated_data): 178 | query_params = {} 179 | date_value_gte = validated_data.get(self.lookup_kwarg_gte, None) 180 | date_value_lte = validated_data.get(self.lookup_kwarg_lte, None) 181 | 182 | if date_value_gte: 183 | query_params["{0}__gte".format(self.field_path)] = self.make_dt_aware( 184 | datetime.datetime.combine(date_value_gte, datetime.time.min), 185 | self.get_timezone(request), 186 | ) 187 | if date_value_lte: 188 | query_params["{0}__lte".format(self.field_path)] = self.make_dt_aware( 189 | datetime.datetime.combine(date_value_lte, datetime.time.max), 190 | self.get_timezone(request), 191 | ) 192 | 193 | return query_params 194 | 195 | def _get_form_fields(self): 196 | return OrderedDict( 197 | ( 198 | ( 199 | self.lookup_kwarg_gte, 200 | forms.DateField( 201 | label="", 202 | widget=AdminDateWidget(attrs={"placeholder": _("From date")}), 203 | localize=True, 204 | required=False, 205 | initial=self.default_gte, 206 | ), 207 | ), 208 | ( 209 | self.lookup_kwarg_lte, 210 | forms.DateField( 211 | label="", 212 | widget=AdminDateWidget(attrs={"placeholder": _("To date")}), 213 | localize=True, 214 | required=False, 215 | initial=self.default_lte, 216 | ), 217 | ), 218 | ) 219 | ) 220 | 221 | def _get_form_class(self): 222 | fields = self._get_form_fields() 223 | 224 | form_class = type(str("DateRangeForm"), (forms.BaseForm,), {"base_fields": fields}) 225 | 226 | # lines below ensure that the js static files are loaded just once 227 | # even if there is more than one DateRangeFilter in use 228 | js_list = getattr(self.request, self._request_key, None) 229 | if not js_list: 230 | js_list = OnceCallMedia() 231 | setattr(self.request, self._request_key, js_list) 232 | 233 | form_class.js = js_list 234 | 235 | return form_class 236 | 237 | 238 | class DateTimeRangeFilter(DateRangeFilter): 239 | def expected_parameters(self): 240 | expected_fields = [] 241 | for field in [self.lookup_kwarg_gte, self.lookup_kwarg_lte]: 242 | for i in range(2): 243 | expected_fields.append("{}_{}".format(field, i)) 244 | 245 | return expected_fields 246 | 247 | def _get_form_fields(self): 248 | return OrderedDict( 249 | ( 250 | ( 251 | self.lookup_kwarg_gte, 252 | forms.SplitDateTimeField( 253 | label="", 254 | widget=AdminSplitDateTime(attrs={"placeholder": _("From date")}), 255 | localize=True, 256 | required=False, 257 | initial=self.default_gte, 258 | ), 259 | ), 260 | ( 261 | self.lookup_kwarg_lte, 262 | forms.SplitDateTimeField( 263 | label="", 264 | widget=AdminSplitDateTime(attrs={"placeholder": _("To date")}), 265 | localize=True, 266 | required=False, 267 | initial=self.default_lte, 268 | ), 269 | ), 270 | ) 271 | ) 272 | 273 | def _make_query_filter(self, request, validated_data): 274 | query_params = {} 275 | date_value_gte = validated_data.get(self.lookup_kwarg_gte, None) 276 | date_value_lte = validated_data.get(self.lookup_kwarg_lte, None) 277 | 278 | if date_value_gte: 279 | query_params["{0}__gte".format(self.field_path)] = self.make_dt_aware( 280 | date_value_gte, self.get_timezone(request) 281 | ) 282 | if date_value_lte: 283 | end_dt = self.make_dt_aware(date_value_lte, self.get_timezone(request)) 284 | if USE_END_MICROSECOND: 285 | end_dt = end_dt.replace(microsecond=999999) 286 | 287 | query_params["{0}__lte".format(self.field_path)] = end_dt 288 | 289 | return query_params 290 | 291 | 292 | class NumericRangeFilter(BaseRangeFilter): 293 | def choices(self, changelist): 294 | yield { 295 | "system_name": force_str( 296 | slugify(self.title) if slugify(self.title) else id(self.title) 297 | ), 298 | "query_string": changelist.get_query_string({}, remove=self.expected_parameters()), 299 | } 300 | 301 | def queryset(self, request, queryset): 302 | if self.form.is_valid(): 303 | validated_data = dict(self.form.cleaned_data.items()) 304 | if validated_data: 305 | return queryset.filter(**self._make_query_filter(request, validated_data)) 306 | return queryset 307 | 308 | def expected_parameters(self): 309 | return [self.lookup_kwarg_gte, self.lookup_kwarg_lte] 310 | 311 | def get_facet_counts(self, pk_attname, filtered_qs): 312 | return {} 313 | 314 | def get_template(self): 315 | if django.VERSION[:2] <= (4, 0): 316 | return "rangefilter/numeric_filter_4_0.html" 317 | 318 | return "rangefilter/numeric_filter.html" 319 | 320 | template = property(get_template) 321 | 322 | def get_form(self, _request): 323 | form_class = self._get_form_class() 324 | 325 | if django.VERSION[:2] >= (5, 0): 326 | for name, value in self.used_parameters.items(): 327 | if isinstance(value, list): 328 | self.used_parameters[name] = value[-1] 329 | 330 | return form_class(self.used_parameters or None) 331 | 332 | def _get_form_fields(self): 333 | return OrderedDict( 334 | ( 335 | ( 336 | self.lookup_kwarg_gte, 337 | forms.FloatField( 338 | label="", 339 | widget=forms.NumberInput(attrs={"placeholder": _("From")}), 340 | required=False, 341 | localize=True, 342 | initial=self.default_gte, 343 | ), 344 | ), 345 | ( 346 | self.lookup_kwarg_lte, 347 | forms.FloatField( 348 | label="", 349 | widget=forms.NumberInput(attrs={"placeholder": _("To")}), 350 | localize=True, 351 | required=False, 352 | initial=self.default_lte, 353 | ), 354 | ), 355 | ) 356 | ) 357 | 358 | def _get_form_class(self): 359 | fields = self._get_form_fields() 360 | 361 | form_class = type(str("NumericRangeFilter"), (forms.BaseForm,), {"base_fields": fields}) 362 | 363 | return form_class 364 | 365 | def _make_query_filter(self, _request, validated_data): 366 | query_params = {} 367 | value_gte = validated_data.get(self.lookup_kwarg_gte, None) 368 | value_lte = validated_data.get(self.lookup_kwarg_lte, None) 369 | 370 | if value_gte is not None: 371 | query_params["{0}__gte".format(self.field_path)] = value_gte 372 | if value_lte is not None: 373 | query_params["{0}__lte".format(self.field_path)] = value_lte 374 | 375 | return query_params 376 | 377 | 378 | class DateRangeQuickSelectListFilter(admin.DateFieldListFilter, DateRangeFilter): 379 | def expected_parameters(self): 380 | params = [self.lookup_kwarg_gte, self.lookup_kwarg_lte] 381 | if self.field.null: 382 | params.append(self.lookup_kwarg_isnull) 383 | return params 384 | 385 | def get_template(self): 386 | return "rangefilter/date_range_quick_select_list_filter.html" 387 | 388 | template = property(get_template) 389 | 390 | def _make_query_filter(self, request, validated_data): 391 | query_params = super()._make_query_filter(request, validated_data) 392 | date_value_gte = validated_data.get(self.lookup_kwarg_gte, None) 393 | date_value_lte = validated_data.get(self.lookup_kwarg_lte, None) 394 | if self.field.null: 395 | date_value_isnull = validated_data.get(self.lookup_kwarg_isnull, None) 396 | 397 | if date_value_isnull is not None and not any([date_value_lte, date_value_gte]): 398 | query_params[self.lookup_kwarg_isnull] = date_value_isnull 399 | 400 | return query_params 401 | 402 | def _get_form_fields(self): 403 | fields = super()._get_form_fields() 404 | if self.field.null: 405 | fields.update( 406 | OrderedDict( 407 | ( 408 | ( 409 | self.lookup_kwarg_isnull, 410 | forms.BooleanField( 411 | label="", 412 | localize=True, 413 | required=False, 414 | widget=forms.HiddenInput, 415 | ), 416 | ), 417 | ) 418 | ) 419 | ) 420 | return fields 421 | 422 | 423 | def DateRangeFilterBuilder(title=None, default_start=None, default_end=None): 424 | filter_cls = type( 425 | str("DateRangeFilter"), 426 | (DateRangeFilter,), 427 | { 428 | "__from_builder": True, 429 | "default_title": title, 430 | "default_start": default_start, 431 | "default_end": default_end, 432 | }, 433 | ) 434 | 435 | return filter_cls 436 | 437 | 438 | def DateTimeRangeFilterBuilder(title=None, default_start=None, default_end=None): 439 | filter_cls = type( 440 | str("DateTimeRangeFilter"), 441 | (DateTimeRangeFilter,), 442 | { 443 | "__from_builder": True, 444 | "default_title": title, 445 | "default_start": default_start, 446 | "default_end": default_end, 447 | }, 448 | ) 449 | 450 | return filter_cls 451 | 452 | 453 | def NumericRangeFilterBuilder(title=None, default_start=None, default_end=None): 454 | filter_cls = type( 455 | str("NumericRangeFilter"), 456 | (NumericRangeFilter,), 457 | { 458 | "__from_builder": True, 459 | "default_title": title, 460 | "default_start": default_start, 461 | "default_end": default_end, 462 | }, 463 | ) 464 | 465 | return filter_cls 466 | 467 | 468 | def DateRangeQuickSelectListFilterBuilder(title=None, default_start=None, default_end=None): 469 | filter_cls = type( 470 | str("DateRangeQuickSelectListFilter"), 471 | (DateRangeQuickSelectListFilter,), 472 | { 473 | "__from_builder": True, 474 | "default_title": title, 475 | "default_start": default_start, 476 | "default_end": default_end, 477 | }, 478 | ) 479 | 480 | return filter_cls 481 | -------------------------------------------------------------------------------- /rangefilter/locale/cs_CZ/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silentsokolov/django-admin-rangefilter/50c04fa9cb3d3254a482b0dde224c9c22845af28/rangefilter/locale/cs_CZ/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /rangefilter/locale/cs_CZ/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | # Translators: 7 | # Michal S , 2019 8 | # 9 | #, fuzzy 10 | msgid "" 11 | msgstr "" 12 | "Project-Id-Version: PACKAGE VERSION\n" 13 | "Report-Msgid-Bugs-To: \n" 14 | "POT-Creation-Date: 2022-09-25 09:48+0300\n" 15 | "PO-Revision-Date: 2019-01-31 09:16+0000\n" 16 | "Last-Translator: Michal S , 2019\n" 17 | "Language-Team: Czech (Czech Republic) (https://www.transifex.com/django-" 18 | "admin-rangefilter/teams/95736/cs_CZ/)\n" 19 | "Language: cs_CZ\n" 20 | "MIME-Version: 1.0\n" 21 | "Content-Type: text/plain; charset=UTF-8\n" 22 | "Content-Transfer-Encoding: 8bit\n" 23 | "Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n " 24 | "<= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;\n" 25 | 26 | #: apps.py:16 27 | msgid "Range Filter" 28 | msgstr "Filtr rozsahu" 29 | 30 | #: filters.py:210 filters.py:246 31 | msgid "From date" 32 | msgstr "Od data" 33 | 34 | #: filters.py:220 filters.py:256 35 | msgid "To date" 36 | msgstr "Do data" 37 | 38 | #: filters.py:351 39 | msgid "From" 40 | msgstr "Od" 41 | 42 | #: filters.py:361 43 | msgid "To" 44 | msgstr "Do" 45 | 46 | #: templates/rangefilter/date_filter.html:139 47 | #: templates/rangefilter/date_filter_1_8.html:90 48 | #: templates/rangefilter/date_filter_csp.html:138 49 | #: templates/rangefilter/numeric_filter.html:55 50 | msgid "Search" 51 | msgstr "Vyhledat" 52 | 53 | #: templates/rangefilter/date_filter.html:140 54 | #: templates/rangefilter/date_filter_1_8.html:91 55 | #: templates/rangefilter/date_filter_csp.html:139 56 | #: templates/rangefilter/numeric_filter.html:56 57 | msgid "Reset" 58 | msgstr "Resetovat" 59 | -------------------------------------------------------------------------------- /rangefilter/locale/da/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silentsokolov/django-admin-rangefilter/50c04fa9cb3d3254a482b0dde224c9c22845af28/rangefilter/locale/da/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /rangefilter/locale/da/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | # Translators: 7 | # Dmitriy Sokolov , 2020 8 | # 9 | #, fuzzy 10 | msgid "" 11 | msgstr "" 12 | "Project-Id-Version: PACKAGE VERSION\n" 13 | "Report-Msgid-Bugs-To: \n" 14 | "POT-Creation-Date: 2022-09-25 09:48+0300\n" 15 | "PO-Revision-Date: 2019-01-31 09:16+0000\n" 16 | "Last-Translator: Dmitriy Sokolov , 2020\n" 17 | "Language-Team: Danish (https://www.transifex.com/django-admin-rangefilter/" 18 | "teams/95736/da/)\n" 19 | "Language: da\n" 20 | "MIME-Version: 1.0\n" 21 | "Content-Type: text/plain; charset=UTF-8\n" 22 | "Content-Transfer-Encoding: 8bit\n" 23 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 24 | 25 | #: apps.py:16 26 | msgid "Range Filter" 27 | msgstr "Datofilter" 28 | 29 | #: filters.py:210 filters.py:246 30 | msgid "From date" 31 | msgstr "Fra dato" 32 | 33 | #: filters.py:220 filters.py:256 34 | msgid "To date" 35 | msgstr "Til dato" 36 | 37 | #: filters.py:351 38 | msgid "From" 39 | msgstr "Fra" 40 | 41 | #: filters.py:361 42 | msgid "To" 43 | msgstr "Til" 44 | 45 | #: templates/rangefilter/date_filter.html:139 46 | #: templates/rangefilter/date_filter_1_8.html:90 47 | #: templates/rangefilter/date_filter_csp.html:138 48 | #: templates/rangefilter/numeric_filter.html:55 49 | msgid "Search" 50 | msgstr "Søg" 51 | 52 | #: templates/rangefilter/date_filter.html:140 53 | #: templates/rangefilter/date_filter_1_8.html:91 54 | #: templates/rangefilter/date_filter_csp.html:139 55 | #: templates/rangefilter/numeric_filter.html:56 56 | msgid "Reset" 57 | msgstr "Nulstil" 58 | -------------------------------------------------------------------------------- /rangefilter/locale/de/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silentsokolov/django-admin-rangefilter/50c04fa9cb3d3254a482b0dde224c9c22845af28/rangefilter/locale/de/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /rangefilter/locale/de/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | # Translators: 7 | # Fabien Schwob , 2019 8 | # 9 | #, fuzzy 10 | msgid "" 11 | msgstr "" 12 | "Project-Id-Version: PACKAGE VERSION\n" 13 | "Report-Msgid-Bugs-To: \n" 14 | "POT-Creation-Date: 2022-09-25 09:48+0300\n" 15 | "PO-Revision-Date: 2019-01-31 09:16+0000\n" 16 | "Last-Translator: Fabien Schwob , 2019\n" 17 | "Language-Team: German (https://www.transifex.com/django-admin-rangefilter/" 18 | "teams/95736/de/)\n" 19 | "Language: de\n" 20 | "MIME-Version: 1.0\n" 21 | "Content-Type: text/plain; charset=UTF-8\n" 22 | "Content-Transfer-Encoding: 8bit\n" 23 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 24 | 25 | #: apps.py:16 26 | msgid "Range Filter" 27 | msgstr "Bereichsfilter" 28 | 29 | #: filters.py:210 filters.py:246 30 | msgid "From date" 31 | msgstr "Von" 32 | 33 | #: filters.py:220 filters.py:256 34 | msgid "To date" 35 | msgstr "Bis" 36 | 37 | #: filters.py:351 38 | msgid "From" 39 | msgstr "Von" 40 | 41 | #: filters.py:361 42 | msgid "To" 43 | msgstr "Bis" 44 | 45 | #: templates/rangefilter/date_filter.html:139 46 | #: templates/rangefilter/date_filter_1_8.html:90 47 | #: templates/rangefilter/date_filter_csp.html:138 48 | #: templates/rangefilter/numeric_filter.html:55 49 | msgid "Search" 50 | msgstr "Suchen" 51 | 52 | #: templates/rangefilter/date_filter.html:140 53 | #: templates/rangefilter/date_filter_1_8.html:91 54 | #: templates/rangefilter/date_filter_csp.html:139 55 | #: templates/rangefilter/numeric_filter.html:56 56 | msgid "Reset" 57 | msgstr "Zurücksetzen" 58 | -------------------------------------------------------------------------------- /rangefilter/locale/el/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silentsokolov/django-admin-rangefilter/50c04fa9cb3d3254a482b0dde224c9c22845af28/rangefilter/locale/el/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /rangefilter/locale/el/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | # Translators: 7 | # Dimitris Karagiannis , 2021 8 | # 9 | #, fuzzy 10 | msgid "" 11 | msgstr "" 12 | "Project-Id-Version: PACKAGE VERSION\n" 13 | "Report-Msgid-Bugs-To: \n" 14 | "POT-Creation-Date: 2022-09-25 09:48+0300\n" 15 | "PO-Revision-Date: 2019-01-31 09:16+0000\n" 16 | "Last-Translator: Dimitris Karagiannis , 2021\n" 17 | "Language-Team: Greek (https://www.transifex.com/django-admin-rangefilter/" 18 | "teams/95736/el/)\n" 19 | "Language: el\n" 20 | "MIME-Version: 1.0\n" 21 | "Content-Type: text/plain; charset=UTF-8\n" 22 | "Content-Transfer-Encoding: 8bit\n" 23 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 24 | 25 | #: apps.py:16 26 | msgid "Range Filter" 27 | msgstr "Φίλτρο εύρους" 28 | 29 | #: filters.py:210 filters.py:246 30 | msgid "From date" 31 | msgstr "Από" 32 | 33 | #: filters.py:220 filters.py:256 34 | msgid "To date" 35 | msgstr "Εως" 36 | 37 | #: filters.py:351 38 | msgid "From" 39 | msgstr "Από" 40 | 41 | #: filters.py:361 42 | msgid "To" 43 | msgstr "Εως" 44 | 45 | #: templates/rangefilter/date_filter.html:139 46 | #: templates/rangefilter/date_filter_1_8.html:90 47 | #: templates/rangefilter/date_filter_csp.html:138 48 | #: templates/rangefilter/numeric_filter.html:55 49 | msgid "Search" 50 | msgstr "Αναζήτηση" 51 | 52 | #: templates/rangefilter/date_filter.html:140 53 | #: templates/rangefilter/date_filter_1_8.html:91 54 | #: templates/rangefilter/date_filter_csp.html:139 55 | #: templates/rangefilter/numeric_filter.html:56 56 | msgid "Reset" 57 | msgstr "Επαναφορά" 58 | -------------------------------------------------------------------------------- /rangefilter/locale/en_US/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silentsokolov/django-admin-rangefilter/50c04fa9cb3d3254a482b0dde224c9c22845af28/rangefilter/locale/en_US/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /rangefilter/locale/en_US/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: PACKAGE VERSION\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2022-09-25 09:48+0300\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \n" 15 | "Language: \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | 20 | #: apps.py:16 21 | msgid "Range Filter" 22 | msgstr "Range Filter" 23 | 24 | #: filters.py:210 filters.py:246 25 | msgid "From date" 26 | msgstr "From date" 27 | 28 | #: filters.py:220 filters.py:256 29 | msgid "To date" 30 | msgstr "To date" 31 | 32 | #: filters.py:351 33 | msgid "From" 34 | msgstr "From" 35 | 36 | #: filters.py:361 37 | msgid "To" 38 | msgstr "To" 39 | 40 | #: templates/rangefilter/date_filter.html:139 41 | #: templates/rangefilter/date_filter_1_8.html:90 42 | #: templates/rangefilter/date_filter_csp.html:138 43 | #: templates/rangefilter/numeric_filter.html:55 44 | msgid "Search" 45 | msgstr "Search" 46 | 47 | #: templates/rangefilter/date_filter.html:140 48 | #: templates/rangefilter/date_filter_1_8.html:91 49 | #: templates/rangefilter/date_filter_csp.html:139 50 | #: templates/rangefilter/numeric_filter.html:56 51 | msgid "Reset" 52 | msgstr "Reset" 53 | -------------------------------------------------------------------------------- /rangefilter/locale/es/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silentsokolov/django-admin-rangefilter/50c04fa9cb3d3254a482b0dde224c9c22845af28/rangefilter/locale/es/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /rangefilter/locale/es/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: PACKAGE VERSION\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2022-09-25 09:48+0300\n" 12 | "PO-Revision-Date: 2019-01-31 09:16+0000\n" 13 | "Last-Translator: Dmitriy Sokolov , 2019\n" 14 | "Language-Team: Spanish (https://www.transifex.com/django-admin-rangefilter/" 15 | "teams/95736/es/)\n" 16 | "Language: es\n" 17 | "MIME-Version: 1.0\n" 18 | "Content-Type: text/plain; charset=UTF-8\n" 19 | "Content-Transfer-Encoding: 8bit\n" 20 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 21 | 22 | #: apps.py:16 23 | msgid "Range Filter" 24 | msgstr "Filtro de Rango" 25 | 26 | #: filters.py:210 filters.py:246 27 | msgid "From date" 28 | msgstr "Desde" 29 | 30 | #: filters.py:220 filters.py:256 31 | msgid "To date" 32 | msgstr "Hasta" 33 | 34 | #: filters.py:351 35 | msgid "From" 36 | msgstr "Desde" 37 | 38 | #: filters.py:361 39 | msgid "To" 40 | msgstr "Hasta" 41 | 42 | #: templates/rangefilter/date_filter.html:139 43 | #: templates/rangefilter/date_filter_1_8.html:90 44 | #: templates/rangefilter/date_filter_csp.html:138 45 | #: templates/rangefilter/numeric_filter.html:55 46 | msgid "Search" 47 | msgstr "Buscar" 48 | 49 | #: templates/rangefilter/date_filter.html:140 50 | #: templates/rangefilter/date_filter_1_8.html:91 51 | #: templates/rangefilter/date_filter_csp.html:139 52 | #: templates/rangefilter/numeric_filter.html:56 53 | msgid "Reset" 54 | msgstr "Reiniciar" 55 | -------------------------------------------------------------------------------- /rangefilter/locale/es_AR/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silentsokolov/django-admin-rangefilter/50c04fa9cb3d3254a482b0dde224c9c22845af28/rangefilter/locale/es_AR/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /rangefilter/locale/es_AR/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: PACKAGE VERSION\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2022-09-25 09:48+0300\n" 12 | "PO-Revision-Date: 2019-01-31 09:16+0000\n" 13 | "Last-Translator: Dmitriy Sokolov , 2019\n" 14 | "Language-Team: Spanish (Argentina) (https://www.transifex.com/django-admin-" 15 | "rangefilter/teams/95736/es_AR/)\n" 16 | "Language: es_AR\n" 17 | "MIME-Version: 1.0\n" 18 | "Content-Type: text/plain; charset=UTF-8\n" 19 | "Content-Transfer-Encoding: 8bit\n" 20 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 21 | 22 | #: apps.py:16 23 | msgid "Range Filter" 24 | msgstr "Seleccionar Rango" 25 | 26 | #: filters.py:210 filters.py:246 27 | msgid "From date" 28 | msgstr "Desde" 29 | 30 | #: filters.py:220 filters.py:256 31 | msgid "To date" 32 | msgstr "Hasta" 33 | 34 | #: filters.py:351 35 | msgid "From" 36 | msgstr "Desde" 37 | 38 | #: filters.py:361 39 | msgid "To" 40 | msgstr "Hasta" 41 | 42 | #: templates/rangefilter/date_filter.html:139 43 | #: templates/rangefilter/date_filter_1_8.html:90 44 | #: templates/rangefilter/date_filter_csp.html:138 45 | #: templates/rangefilter/numeric_filter.html:55 46 | msgid "Search" 47 | msgstr "Aplicar filtros" 48 | 49 | #: templates/rangefilter/date_filter.html:140 50 | #: templates/rangefilter/date_filter_1_8.html:91 51 | #: templates/rangefilter/date_filter_csp.html:139 52 | #: templates/rangefilter/numeric_filter.html:56 53 | msgid "Reset" 54 | msgstr "Quitar filtros" 55 | -------------------------------------------------------------------------------- /rangefilter/locale/fa/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silentsokolov/django-admin-rangefilter/50c04fa9cb3d3254a482b0dde224c9c22845af28/rangefilter/locale/fa/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /rangefilter/locale/fa/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: PACKAGE VERSION\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2022-09-25 09:48+0300\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: Mahdi Shahmohammadi \n" 14 | "Language-Team: Iran \n" 15 | "Language: \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Plural-Forms: nplurals=2; plural=(n > 1);\n" 20 | 21 | #: apps.py:16 22 | msgid "Range Filter" 23 | msgstr "محدوده فیلتر" 24 | 25 | #: filters.py:210 filters.py:246 26 | msgid "From date" 27 | msgstr "از روز" 28 | 29 | #: filters.py:220 filters.py:256 30 | msgid "To date" 31 | msgstr "تا روز" 32 | 33 | #: filters.py:351 34 | msgid "From" 35 | msgstr "" 36 | 37 | #: filters.py:361 38 | msgid "To" 39 | msgstr "" 40 | 41 | #: templates/rangefilter/date_filter.html:139 42 | #: templates/rangefilter/date_filter_1_8.html:90 43 | #: templates/rangefilter/date_filter_csp.html:138 44 | #: templates/rangefilter/numeric_filter.html:55 45 | msgid "Search" 46 | msgstr "جست و جو" 47 | 48 | #: templates/rangefilter/date_filter.html:140 49 | #: templates/rangefilter/date_filter_1_8.html:91 50 | #: templates/rangefilter/date_filter_csp.html:139 51 | #: templates/rangefilter/numeric_filter.html:56 52 | msgid "Reset" 53 | msgstr "بازنشانی" 54 | -------------------------------------------------------------------------------- /rangefilter/locale/fr/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silentsokolov/django-admin-rangefilter/50c04fa9cb3d3254a482b0dde224c9c22845af28/rangefilter/locale/fr/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /rangefilter/locale/fr/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | # Translators: 7 | # Fabien Schwob , 2019 8 | # 9 | #, fuzzy 10 | msgid "" 11 | msgstr "" 12 | "Project-Id-Version: PACKAGE VERSION\n" 13 | "Report-Msgid-Bugs-To: \n" 14 | "POT-Creation-Date: 2022-09-25 09:48+0300\n" 15 | "PO-Revision-Date: 2019-01-31 09:16+0000\n" 16 | "Last-Translator: Fabien Schwob , 2019\n" 17 | "Language-Team: French (https://www.transifex.com/django-admin-rangefilter/" 18 | "teams/95736/fr/)\n" 19 | "Language: fr\n" 20 | "MIME-Version: 1.0\n" 21 | "Content-Type: text/plain; charset=UTF-8\n" 22 | "Content-Transfer-Encoding: 8bit\n" 23 | "Plural-Forms: nplurals=2; plural=(n > 1);\n" 24 | 25 | #: apps.py:16 26 | msgid "Range Filter" 27 | msgstr "Filtre par tranche" 28 | 29 | #: filters.py:210 filters.py:246 30 | msgid "From date" 31 | msgstr "Depuis la date" 32 | 33 | #: filters.py:220 filters.py:256 34 | msgid "To date" 35 | msgstr "Jusqu'à la date" 36 | 37 | #: filters.py:351 38 | msgid "From" 39 | msgstr "Depuis" 40 | 41 | #: filters.py:361 42 | msgid "To" 43 | msgstr "Jusqu'à" 44 | 45 | #: templates/rangefilter/date_filter.html:139 46 | #: templates/rangefilter/date_filter_1_8.html:90 47 | #: templates/rangefilter/date_filter_csp.html:138 48 | #: templates/rangefilter/numeric_filter.html:55 49 | msgid "Search" 50 | msgstr "Chercher" 51 | 52 | #: templates/rangefilter/date_filter.html:140 53 | #: templates/rangefilter/date_filter_1_8.html:91 54 | #: templates/rangefilter/date_filter_csp.html:139 55 | #: templates/rangefilter/numeric_filter.html:56 56 | msgid "Reset" 57 | msgstr "Réinitialiser" 58 | -------------------------------------------------------------------------------- /rangefilter/locale/it/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silentsokolov/django-admin-rangefilter/50c04fa9cb3d3254a482b0dde224c9c22845af28/rangefilter/locale/it/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /rangefilter/locale/it/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | # Translators: 7 | # Daniele Bortoluzzi , 2020 8 | # 9 | #, fuzzy 10 | msgid "" 11 | msgstr "" 12 | "Project-Id-Version: PACKAGE VERSION\n" 13 | "Report-Msgid-Bugs-To: \n" 14 | "POT-Creation-Date: 2022-09-25 09:48+0300\n" 15 | "PO-Revision-Date: 2019-01-31 09:16+0000\n" 16 | "Last-Translator: Daniele Bortoluzzi , 2020\n" 17 | "Language-Team: Italian (https://www.transifex.com/django-admin-rangefilter/" 18 | "teams/95736/it/)\n" 19 | "Language: it\n" 20 | "MIME-Version: 1.0\n" 21 | "Content-Type: text/plain; charset=UTF-8\n" 22 | "Content-Transfer-Encoding: 8bit\n" 23 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 24 | 25 | #: apps.py:16 26 | msgid "Range Filter" 27 | msgstr "Filtra per intervallo" 28 | 29 | #: filters.py:210 filters.py:246 30 | msgid "From date" 31 | msgstr "Da" 32 | 33 | #: filters.py:220 filters.py:256 34 | msgid "To date" 35 | msgstr "A" 36 | 37 | #: filters.py:351 38 | msgid "From" 39 | msgstr "Da" 40 | 41 | #: filters.py:361 42 | msgid "To" 43 | msgstr "A" 44 | 45 | #: templates/rangefilter/date_filter.html:139 46 | #: templates/rangefilter/date_filter_1_8.html:90 47 | #: templates/rangefilter/date_filter_csp.html:138 48 | #: templates/rangefilter/numeric_filter.html:55 49 | msgid "Search" 50 | msgstr "Cerca" 51 | 52 | #: templates/rangefilter/date_filter.html:140 53 | #: templates/rangefilter/date_filter_1_8.html:91 54 | #: templates/rangefilter/date_filter_csp.html:139 55 | #: templates/rangefilter/numeric_filter.html:56 56 | msgid "Reset" 57 | msgstr "Reimposta" 58 | -------------------------------------------------------------------------------- /rangefilter/locale/ja/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silentsokolov/django-admin-rangefilter/50c04fa9cb3d3254a482b0dde224c9c22845af28/rangefilter/locale/ja/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /rangefilter/locale/ja/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | # Translators: 7 | # syachi , 2020 8 | # 9 | #, fuzzy 10 | msgid "" 11 | msgstr "" 12 | "Project-Id-Version: PACKAGE VERSION\n" 13 | "Report-Msgid-Bugs-To: \n" 14 | "POT-Creation-Date: 2022-09-25 09:48+0300\n" 15 | "PO-Revision-Date: 2019-01-31 09:16+0000\n" 16 | "Last-Translator: syachi , 2020\n" 17 | "Language-Team: Japanese (https://www.transifex.com/django-admin-rangefilter/" 18 | "teams/95736/ja/)\n" 19 | "Language: ja\n" 20 | "MIME-Version: 1.0\n" 21 | "Content-Type: text/plain; charset=UTF-8\n" 22 | "Content-Transfer-Encoding: 8bit\n" 23 | "Plural-Forms: nplurals=1; plural=0;\n" 24 | 25 | #: apps.py:16 26 | msgid "Range Filter" 27 | msgstr "範囲フィルター" 28 | 29 | #: filters.py:210 filters.py:246 30 | msgid "From date" 31 | msgstr "開始" 32 | 33 | #: filters.py:220 filters.py:256 34 | msgid "To date" 35 | msgstr "終了" 36 | 37 | #: filters.py:351 38 | msgid "From" 39 | msgstr "" 40 | 41 | #: filters.py:361 42 | msgid "To" 43 | msgstr "" 44 | 45 | #: templates/rangefilter/date_filter.html:139 46 | #: templates/rangefilter/date_filter_1_8.html:90 47 | #: templates/rangefilter/date_filter_csp.html:138 48 | #: templates/rangefilter/numeric_filter.html:55 49 | msgid "Search" 50 | msgstr "検索" 51 | 52 | #: templates/rangefilter/date_filter.html:140 53 | #: templates/rangefilter/date_filter_1_8.html:91 54 | #: templates/rangefilter/date_filter_csp.html:139 55 | #: templates/rangefilter/numeric_filter.html:56 56 | msgid "Reset" 57 | msgstr "リセット" 58 | -------------------------------------------------------------------------------- /rangefilter/locale/pl/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silentsokolov/django-admin-rangefilter/50c04fa9cb3d3254a482b0dde224c9c22845af28/rangefilter/locale/pl/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /rangefilter/locale/pl/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | # Translators: 7 | # sqlmiles , 2020 8 | # 9 | #, fuzzy 10 | msgid "" 11 | msgstr "" 12 | "Project-Id-Version: PACKAGE VERSION\n" 13 | "Report-Msgid-Bugs-To: \n" 14 | "POT-Creation-Date: 2022-09-25 09:48+0300\n" 15 | "PO-Revision-Date: 2019-01-31 09:16+0000\n" 16 | "Last-Translator: sqlmiles , 2020\n" 17 | "Language-Team: Polish (https://www.transifex.com/django-admin-rangefilter/" 18 | "teams/95736/pl/)\n" 19 | "Language: pl\n" 20 | "MIME-Version: 1.0\n" 21 | "Content-Type: text/plain; charset=UTF-8\n" 22 | "Content-Transfer-Encoding: 8bit\n" 23 | "Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n" 24 | "%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n" 25 | "%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n" 26 | 27 | #: apps.py:16 28 | msgid "Range Filter" 29 | msgstr "Filtr zakresu dat" 30 | 31 | #: filters.py:210 filters.py:246 32 | msgid "From date" 33 | msgstr "Od" 34 | 35 | #: filters.py:220 filters.py:256 36 | msgid "To date" 37 | msgstr "Do" 38 | 39 | #: filters.py:351 40 | msgid "From" 41 | msgstr "Od" 42 | 43 | #: filters.py:361 44 | msgid "To" 45 | msgstr "Do" 46 | 47 | #: templates/rangefilter/date_filter.html:139 48 | #: templates/rangefilter/date_filter_1_8.html:90 49 | #: templates/rangefilter/date_filter_csp.html:138 50 | #: templates/rangefilter/numeric_filter.html:55 51 | msgid "Search" 52 | msgstr "Szukaj" 53 | 54 | #: templates/rangefilter/date_filter.html:140 55 | #: templates/rangefilter/date_filter_1_8.html:91 56 | #: templates/rangefilter/date_filter_csp.html:139 57 | #: templates/rangefilter/numeric_filter.html:56 58 | msgid "Reset" 59 | msgstr "Resetuj" 60 | -------------------------------------------------------------------------------- /rangefilter/locale/pt_BR/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silentsokolov/django-admin-rangefilter/50c04fa9cb3d3254a482b0dde224c9c22845af28/rangefilter/locale/pt_BR/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /rangefilter/locale/pt_BR/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | # Translators: 7 | # Dmitriy Sokolov , 2019 8 | # Rui Mineiro , 2020 9 | # 10 | #, fuzzy 11 | msgid "" 12 | msgstr "" 13 | "Project-Id-Version: PACKAGE VERSION\n" 14 | "Report-Msgid-Bugs-To: \n" 15 | "POT-Creation-Date: 2022-09-25 09:48+0300\n" 16 | "PO-Revision-Date: 2019-01-31 09:16+0000\n" 17 | "Last-Translator: Rui Mineiro , 2020\n" 18 | "Language-Team: Portuguese (Brazil) (https://www.transifex.com/django-admin-" 19 | "rangefilter/teams/95736/pt_BR/)\n" 20 | "Language: pt_BR\n" 21 | "MIME-Version: 1.0\n" 22 | "Content-Type: text/plain; charset=UTF-8\n" 23 | "Content-Transfer-Encoding: 8bit\n" 24 | "Plural-Forms: nplurals=2; plural=(n > 1);\n" 25 | 26 | #: apps.py:16 27 | msgid "Range Filter" 28 | msgstr "Range Filter" 29 | 30 | #: filters.py:210 filters.py:246 31 | msgid "From date" 32 | msgstr "Data inicial" 33 | 34 | #: filters.py:220 filters.py:256 35 | msgid "To date" 36 | msgstr "Data final" 37 | 38 | #: filters.py:351 39 | msgid "From" 40 | msgstr "Inicial" 41 | 42 | #: filters.py:361 43 | msgid "To" 44 | msgstr "Final" 45 | 46 | #: templates/rangefilter/date_filter.html:139 47 | #: templates/rangefilter/date_filter_1_8.html:90 48 | #: templates/rangefilter/date_filter_csp.html:138 49 | #: templates/rangefilter/numeric_filter.html:55 50 | msgid "Search" 51 | msgstr "Pesquisar" 52 | 53 | #: templates/rangefilter/date_filter.html:140 54 | #: templates/rangefilter/date_filter_1_8.html:91 55 | #: templates/rangefilter/date_filter_csp.html:139 56 | #: templates/rangefilter/numeric_filter.html:56 57 | msgid "Reset" 58 | msgstr "Limpar" 59 | -------------------------------------------------------------------------------- /rangefilter/locale/pt_PT/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silentsokolov/django-admin-rangefilter/50c04fa9cb3d3254a482b0dde224c9c22845af28/rangefilter/locale/pt_PT/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /rangefilter/locale/pt_PT/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | # Translators: 7 | # Rui Mineiro , 2020 8 | # Dmitriy Sokolov , 2020 9 | # 10 | #, fuzzy 11 | msgid "" 12 | msgstr "" 13 | "Project-Id-Version: PACKAGE VERSION\n" 14 | "Report-Msgid-Bugs-To: \n" 15 | "POT-Creation-Date: 2022-09-25 09:48+0300\n" 16 | "PO-Revision-Date: 2019-01-31 09:16+0000\n" 17 | "Last-Translator: Dmitriy Sokolov , 2020\n" 18 | "Language-Team: Portuguese (Portugal) (https://www.transifex.com/django-admin-" 19 | "rangefilter/teams/95736/pt_PT/)\n" 20 | "Language: pt_PT\n" 21 | "MIME-Version: 1.0\n" 22 | "Content-Type: text/plain; charset=UTF-8\n" 23 | "Content-Transfer-Encoding: 8bit\n" 24 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 25 | 26 | #: apps.py:16 27 | msgid "Range Filter" 28 | msgstr "Range Filter" 29 | 30 | #: filters.py:210 filters.py:246 31 | msgid "From date" 32 | msgstr "Data inicial" 33 | 34 | #: filters.py:220 filters.py:256 35 | msgid "To date" 36 | msgstr "Data final" 37 | 38 | #: filters.py:351 39 | msgid "From" 40 | msgstr "Inicial" 41 | 42 | #: filters.py:361 43 | msgid "To" 44 | msgstr "Final" 45 | 46 | #: templates/rangefilter/date_filter.html:139 47 | #: templates/rangefilter/date_filter_1_8.html:90 48 | #: templates/rangefilter/date_filter_csp.html:138 49 | #: templates/rangefilter/numeric_filter.html:55 50 | msgid "Search" 51 | msgstr "Pesquisar" 52 | 53 | #: templates/rangefilter/date_filter.html:140 54 | #: templates/rangefilter/date_filter_1_8.html:91 55 | #: templates/rangefilter/date_filter_csp.html:139 56 | #: templates/rangefilter/numeric_filter.html:56 57 | msgid "Reset" 58 | msgstr "Limpar" 59 | -------------------------------------------------------------------------------- /rangefilter/locale/ru/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silentsokolov/django-admin-rangefilter/50c04fa9cb3d3254a482b0dde224c9c22845af28/rangefilter/locale/ru/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /rangefilter/locale/ru/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: PACKAGE VERSION\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2022-09-25 09:48+0300\n" 12 | "PO-Revision-Date: 2019-01-31 09:16+0000\n" 13 | "Last-Translator: Dmitriy Sokolov , 2019\n" 14 | "Language-Team: Russian (https://www.transifex.com/django-admin-rangefilter/" 15 | "teams/95736/ru/)\n" 16 | "Language: ru\n" 17 | "MIME-Version: 1.0\n" 18 | "Content-Type: text/plain; charset=UTF-8\n" 19 | "Content-Transfer-Encoding: 8bit\n" 20 | "Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" 21 | "%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n" 22 | "%100>=11 && n%100<=14)? 2 : 3);\n" 23 | 24 | #: apps.py:16 25 | msgid "Range Filter" 26 | msgstr "Фильтр диапазона" 27 | 28 | #: filters.py:210 filters.py:246 29 | msgid "From date" 30 | msgstr "С даты" 31 | 32 | #: filters.py:220 filters.py:256 33 | msgid "To date" 34 | msgstr "По дату" 35 | 36 | #: filters.py:351 37 | msgid "From" 38 | msgstr "С" 39 | 40 | #: filters.py:361 41 | msgid "To" 42 | msgstr "До" 43 | 44 | #: templates/rangefilter/date_filter.html:139 45 | #: templates/rangefilter/date_filter_1_8.html:90 46 | #: templates/rangefilter/date_filter_csp.html:138 47 | #: templates/rangefilter/numeric_filter.html:55 48 | msgid "Search" 49 | msgstr "Поиск" 50 | 51 | #: templates/rangefilter/date_filter.html:140 52 | #: templates/rangefilter/date_filter_1_8.html:91 53 | #: templates/rangefilter/date_filter_csp.html:139 54 | #: templates/rangefilter/numeric_filter.html:56 55 | msgid "Reset" 56 | msgstr "Сбросить" 57 | -------------------------------------------------------------------------------- /rangefilter/locale/tr/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silentsokolov/django-admin-rangefilter/50c04fa9cb3d3254a482b0dde224c9c22845af28/rangefilter/locale/tr/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /rangefilter/locale/tr/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # EMIR TAGMAT , 2023. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: \n" 9 | "Report-Msgid-Bugs-To: \n" 10 | "POT-Creation-Date: 2023-12-05 13:34+0000\n" 11 | "PO-Revision-Date: 2023-12-05 16:38+0300\n" 12 | "Last-Translator: EMIR TAGMAT , 2023\n" 13 | "Language-Team: \n" 14 | "Language: tr\n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=UTF-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 19 | "X-Generator: Poedit 3.4.1\n" 20 | 21 | #: django-admin-rangefilter/rangefilter/apps.py:16 22 | msgid "Range Filter" 23 | msgstr "Aralık Filtresi" 24 | 25 | #: django-admin-rangefilter/rangefilter/filters.py:194 26 | #: django-admin-rangefilter/rangefilter/filters.py:250 27 | msgid "From date" 28 | msgstr "Başlangıç tarihi" 29 | 30 | #: django-admin-rangefilter/rangefilter/filters.py:204 31 | #: django-admin-rangefilter/rangefilter/filters.py:260 32 | msgid "To date" 33 | msgstr "Bitiş tarihi" 34 | 35 | #: django-admin-rangefilter/rangefilter/filters.py:308 36 | msgid "From" 37 | msgstr "Başlangıç" 38 | 39 | #: django-admin-rangefilter/rangefilter/filters.py:318 40 | msgid "To" 41 | msgstr "Bitiş" 42 | 43 | #: django-admin-rangefilter/rangefilter/templates/rangefilter/date_filter.html:142 44 | #: django-admin-rangefilter/rangefilter/templates/rangefilter/date_filter_1_8.html:90 45 | #: django-admin-rangefilter/rangefilter/templates/rangefilter/date_filter_csp.html:112 46 | #: django-admin-rangefilter/rangefilter/templates/rangefilter/numeric_filter.html:57 47 | #: django-admin-rangefilter/rangefilter/templates/rangefilter/numeric_filter_csp.html:45 48 | msgid "Search" 49 | msgstr "Ara" 50 | 51 | #: django-admin-rangefilter/rangefilter/templates/rangefilter/date_filter.html:143 52 | #: django-admin-rangefilter/rangefilter/templates/rangefilter/date_filter_1_8.html:91 53 | #: django-admin-rangefilter/rangefilter/templates/rangefilter/date_filter_csp.html:113 54 | #: django-admin-rangefilter/rangefilter/templates/rangefilter/numeric_filter.html:58 55 | #: django-admin-rangefilter/rangefilter/templates/rangefilter/numeric_filter_csp.html:46 56 | msgid "Reset" 57 | msgstr "Sıfırla" 58 | -------------------------------------------------------------------------------- /rangefilter/locale/uk/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silentsokolov/django-admin-rangefilter/50c04fa9cb3d3254a482b0dde224c9c22845af28/rangefilter/locale/uk/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /rangefilter/locale/uk/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | # Translators: 7 | # vadshu , 2019 8 | # 9 | #, fuzzy 10 | msgid "" 11 | msgstr "" 12 | "Project-Id-Version: PACKAGE VERSION\n" 13 | "Report-Msgid-Bugs-To: \n" 14 | "POT-Creation-Date: 2022-09-25 09:48+0300\n" 15 | "PO-Revision-Date: 2019-01-31 09:16+0000\n" 16 | "Last-Translator: vadshu , 2019\n" 17 | "Language-Team: Ukrainian (https://www.transifex.com/django-admin-rangefilter/" 18 | "teams/95736/uk/)\n" 19 | "Language: uk\n" 20 | "MIME-Version: 1.0\n" 21 | "Content-Type: text/plain; charset=UTF-8\n" 22 | "Content-Transfer-Encoding: 8bit\n" 23 | "Plural-Forms: nplurals=4; plural=(n % 1 == 0 && n % 10 == 1 && n % 100 != " 24 | "11 ? 0 : n % 1 == 0 && n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % " 25 | "100 > 14) ? 1 : n % 1 == 0 && (n % 10 ==0 || (n % 10 >=5 && n % 10 <=9) || " 26 | "(n % 100 >=11 && n % 100 <=14 )) ? 2: 3);\n" 27 | 28 | #: apps.py:16 29 | msgid "Range Filter" 30 | msgstr "Фільтр діапазону" 31 | 32 | #: filters.py:210 filters.py:246 33 | msgid "From date" 34 | msgstr "З дати" 35 | 36 | #: filters.py:220 filters.py:256 37 | msgid "To date" 38 | msgstr "По дату" 39 | 40 | #: filters.py:351 41 | msgid "From" 42 | msgstr "З" 43 | 44 | #: filters.py:361 45 | msgid "To" 46 | msgstr "По" 47 | 48 | #: templates/rangefilter/date_filter.html:139 49 | #: templates/rangefilter/date_filter_1_8.html:90 50 | #: templates/rangefilter/date_filter_csp.html:138 51 | #: templates/rangefilter/numeric_filter.html:55 52 | msgid "Search" 53 | msgstr "Пошук" 54 | 55 | #: templates/rangefilter/date_filter.html:140 56 | #: templates/rangefilter/date_filter_1_8.html:91 57 | #: templates/rangefilter/date_filter_csp.html:139 58 | #: templates/rangefilter/numeric_filter.html:56 59 | msgid "Reset" 60 | msgstr "Скинути" 61 | -------------------------------------------------------------------------------- /rangefilter/locale/zh_CN/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silentsokolov/django-admin-rangefilter/50c04fa9cb3d3254a482b0dde224c9c22845af28/rangefilter/locale/zh_CN/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /rangefilter/locale/zh_CN/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | # Translators: 7 | # Jian Dai , 2020 8 | # 9 | #, fuzzy 10 | msgid "" 11 | msgstr "" 12 | "Project-Id-Version: PACKAGE VERSION\n" 13 | "Report-Msgid-Bugs-To: \n" 14 | "POT-Creation-Date: 2022-09-25 09:48+0300\n" 15 | "PO-Revision-Date: 2019-01-31 09:16+0000\n" 16 | "Last-Translator: Jian Dai , 2020\n" 17 | "Language-Team: Chinese (China) (https://www.transifex.com/django-admin-" 18 | "rangefilter/teams/95736/zh_CN/)\n" 19 | "Language: zh_CN\n" 20 | "MIME-Version: 1.0\n" 21 | "Content-Type: text/plain; charset=UTF-8\n" 22 | "Content-Transfer-Encoding: 8bit\n" 23 | "Plural-Forms: nplurals=1; plural=0;\n" 24 | 25 | #: apps.py:16 26 | msgid "Range Filter" 27 | msgstr "区间过滤器" 28 | 29 | #: filters.py:210 filters.py:246 30 | msgid "From date" 31 | msgstr "从" 32 | 33 | #: filters.py:220 filters.py:256 34 | msgid "To date" 35 | msgstr "到" 36 | 37 | #: filters.py:351 38 | msgid "From" 39 | msgstr "从" 40 | 41 | #: filters.py:361 42 | msgid "To" 43 | msgstr "到" 44 | 45 | #: templates/rangefilter/date_filter.html:139 46 | #: templates/rangefilter/date_filter_1_8.html:90 47 | #: templates/rangefilter/date_filter_csp.html:138 48 | #: templates/rangefilter/numeric_filter.html:55 49 | msgid "Search" 50 | msgstr "搜索" 51 | 52 | #: templates/rangefilter/date_filter.html:140 53 | #: templates/rangefilter/date_filter_1_8.html:91 54 | #: templates/rangefilter/date_filter_csp.html:139 55 | #: templates/rangefilter/numeric_filter.html:56 56 | msgid "Reset" 57 | msgstr "重置" 58 | -------------------------------------------------------------------------------- /rangefilter/locale/zh_Hans/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silentsokolov/django-admin-rangefilter/50c04fa9cb3d3254a482b0dde224c9c22845af28/rangefilter/locale/zh_Hans/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /rangefilter/locale/zh_Hans/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | # Translators: 7 | # Jian Dai , 2020 8 | # 9 | #, fuzzy 10 | msgid "" 11 | msgstr "" 12 | "Project-Id-Version: PACKAGE VERSION\n" 13 | "Report-Msgid-Bugs-To: \n" 14 | "POT-Creation-Date: 2022-09-25 09:48+0300\n" 15 | "PO-Revision-Date: 2019-01-31 09:16+0000\n" 16 | "Last-Translator: Jian Dai , 2020\n" 17 | "Language-Team: Chinese (China) (https://www.transifex.com/django-admin-" 18 | "rangefilter/teams/95736/zh_CN/)\n" 19 | "Language: zh_CN\n" 20 | "MIME-Version: 1.0\n" 21 | "Content-Type: text/plain; charset=UTF-8\n" 22 | "Content-Transfer-Encoding: 8bit\n" 23 | "Plural-Forms: nplurals=1; plural=0;\n" 24 | 25 | #: apps.py:16 26 | msgid "Range Filter" 27 | msgstr "区间过滤器" 28 | 29 | #: filters.py:210 filters.py:246 30 | msgid "From date" 31 | msgstr "从" 32 | 33 | #: filters.py:220 filters.py:256 34 | msgid "To date" 35 | msgstr "到" 36 | 37 | #: filters.py:351 38 | msgid "From" 39 | msgstr "从" 40 | 41 | #: filters.py:361 42 | msgid "To" 43 | msgstr "到" 44 | 45 | #: templates/rangefilter/date_filter.html:139 46 | #: templates/rangefilter/date_filter_1_8.html:90 47 | #: templates/rangefilter/date_filter_csp.html:138 48 | #: templates/rangefilter/numeric_filter.html:55 49 | msgid "Search" 50 | msgstr "搜索" 51 | 52 | #: templates/rangefilter/date_filter.html:140 53 | #: templates/rangefilter/date_filter_1_8.html:91 54 | #: templates/rangefilter/date_filter_csp.html:139 55 | #: templates/rangefilter/numeric_filter.html:56 56 | msgid "Reset" 57 | msgstr "重置" 58 | -------------------------------------------------------------------------------- /rangefilter/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | -------------------------------------------------------------------------------- /rangefilter/templates/rangefilter/date_filter.html: -------------------------------------------------------------------------------- 1 | {% load i18n rangefilter_compat %} 2 |
3 | {% blocktranslate with filter_title=title %} By {{ filter_title }} {% endblocktranslate %} 4 | 5 | 70 | 71 | {% comment %} 72 | Force load jsi18n, issues #5 73 | https://github.com/django/django/blob/stable/1.10.x/django/contrib/admin/templates/admin/change_list.html#L7 74 | {% endcomment %} 75 | 76 | 151 | {% block quick-select-choices %}{% endblock %} 152 |
153 |
154 | {{ spec.form.as_p }} 155 | {% for choice in choices %} 156 | 157 | {% endfor %} 158 |
159 | 160 | 161 |
162 |
163 |
164 |
165 | -------------------------------------------------------------------------------- /rangefilter/templates/rangefilter/date_filter_1_8.html: -------------------------------------------------------------------------------- 1 | {% load i18n rangefilter_compat %} 2 |

{{ title }}

3 | 4 | 54 | {% comment %} 55 | Force load jsi18n, issues #5 56 | https://github.com/django/django/blob/stable/1.8.x/django/contrib/admin/templates/admin/change_list.html#L7 57 | {% endcomment %} 58 | 59 | 72 | 83 |
84 |
85 | {{ spec.form }} 86 | {% for choice in choices %} 87 | 88 | {% endfor %} 89 |
90 | 91 | 92 |
93 |
94 |
95 | -------------------------------------------------------------------------------- /rangefilter/templates/rangefilter/date_filter_4_0.html: -------------------------------------------------------------------------------- 1 | {% load i18n rangefilter_compat %} 2 |

{{ title }}

3 | 4 | 66 | 67 | {% comment %} 68 | Force load jsi18n, issues #5 69 | https://github.com/django/django/blob/stable/1.10.x/django/contrib/admin/templates/admin/change_list.html#L7 70 | {% endcomment %} 71 | 72 | 147 | {% block quick-select-choices %}{% endblock %} 148 |
149 |
150 | {{ spec.form.as_p }} 151 | {% for choice in choices %} 152 | 153 | {% endfor %} 154 |
155 | 156 | 157 |
158 |
159 |
160 | -------------------------------------------------------------------------------- /rangefilter/templates/rangefilter/date_range_quick_select_list_filter.html: -------------------------------------------------------------------------------- 1 | {% extends 'rangefilter/date_filter.html' %} 2 | 3 | {% block quick-select-choices %} 4 | {% comment %} 5 | The following code under the
    tag belongs to admin/filter.html: 6 | https://github.com/django/django/blob/stable/2.2.x/django/contrib/admin/templates/admin/filter.html#L3-L8 7 | {% endcomment %} 8 | 16 | {% endblock %} 17 | -------------------------------------------------------------------------------- /rangefilter/templates/rangefilter/numeric_filter.html: -------------------------------------------------------------------------------- 1 | {% load i18n rangefilter_compat %} 2 |
    3 | {% blocktranslate with filter_title=title %} By {{ filter_title }} {% endblocktranslate %} 4 | 32 | 33 | {% comment %} 34 | Force load jsi18n, issues #5 35 | https://github.com/django/django/blob/stable/1.10.x/django/contrib/admin/templates/admin/change_list.html#L7 36 | {% endcomment %} 37 | 38 | 39 | 57 |
    58 |
    59 | {{ spec.form.as_p }} 60 | {% for choice in choices %} 61 | 62 | {% endfor %} 63 |
    64 | 65 | 66 |
    67 |
    68 |
    69 |
    70 | -------------------------------------------------------------------------------- /rangefilter/templates/rangefilter/numeric_filter_4_0.html: -------------------------------------------------------------------------------- 1 | {% load i18n rangefilter_compat %} 2 |

    {{ title }}

    3 | 28 | 29 | {% comment %} 30 | Force load jsi18n, issues #5 31 | https://github.com/django/django/blob/stable/1.10.x/django/contrib/admin/templates/admin/change_list.html#L7 32 | {% endcomment %} 33 | 34 | 35 | 53 |
    54 |
    55 | {{ spec.form.as_p }} 56 | {% for choice in choices %} 57 | 58 | {% endfor %} 59 |
    60 | 61 | 62 |
    63 |
    64 |
    65 | -------------------------------------------------------------------------------- /rangefilter/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silentsokolov/django-admin-rangefilter/50c04fa9cb3d3254a482b0dde224c9c22845af28/rangefilter/templatetags/__init__.py -------------------------------------------------------------------------------- /rangefilter/templatetags/rangefilter_compat.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import django 4 | from django.template import Library 5 | 6 | if django.VERSION[:2] >= (1, 10): 7 | from django.templatetags.static import static as _static # pylint: disable-all 8 | else: 9 | from django.contrib.admin.templatetags.admin_static import ( 10 | static as _static, # pylint: disable-all 11 | ) 12 | 13 | register = Library() 14 | 15 | 16 | @register.simple_tag() 17 | def static(path): 18 | return _static(path) 19 | 20 | 21 | CSS_VARIABLES = """ 22 | :root { 23 | --button-bg: #79aec8; 24 | --button-fg: #fff; 25 | --border-bottom: #eaeaea; 26 | } 27 | """ 28 | 29 | 30 | @register.simple_tag() 31 | def default_css_vars_if_needed(): 32 | return CSS_VARIABLES if django.VERSION[:2] < (3, 2) else "" 33 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | Django==5.* 2 | black 3 | isort 4 | pylint 5 | yamllint 6 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | import re 6 | from os.path import dirname, join 7 | 8 | from setuptools import setup 9 | 10 | 11 | def get_version(package): 12 | init_py = open(os.path.join(package, "__init__.py"), encoding="utf-8").read() 13 | return re.search("__version__ = ['\"]([^'\"]+)['\"]", init_py).group(1) 14 | 15 | 16 | def get_packages(package): 17 | return [ 18 | dirpath 19 | for dirpath, dirnames, filenames in os.walk(package) 20 | if os.path.exists(os.path.join(dirpath, "__init__.py")) 21 | ] 22 | 23 | 24 | def get_package_data(package): 25 | walk = [ 26 | (dirpath.replace(package + os.sep, "", 1), filenames) 27 | for dirpath, dirnames, filenames in os.walk(package) 28 | if not os.path.exists(os.path.join(dirpath, "__init__.py")) 29 | ] 30 | 31 | filepaths = [] 32 | for base, filenames in walk: 33 | filepaths.extend([os.path.join(base, filename) for filename in filenames]) 34 | return {package: filepaths} 35 | 36 | 37 | setup( 38 | name="django-admin-rangefilter", 39 | version=get_version("rangefilter"), 40 | url="https://github.com/silentsokolov/django-admin-rangefilter", 41 | license="MIT", 42 | description="django-admin-rangefilter app, add the filter by a custom date range on the admin UI.", 43 | long_description_content_type="text/x-rst", 44 | long_description=open(join(dirname(__file__), "README.rst"), encoding="utf-8").read(), 45 | author="Dmitriy Sokolov", 46 | author_email="silentsokolov@gmail.com", 47 | packages=get_packages("rangefilter"), 48 | package_data=get_package_data("rangefilter"), 49 | include_package_data=True, 50 | install_requires=[], 51 | python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*", 52 | zip_safe=False, 53 | platforms="any", 54 | classifiers=[ 55 | "Development Status :: 5 - Production/Stable", 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 :: 2", 63 | "Programming Language :: Python :: 2.7", 64 | "Programming Language :: Python :: 3", 65 | "Programming Language :: Python :: 3.5", 66 | "Programming Language :: Python :: 3.6", 67 | "Programming Language :: Python :: 3.7", 68 | "Programming Language :: Python :: 3.8", 69 | "Programming Language :: Python :: 3.9", 70 | "Programming Language :: Python :: 3.10", 71 | "Programming Language :: Python :: 3.11", 72 | "Programming Language :: Python :: 3.12", 73 | "Topic :: Utilities", 74 | ], 75 | ) 76 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silentsokolov/django-admin-rangefilter/50c04fa9cb3d3254a482b0dde224c9c22845af28/tests/__init__.py -------------------------------------------------------------------------------- /tests/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.6 on 2020-06-04 09:00 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | initial = True 8 | 9 | dependencies = [] 10 | 11 | operations = [ 12 | migrations.CreateModel( 13 | name="RangeModelD", 14 | fields=[ 15 | ( 16 | "id", 17 | models.AutoField( 18 | auto_created=True, 19 | primary_key=True, 20 | serialize=False, 21 | verbose_name="ID", 22 | ), 23 | ), 24 | ("created_at", models.DateField()), 25 | ], 26 | options={ 27 | "ordering": ("created_at",), 28 | }, 29 | ), 30 | migrations.CreateModel( 31 | name="RangeModelDT", 32 | fields=[ 33 | ( 34 | "id", 35 | models.AutoField( 36 | auto_created=True, 37 | primary_key=True, 38 | serialize=False, 39 | verbose_name="ID", 40 | ), 41 | ), 42 | ("created_at", models.DateTimeField()), 43 | ], 44 | options={ 45 | "ordering": ("created_at",), 46 | }, 47 | ), 48 | migrations.CreateModel( 49 | name="RangeModelFloat", 50 | fields=[ 51 | ( 52 | "id", 53 | models.AutoField( 54 | auto_created=True, 55 | primary_key=True, 56 | serialize=False, 57 | verbose_name="ID", 58 | ), 59 | ), 60 | ("float_value", models.FloatField()), 61 | ], 62 | options={ 63 | "ordering": ("float_value",), 64 | }, 65 | ), 66 | ] 67 | -------------------------------------------------------------------------------- /tests/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silentsokolov/django-admin-rangefilter/50c04fa9cb3d3254a482b0dde224c9c22845af28/tests/migrations/__init__.py -------------------------------------------------------------------------------- /tests/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.db import models 4 | 5 | 6 | class RangeModelDT(models.Model): 7 | created_at = models.DateTimeField() 8 | 9 | class Meta: 10 | ordering = ("created_at",) 11 | 12 | 13 | class RangeModelD(models.Model): 14 | created_at = models.DateField() 15 | 16 | class Meta: 17 | ordering = ("created_at",) 18 | 19 | 20 | class RangeModelFloat(models.Model): 21 | float_value = models.FloatField() 22 | 23 | class Meta: 24 | ordering = ("float_value",) 25 | -------------------------------------------------------------------------------- /tests/settings.py: -------------------------------------------------------------------------------- 1 | SECRET_KEY = "testkey" 2 | 3 | DATABASES = { 4 | "default": { 5 | "ENGINE": "django.db.backends.sqlite3", 6 | "NAME": ":memory:", 7 | } 8 | } 9 | 10 | INSTALLED_APPS = ( 11 | "django.contrib.auth", 12 | "django.contrib.contenttypes", 13 | "django.contrib.sites", 14 | "django.contrib.admin", 15 | "django.contrib.sessions", 16 | "django.contrib.messages", 17 | "rangefilter", 18 | "tests", 19 | ) 20 | 21 | TEST_RUNNER = "django.test.runner.DiscoverRunner" 22 | 23 | USE_TZ = True 24 | TIME_ZONE = "UTC" 25 | 26 | SITE_ID = 1 27 | 28 | STATIC_URL = "/static/" 29 | 30 | TEMPLATES = [ 31 | { 32 | "BACKEND": "django.template.backends.django.DjangoTemplates", 33 | "APP_DIRS": True, 34 | "OPTIONS": { 35 | "debug": True, 36 | "context_processors": [ 37 | "django.contrib.auth.context_processors.auth", 38 | "django.contrib.messages.context_processors.messages", 39 | "django.template.context_processors.request", 40 | ], 41 | }, 42 | }, 43 | ] 44 | 45 | MIDDLEWARE = ( 46 | "django.middleware.common.CommonMiddleware", 47 | "django.contrib.sessions.middleware.SessionMiddleware", 48 | "django.contrib.auth.middleware.AuthenticationMiddleware", 49 | "django.contrib.messages.middleware.MessageMiddleware", 50 | ) 51 | 52 | PASSWORD_HASHERS = ("django.contrib.auth.hashers.MD5PasswordHasher",) 53 | 54 | DEFAULT_AUTO_FIELD = "django.db.models.AutoField" 55 | -------------------------------------------------------------------------------- /tests/tests.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import datetime 4 | 5 | try: 6 | import pytz 7 | except ImportError: 8 | pytz = None 9 | 10 | from unittest import skipIf 11 | 12 | import django 13 | from django.contrib.admin import ModelAdmin, site 14 | from django.contrib.admin.views.main import ChangeList 15 | from django.contrib.auth.models import User 16 | from django.contrib.staticfiles.storage import staticfiles_storage 17 | from django.test import RequestFactory, TestCase 18 | from django.test.utils import override_settings 19 | from django.utils import timezone 20 | from django.utils.encoding import force_str 21 | 22 | from rangefilter.filters import ( 23 | DateRangeFilter, 24 | DateTimeRangeFilter, 25 | NumericRangeFilter, 26 | OnceCallMedia, 27 | ) 28 | from rangefilter.templatetags.rangefilter_compat import static 29 | 30 | from .models import RangeModelD, RangeModelDT, RangeModelFloat 31 | 32 | 33 | class RangeModelDTAdmin(ModelAdmin): 34 | list_filter = (("created_at", DateRangeFilter),) 35 | ordering = ("-id",) 36 | 37 | 38 | class RangeModelDAdmin(ModelAdmin): 39 | list_filter = (("created_at", DateRangeFilter),) 40 | ordering = ("-id",) 41 | 42 | 43 | class RangeModelDTTimeAdmin(ModelAdmin): 44 | list_filter = (("created_at", DateTimeRangeFilter),) 45 | ordering = ("-id",) 46 | 47 | 48 | class RangeModelDTimeAdmin(ModelAdmin): 49 | list_filter = (("created_at", DateTimeRangeFilter),) 50 | ordering = ("-id",) 51 | 52 | 53 | class RangeModelFloatAdmin(ModelAdmin): 54 | list_filter = (("float_value", NumericRangeFilter),) 55 | ordering = ("-id",) 56 | 57 | 58 | def select_by(dictlist): 59 | return list(dictlist)[0] 60 | 61 | 62 | class DateFuncTestCase(TestCase): 63 | def test_make_dt_aware_without_pytz(self): 64 | with override_settings(USE_TZ=False): 65 | now = datetime.datetime.now() 66 | date = DateRangeFilter.make_dt_aware(now, pytz.utc) 67 | 68 | self.assertEqual(date.tzinfo, None) 69 | self.assertTrue(timezone.is_naive(date)) 70 | 71 | @skipIf(pytz is None, "install pytz") 72 | @skipIf(django.VERSION >= (4, 0, 0), "pytz not django >= 4") 73 | def test_make_dt_aware_with_pytz(self): 74 | local_tz = timezone.get_current_timezone() 75 | now = datetime.datetime.now() 76 | date = DateRangeFilter.make_dt_aware(now, local_tz) 77 | 78 | self.assertEqual(date.tzinfo.zone, local_tz.zone) 79 | self.assertTrue(timezone.is_aware(date)) 80 | 81 | now = timezone.now() 82 | date = DateRangeFilter.make_dt_aware(now, local_tz) 83 | self.assertEqual(date.tzinfo.zone, local_tz.zone) 84 | self.assertTrue(timezone.is_aware(date)) 85 | 86 | @skipIf(django.VERSION < (4, 0, 0), "timezone django < 4") 87 | def test_make_dt_aware(self): 88 | local_tz = timezone.get_current_timezone() 89 | now = datetime.datetime.now() 90 | date = DateRangeFilter.make_dt_aware(now, local_tz) 91 | 92 | self.assertEqual(date.tzinfo, local_tz) 93 | self.assertTrue(timezone.is_aware(date)) 94 | 95 | now = timezone.now() 96 | date = DateRangeFilter.make_dt_aware(now, local_tz) 97 | self.assertEqual(date.tzinfo, local_tz) 98 | self.assertTrue(timezone.is_aware(date)) 99 | 100 | 101 | class DateRangeFilterTestCase(TestCase): 102 | def setUp(self): 103 | self.today = datetime.date.today() 104 | self.tomorrow = self.today + datetime.timedelta(days=1) 105 | self.one_week_ago = self.today - datetime.timedelta(days=7) 106 | 107 | self.django_book = RangeModelDT.objects.create(created_at=timezone.now()) 108 | self.djangonaut_book = RangeModelDT.objects.create( 109 | created_at=timezone.now() - datetime.timedelta(days=7) 110 | ) 111 | 112 | self.django_book_date = RangeModelD.objects.create(created_at=timezone.now()) 113 | self.djangonaut_book_date = RangeModelD.objects.create( 114 | created_at=timezone.now() - datetime.timedelta(days=7) 115 | ) 116 | 117 | self.username = "foo" 118 | self.email = "bar@foo.com" 119 | self.password = "top_secret" 120 | self.user = User.objects.create_user(self.username, self.email, self.password) 121 | 122 | def get_changelist(self, request, model, modeladmin): 123 | if getattr(modeladmin, "get_changelist_instance", None): 124 | return modeladmin.get_changelist_instance(request) 125 | 126 | return ChangeList( 127 | request, 128 | model, 129 | modeladmin.list_display, 130 | modeladmin.list_display_links, 131 | modeladmin.list_filter, 132 | modeladmin.date_hierarchy, 133 | modeladmin.search_fields, 134 | modeladmin.list_select_related, 135 | modeladmin.list_per_page, 136 | modeladmin.list_max_show_all, 137 | modeladmin.list_editable, 138 | modeladmin, 139 | ) 140 | 141 | def test_datefilter(self): 142 | request_factory = RequestFactory() 143 | modeladmin = RangeModelDTAdmin(RangeModelDT, site) 144 | 145 | request = request_factory.get("/") 146 | request.user = self.user 147 | 148 | changelist = self.get_changelist(request, RangeModelDT, modeladmin) 149 | 150 | queryset = changelist.get_queryset(request) 151 | 152 | self.assertEqual(list(queryset), [self.djangonaut_book, self.django_book]) 153 | filterspec = changelist.get_filters(request)[0][0] 154 | self.assertEqual(force_str(filterspec.title), "created at") 155 | 156 | def test_datefilter_filtered(self): 157 | request_factory = RequestFactory() 158 | modeladmin = RangeModelDTAdmin(RangeModelDT, site) 159 | 160 | request = request_factory.get( 161 | "/", 162 | { 163 | "created_at__range__gte": self.today, 164 | "created_at__range__lte": self.tomorrow, 165 | }, 166 | ) 167 | request.user = self.user 168 | 169 | changelist = self.get_changelist(request, RangeModelDT, modeladmin) 170 | 171 | queryset = changelist.get_queryset(request) 172 | 173 | self.assertEqual(list(queryset), [self.django_book]) 174 | filterspec = changelist.get_filters(request)[0][0] 175 | self.assertEqual(force_str(filterspec.title), "created at") 176 | 177 | choice = select_by(filterspec.choices(changelist)) 178 | self.assertEqual(choice["query_string"], "?") 179 | self.assertEqual(choice["system_name"], "created-at") 180 | 181 | def test_datefilter_with_default(self): 182 | request_factory = RequestFactory() 183 | modeladmin = RangeModelDTAdmin(RangeModelDT, site) 184 | modeladmin.get_rangefilter_created_at_default = lambda func: [ # pylint: disable=W0201 185 | self.today, 186 | self.tomorrow, 187 | ] 188 | 189 | request = request_factory.get("/") 190 | request.user = self.user 191 | 192 | changelist = self.get_changelist(request, RangeModelDT, modeladmin) 193 | 194 | queryset = changelist.get_queryset(request) 195 | 196 | self.assertEqual(list(queryset), [self.djangonaut_book, self.django_book]) 197 | filterspec = changelist.get_filters(request)[0][0] 198 | self.assertEqual(force_str(filterspec.title), "created at") 199 | self.assertEqual(filterspec.default_gte, self.today) 200 | self.assertEqual(filterspec.default_lte, self.tomorrow) 201 | 202 | def test_datefilter_filtered_with_one_params(self): 203 | request_factory = RequestFactory() 204 | modeladmin = RangeModelDTAdmin(RangeModelDT, site) 205 | 206 | request = request_factory.get("/", {"created_at__range__gte": self.today}) 207 | request.user = self.user 208 | 209 | changelist = self.get_changelist(request, RangeModelDT, modeladmin) 210 | 211 | queryset = changelist.get_queryset(request) 212 | 213 | self.assertEqual(list(queryset), [self.django_book]) 214 | filterspec = changelist.get_filters(request)[0][0] 215 | self.assertEqual(force_str(filterspec.title), "created at") 216 | 217 | choice = select_by(filterspec.choices(changelist)) 218 | self.assertEqual(choice["query_string"], "?") 219 | self.assertEqual(choice["system_name"], "created-at") 220 | 221 | def test_datefilter_filtered_datefield(self): 222 | request_factory = RequestFactory() 223 | modeladmin = RangeModelDAdmin(RangeModelD, site) 224 | 225 | request = request_factory.get( 226 | "/", 227 | { 228 | "created_at__range__gte": self.today, 229 | }, 230 | ) 231 | request.user = self.user 232 | 233 | changelist = self.get_changelist(request, RangeModelD, modeladmin) 234 | 235 | queryset = changelist.get_queryset(request) 236 | 237 | self.assertEqual(list(queryset), [self.django_book_date]) 238 | filterspec = changelist.get_filters(request)[0][0] 239 | self.assertEqual(force_str(filterspec.title), "created at") 240 | 241 | choice = select_by(filterspec.choices(changelist)) 242 | self.assertEqual(choice["query_string"], "?") 243 | self.assertEqual(choice["system_name"], "created-at") 244 | 245 | 246 | class NumericRangeFilterTestCase(TestCase): 247 | def setUp(self): 248 | self.django_book = RangeModelFloat.objects.create(float_value=1) 249 | self.djangonaut_book = RangeModelFloat.objects.create(float_value=10) 250 | 251 | self.username = "foo" 252 | self.email = "bar@foo.com" 253 | self.password = "top_secret" 254 | self.user = User.objects.create_user(self.username, self.email, self.password) 255 | 256 | def get_changelist(self, request, model, modeladmin): 257 | if getattr(modeladmin, "get_changelist_instance", None): 258 | return modeladmin.get_changelist_instance(request) 259 | 260 | return ChangeList( 261 | request, 262 | model, 263 | modeladmin.list_display, 264 | modeladmin.list_display_links, 265 | modeladmin.list_filter, 266 | modeladmin.date_hierarchy, 267 | modeladmin.search_fields, 268 | modeladmin.list_select_related, 269 | modeladmin.list_per_page, 270 | modeladmin.list_max_show_all, 271 | modeladmin.list_editable, 272 | modeladmin, 273 | ) 274 | 275 | def test_numericfilter(self): 276 | request_factory = RequestFactory() 277 | modeladmin = RangeModelFloatAdmin(RangeModelFloat, site) 278 | 279 | request = request_factory.get("/") 280 | request.user = self.user 281 | 282 | changelist = self.get_changelist(request, RangeModelFloat, modeladmin) 283 | 284 | queryset = changelist.get_queryset(request) 285 | 286 | self.assertEqual(list(queryset), [self.djangonaut_book, self.django_book]) 287 | filterspec = changelist.get_filters(request)[0][0] 288 | self.assertEqual(force_str(filterspec.title), "float value") 289 | 290 | def test_numericfilter_filtered(self): 291 | request_factory = RequestFactory() 292 | modeladmin = RangeModelFloatAdmin(RangeModelFloat, site) 293 | 294 | request = request_factory.get( 295 | "/", 296 | { 297 | "float_value__range__gte": 1, 298 | "float_value__range__lte": 5.5, 299 | }, 300 | ) 301 | request.user = self.user 302 | 303 | changelist = self.get_changelist(request, RangeModelFloat, modeladmin) 304 | 305 | queryset = changelist.get_queryset(request) 306 | 307 | self.assertEqual(list(queryset), [self.django_book]) 308 | filterspec = changelist.get_filters(request)[0][0] 309 | self.assertEqual(force_str(filterspec.title), "float value") 310 | 311 | choice = select_by(filterspec.choices(changelist)) 312 | self.assertEqual(choice["query_string"], "?") 313 | self.assertEqual(choice["system_name"], "float-value") 314 | 315 | def test_numericfilter_with_default(self): 316 | request_factory = RequestFactory() 317 | modeladmin = RangeModelFloatAdmin(RangeModelFloat, site) 318 | modeladmin.get_rangefilter_float_value_default = lambda r: [ # pylint: disable=W0201 319 | 1, 320 | 20, 321 | ] 322 | 323 | request = request_factory.get("/") 324 | request.user = self.user 325 | 326 | changelist = self.get_changelist(request, RangeModelFloat, modeladmin) 327 | 328 | queryset = changelist.get_queryset(request) 329 | 330 | self.assertEqual(list(queryset), [self.djangonaut_book, self.django_book]) 331 | filterspec = changelist.get_filters(request)[0][0] 332 | self.assertEqual(force_str(filterspec.title), "float value") 333 | self.assertEqual(filterspec.default_gte, 1) 334 | self.assertEqual(filterspec.default_lte, 20) 335 | 336 | def test_numericfilter_filtered_with_one_params(self): 337 | request_factory = RequestFactory() 338 | modeladmin = RangeModelFloatAdmin(RangeModelFloat, site) 339 | 340 | request = request_factory.get( 341 | "/", 342 | { 343 | "float_value__range__lte": 5, 344 | }, 345 | ) 346 | request.user = self.user 347 | 348 | changelist = self.get_changelist(request, RangeModelFloat, modeladmin) 349 | 350 | queryset = changelist.get_queryset(request) 351 | 352 | self.assertEqual(list(queryset), [self.django_book]) 353 | filterspec = changelist.get_filters(request)[0][0] 354 | self.assertEqual(force_str(filterspec.title), "float value") 355 | 356 | choice = select_by(filterspec.choices(changelist)) 357 | self.assertEqual(choice["query_string"], "?") 358 | self.assertEqual(choice["system_name"], "float-value") 359 | 360 | def test_numericfilter_custom_title(self): 361 | request_factory = RequestFactory() 362 | custom_title = "foo bar" 363 | modeladmin = RangeModelFloatAdmin(RangeModelFloat, site) 364 | modeladmin.get_rangefilter_float_value_title = ( # pylint: disable=W0201 365 | lambda r, f: custom_title 366 | ) 367 | 368 | request = request_factory.get("/") 369 | request.user = self.user 370 | 371 | changelist = self.get_changelist(request, RangeModelFloat, modeladmin) 372 | 373 | queryset = changelist.get_queryset(request) 374 | 375 | self.assertEqual(list(queryset), [self.djangonaut_book, self.django_book]) 376 | filterspec = changelist.get_filters(request)[0][0] 377 | self.assertEqual(force_str(filterspec.title), custom_title) 378 | 379 | 380 | class DateTimeRangeFilterTestCase(TestCase): 381 | def setUp(self): 382 | self.today = datetime.date.today() 383 | self.max_time = datetime.datetime.combine(timezone.now(), datetime.time.max).time() 384 | self.min_time = datetime.datetime.combine(timezone.now(), datetime.time.min).time() 385 | self.tomorrow = self.today + datetime.timedelta(days=1) 386 | self.one_week_ago = self.today - datetime.timedelta(days=7) 387 | 388 | self.django_book = RangeModelDT.objects.create(created_at=timezone.now()) 389 | self.djangonaut_book = RangeModelDT.objects.create( 390 | created_at=timezone.now() - datetime.timedelta(days=7) 391 | ) 392 | 393 | self.django_book_date = RangeModelD.objects.create(created_at=timezone.now()) 394 | self.djangonaut_book_date = RangeModelD.objects.create( 395 | created_at=timezone.now() - datetime.timedelta(days=7) 396 | ) 397 | 398 | self.username = "foo" 399 | self.email = "bar@foo.com" 400 | self.password = "top_secret" 401 | self.user = User.objects.create_user(self.username, self.email, self.password) 402 | 403 | def get_changelist(self, request, model, modeladmin): 404 | if getattr(modeladmin, "get_changelist_instance", None): 405 | return modeladmin.get_changelist_instance(request) 406 | 407 | return ChangeList( 408 | request, 409 | model, 410 | modeladmin.list_display, 411 | modeladmin.list_display_links, 412 | modeladmin.list_filter, 413 | modeladmin.date_hierarchy, 414 | modeladmin.search_fields, 415 | modeladmin.list_select_related, 416 | modeladmin.list_per_page, 417 | modeladmin.list_max_show_all, 418 | modeladmin.list_editable, 419 | modeladmin, 420 | ) 421 | 422 | def test_datetimefilter(self): 423 | request_factory = RequestFactory() 424 | modeladmin = RangeModelDTTimeAdmin(RangeModelDT, site) 425 | 426 | request = request_factory.get("/") 427 | request.user = self.user 428 | 429 | changelist = self.get_changelist(request, RangeModelDT, modeladmin) 430 | 431 | queryset = changelist.get_queryset(request) 432 | 433 | self.assertEqual(list(queryset), [self.djangonaut_book, self.django_book]) 434 | filterspec = changelist.get_filters(request)[0][0] 435 | self.assertEqual(force_str(filterspec.title), "created at") 436 | 437 | def test_datetimefilter_filtered(self): 438 | request_factory = RequestFactory() 439 | modeladmin = RangeModelDTTimeAdmin(RangeModelDT, site) 440 | 441 | request = request_factory.get( 442 | "/", 443 | { 444 | "created_at__range__gte_0": self.today, 445 | "created_at__range__gte_1": self.min_time, 446 | "created_at__range__lte_0": self.tomorrow, 447 | "created_at__range__lte_1": self.max_time, 448 | }, 449 | ) 450 | request.user = self.user 451 | 452 | changelist = self.get_changelist(request, RangeModelDT, modeladmin) 453 | 454 | queryset = changelist.get_queryset(request) 455 | 456 | self.assertEqual(list(queryset), [self.django_book]) 457 | filterspec = changelist.get_filters(request)[0][0] 458 | self.assertEqual(force_str(filterspec.title), "created at") 459 | 460 | choice = select_by(filterspec.choices(changelist)) 461 | self.assertEqual(choice["query_string"], "?") 462 | self.assertEqual(choice["system_name"], "created-at") 463 | 464 | def test_datetimefilter_with_default(self): 465 | request_factory = RequestFactory() 466 | modeladmin = RangeModelDTTimeAdmin(RangeModelDT, site) 467 | modeladmin.get_rangefilter_created_at_default = lambda r: [ # pylint: disable=W0201 468 | self.today, 469 | self.tomorrow, 470 | ] 471 | 472 | request = request_factory.get("/") 473 | request.user = self.user 474 | 475 | changelist = self.get_changelist(request, RangeModelDT, modeladmin) 476 | 477 | queryset = changelist.get_queryset(request) 478 | 479 | self.assertEqual(list(queryset), [self.djangonaut_book, self.django_book]) 480 | filterspec = changelist.get_filters(request)[0][0] 481 | self.assertEqual(force_str(filterspec.title), "created at") 482 | self.assertEqual(filterspec.default_gte, self.today) 483 | self.assertEqual(filterspec.default_lte, self.tomorrow) 484 | 485 | def test_datetimefilter_filtered_with_one_params(self): 486 | request_factory = RequestFactory() 487 | modeladmin = RangeModelDTTimeAdmin(RangeModelDT, site) 488 | 489 | request = request_factory.get( 490 | "/", 491 | { 492 | "created_at__range__gte_0": self.today, 493 | "created_at__range__gte_1": self.min_time, 494 | }, 495 | ) 496 | request.user = self.user 497 | 498 | changelist = self.get_changelist(request, RangeModelDT, modeladmin) 499 | 500 | queryset = changelist.get_queryset(request) 501 | 502 | self.assertEqual(list(queryset), [self.django_book]) 503 | filterspec = changelist.get_filters(request)[0][0] 504 | self.assertEqual(force_str(filterspec.title), "created at") 505 | 506 | choice = select_by(filterspec.choices(changelist)) 507 | self.assertEqual(choice["query_string"], "?") 508 | self.assertEqual(choice["system_name"], "created-at") 509 | 510 | def test_datetimefilter_custom_title(self): 511 | request_factory = RequestFactory() 512 | custom_title = "foo bar" 513 | modeladmin = RangeModelDTTimeAdmin(RangeModelDT, site) 514 | modeladmin.get_rangefilter_created_at_title = ( # pylint: disable=W0201 515 | lambda r, f: custom_title 516 | ) 517 | 518 | request = request_factory.get("/") 519 | request.user = self.user 520 | 521 | changelist = self.get_changelist(request, RangeModelDT, modeladmin) 522 | 523 | queryset = changelist.get_queryset(request) 524 | 525 | self.assertEqual(list(queryset), [self.djangonaut_book, self.django_book]) 526 | filterspec = changelist.get_filters(request)[0][0] 527 | self.assertEqual(force_str(filterspec.title), custom_title) 528 | 529 | 530 | class TemplateTagsTestCase(TestCase): 531 | @override_settings(STATIC_URL="/test/") 532 | def test_returns_static_path_to_asset_when_staticfiles_app_is_not_installed(self): 533 | self.assertEqual(static("path"), "/test/path") 534 | 535 | def test_returns_static_path_to_asset_when_staticfiles_app_is_installed(self): 536 | with self.modify_settings( 537 | INSTALLED_APPS={ 538 | "append": "django.contrib.staticfiles", 539 | } 540 | ): 541 | old_url = staticfiles_storage.base_url 542 | staticfiles_storage.base_url = "/test/" 543 | try: 544 | self.assertEqual(static("path"), "/test/path") 545 | finally: 546 | staticfiles_storage.base_url = old_url 547 | 548 | 549 | class OnceCallMediaTestCase(TestCase): 550 | def setUp(self): 551 | self.media = OnceCallMedia() 552 | 553 | def test_str(self): 554 | self.assertEqual( 555 | str(self.media), 556 | "['/static/admin/js/calendar.js', '/static/admin/js/admin/DateTimeShortcuts.js']", 557 | ) 558 | 559 | def test_repr(self): 560 | self.assertEqual( 561 | repr(self.media), 562 | "OnceCallMedia(js=['/static/admin/js/calendar.js', '/static/admin/js/admin/DateTimeShortcuts.js'])", 563 | ) 564 | 565 | def test_call(self): 566 | self.assertFalse(self.media._is_rendered) # pylint: disable=protected-access 567 | self.assertNotEqual(self.media(), []) 568 | self.assertTrue(self.media._is_rendered) # pylint: disable=protected-access 569 | self.assertEqual(self.media(), []) 570 | --------------------------------------------------------------------------------