├── .gitignore ├── LICENSE ├── README.md ├── admin_numeric_filter ├── __init__.py ├── admin.py ├── forms.py ├── static │ ├── css │ │ └── admin-numeric-filter.css │ └── js │ │ ├── admin-numeric-filter.js │ │ ├── nouislider.min.css │ │ ├── nouislider.min.js │ │ └── wNumb.min.js └── templates │ └── admin │ ├── filter_numeric_range.html │ ├── filter_numeric_single.html │ └── filter_numeric_slider.html ├── pyproject.toml └── screenshot.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | .DS_Store 106 | .idea -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Lukas Vinclav 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Screenshot](https://github.com/lukasvinclav/django-admin-numeric-filter/raw/main/screenshot.png) 2 | 3 | # django-admin-numeric-filter 4 | 5 | ![](https://img.shields.io/badge/Version-0.1.9-orange.svg?style=flat-square) 6 | ![](https://img.shields.io/badge/Django-2.0+-green.svg?style=flat-square) 7 | ![](https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square) 8 | 9 | django-admin-numeric-filter provides you several filter classes for Django admin which you can use to filter results in change list. It works in **list_filter** when a field name is defined as list where the first value is field name and second one is custom filter class (you can find classes below). 10 | 11 | Don't forget to inherit your model admin from **admin_actions.admin.NumericFilterModelAdmin** to load custom CSS styles and JavaScript files declared in inner Media class. 12 | 13 | ## Getting started 14 | 15 | 1. Installation 16 | 17 | ```bash 18 | pip install django-admin-numeric-filter 19 | ``` 20 | 21 | 2. Add **admin_numeric_filter** into **INSTALLED_APPS** in your settings file before **django.contrib.admin**. 22 | 23 | ## Sample admin configuration 24 | 25 | ```python 26 | from admin_numeric_filter.admin import NumericFilterModelAdmin, SingleNumericFilter, RangeNumericFilter, \ 27 | SliderNumericFilter 28 | 29 | from .models import YourModel 30 | 31 | 32 | class CustomSliderNumericFilter(SliderNumericFilter): 33 | MAX_DECIMALS = 2 34 | STEP = 10 35 | 36 | 37 | @admin.register(YourModel) 38 | class YourModelAdmin(NumericFilterModelAdmin): 39 | list_filter = ( 40 | ('field_A', SingleNumericFilter), # Single field search, __gte lookup 41 | ('field_B', RangeNumericFilter), # Range search, __gte and __lte lookup 42 | ('field_C', SliderNumericFilter), # Same as range above but with slider 43 | ('field_D', CustomSliderNumericFilter), # Filter with custom attributes 44 | ) 45 | ``` 46 | 47 | ## Filter classes 48 | 49 | | Class name | Description | 50 | |------------------------------------------|----------------------------------------| 51 | | admin_actions.admin.SingleNumericFilter | Single field search, __gte lookup | 52 | | admin_actions.admin.RangeNumericFilter | Range search, __gte and __lte lookup | 53 | | admin_actions.admin.SliderNumericFilter | Same as range above but with slider | 54 | 55 | 56 | ## Slider default options for certain field types 57 | 58 | | Django model field | Step | Decimal places | 59 | |------------------------------------------|--------------------------|----------------------------| 60 | | django.db.models.fields.DecimalField() | Based on decimal places | max precision from DB | 61 | | django.db.models.fields.FloatField() | Based on decimal places | field decimal_places attr | 62 | | django.db.models.fields.IntegerField() | 1 | 0 | -------------------------------------------------------------------------------- /admin_numeric_filter/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukasvinclav/django-admin-numeric-filter/219ae41b81f54f69b6a65da4e10be54d58776c04/admin_numeric_filter/__init__.py -------------------------------------------------------------------------------- /admin_numeric_filter/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.contrib.admin.utils import reverse_field_path 3 | from django.db.models import Max, Min 4 | from django.db.models.fields import DecimalField, FloatField, IntegerField, AutoField 5 | 6 | from .forms import RangeNumericForm, SingleNumericForm, SliderNumericForm 7 | 8 | 9 | class NumericFilterModelAdmin(admin.ModelAdmin): 10 | class Media: 11 | css = { 12 | 'all': ( 13 | 'js/nouislider.min.css', 14 | 'css/admin-numeric-filter.css', 15 | ) 16 | } 17 | js = ( 18 | 'js/wNumb.min.js', 19 | 'js/nouislider.min.js', 20 | 'js/admin-numeric-filter.js', 21 | ) 22 | 23 | 24 | class SingleNumericFilter(admin.FieldListFilter): 25 | request = None 26 | parameter_name = None 27 | template = 'admin/filter_numeric_single.html' 28 | 29 | def __init__(self, field, request, params, model, model_admin, field_path): 30 | super().__init__(field, request, params, model, model_admin, field_path) 31 | if not isinstance(field, (DecimalField, IntegerField, FloatField, AutoField)): 32 | raise TypeError('Class {} is not supported for {}.'.format(type(self.field), self.__class__.__name__)) 33 | 34 | self.request = request 35 | if self.parameter_name is None: 36 | 37 | self.parameter_name = self.field_path 38 | if self.parameter_name in params: 39 | value = params.pop(self.parameter_name) 40 | self.used_parameters[self.parameter_name] = value 41 | 42 | def queryset(self, request, queryset): 43 | if self.value(): 44 | return queryset.filter(**{self.parameter_name: self.value()}) 45 | 46 | def value(self): 47 | return self.used_parameters.get(self.parameter_name, None) 48 | 49 | def expected_parameters(self): 50 | return [self.parameter_name] 51 | 52 | def choices(self, changelist): 53 | return ({ 54 | 'request': self.request, 55 | 'parameter_name': self.parameter_name, 56 | 'form': SingleNumericForm(name=self.parameter_name, data={self.parameter_name: self.value()}), 57 | }, ) 58 | 59 | 60 | class RangeNumericFilter(admin.FieldListFilter): 61 | request = None 62 | parameter_name = None 63 | template = 'admin/filter_numeric_range.html' 64 | 65 | def __init__(self, field, request, params, model, model_admin, field_path): 66 | super().__init__(field, request, params, model, model_admin, field_path) 67 | if not isinstance(field, (DecimalField, IntegerField, FloatField, AutoField)): 68 | raise TypeError('Class {} is not supported for {}.'.format(type(self.field), self.__class__.__name__)) 69 | 70 | self.request = request 71 | if self.parameter_name is None: 72 | self.parameter_name = self.field_path 73 | 74 | if self.parameter_name + '_from' in params: 75 | value = params.pop(self.field_path + '_from') 76 | self.used_parameters[self.field_path + '_from'] = value 77 | 78 | if self.parameter_name + '_to' in params: 79 | value = params.pop(self.field_path + '_to') 80 | self.used_parameters[self.field_path + '_to'] = value 81 | 82 | def queryset(self, request, queryset): 83 | filters = {} 84 | 85 | value_from = self.used_parameters.get(self.parameter_name + '_from', None) 86 | if value_from is not None and value_from != '': 87 | filters.update({ 88 | self.parameter_name + '__gte': self.used_parameters.get(self.parameter_name + '_from', None), 89 | }) 90 | 91 | value_to = self.used_parameters.get(self.parameter_name + '_to', None) 92 | if value_to is not None and value_to != '': 93 | filters.update({ 94 | self.parameter_name + '__lte': self.used_parameters.get(self.parameter_name + '_to', None), 95 | }) 96 | 97 | return queryset.filter(**filters) 98 | 99 | def expected_parameters(self): 100 | return [ 101 | '{}_from'.format(self.parameter_name), 102 | '{}_to'.format(self.parameter_name), 103 | ] 104 | 105 | def choices(self, changelist): 106 | return ({ 107 | 'request': self.request, 108 | 'parameter_name': self.parameter_name, 109 | 'form': RangeNumericForm(name=self.parameter_name, data={ 110 | self.parameter_name + '_from': self.used_parameters.get(self.parameter_name + '_from', None), 111 | self.parameter_name + '_to': self.used_parameters.get(self.parameter_name + '_to', None), 112 | }), 113 | }, ) 114 | 115 | 116 | class SliderNumericFilter(RangeNumericFilter): 117 | MAX_DECIMALS = 7 118 | STEP = None 119 | 120 | template = 'admin/filter_numeric_slider.html' 121 | field = None 122 | 123 | def __init__(self, field, request, params, model, model_admin, field_path): 124 | super().__init__(field, request, params, model, model_admin, field_path) 125 | 126 | self.field = field 127 | self.q = model_admin.get_queryset(request) 128 | 129 | def choices(self, changelist): 130 | total = self.q.all().count() 131 | min_value = self.q.all().aggregate( 132 | min=Min(self.parameter_name) 133 | ).get('min', 0) 134 | 135 | if total > 1: 136 | max_value = self.q.all().aggregate( 137 | max=Max(self.parameter_name) 138 | ).get('max', 0) 139 | else: 140 | max_value = None 141 | 142 | if isinstance(self.field, (FloatField, DecimalField)): 143 | decimals = self.MAX_DECIMALS 144 | step = self.STEP if self.STEP else self._get_min_step(self.MAX_DECIMALS) 145 | else: 146 | decimals = 0 147 | step = self.STEP if self.STEP else 1 148 | 149 | return ({ 150 | 'decimals': decimals, 151 | 'step': step, 152 | 'parameter_name': self.parameter_name, 153 | 'request': self.request, 154 | 'min': min_value, 155 | 'max': max_value, 156 | 'value_from': self.used_parameters.get(self.parameter_name + '_from', min_value), 157 | 'value_to': self.used_parameters.get(self.parameter_name + '_to', max_value), 158 | 'form': SliderNumericForm(name=self.parameter_name, data={ 159 | self.parameter_name + '_from': self.used_parameters.get(self.parameter_name + '_from', min_value), 160 | self.parameter_name + '_to': self.used_parameters.get(self.parameter_name + '_to', max_value), 161 | }) 162 | }, ) 163 | 164 | def _get_min_step(self, precision): 165 | result_format = '{{:.{}f}}'.format(precision - 1) 166 | return float(result_format.format(0) + '1') 167 | -------------------------------------------------------------------------------- /admin_numeric_filter/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django.forms import Media 3 | try: 4 | from django.utils.translation import ugettext_lazy as _ # until django 3.2 5 | except ImportError: 6 | from django.utils.translation import gettext_lazy as _ # from django 4 7 | 8 | 9 | 10 | NUMERIC_FILTER_CSS = 'css/admin-numeric-filter.css' 11 | 12 | 13 | class SingleNumericForm(forms.Form): 14 | def __init__(self, *args, **kwargs): 15 | name = kwargs.pop('name') 16 | super().__init__(*args, **kwargs) 17 | 18 | self.fields[name] = forms.FloatField(label='', required=False, 19 | widget=forms.NumberInput(attrs={'placeholder': _('Value')})) 20 | 21 | @property 22 | def media(self): 23 | return super().media + Media(css=[self.NUMERIC_FILTER_CSS]) 24 | 25 | 26 | class RangeNumericForm(forms.Form): 27 | name = None 28 | 29 | def __init__(self, *args, **kwargs): 30 | self.name = kwargs.pop('name') 31 | super().__init__(*args, **kwargs) 32 | 33 | self.fields[self.name + '_from'] = forms.FloatField(label='', required=False, 34 | widget=forms.NumberInput(attrs={'placeholder': _('From')})) 35 | self.fields[self.name + '_to'] = forms.FloatField(label='', required=False, 36 | widget=forms.NumberInput(attrs={'placeholder': _('To')})) 37 | 38 | @property 39 | def media(self): 40 | return super().media + Media(css=[self.NUMERIC_FILTER_CSS]) 41 | 42 | 43 | class SliderNumericForm(RangeNumericForm): 44 | pass 45 | -------------------------------------------------------------------------------- /admin_numeric_filter/static/css/admin-numeric-filter.css: -------------------------------------------------------------------------------- 1 | #changelist-filter .admin-numeric-filter-wrapper { 2 | border-bottom: 1px solid #eaeaea; 3 | overflow: hidden; 4 | padding: 0 15px 15px 15px; 5 | } 6 | 7 | #changelist-filter .admin-numeric-filter-wrapper h3 { 8 | padding-left: 0; 9 | padding-right: 0; 10 | } 11 | 12 | #changelist-filter .admin-numeric-filter-wrapper p { 13 | display: flex; 14 | flex-direction: column; 15 | padding-left: 0; 16 | padding-right: 0; 17 | } 18 | 19 | #changelist-filter .admin-numeric-filter-wrapper p label { 20 | color: #999; 21 | display: block; 22 | font-size: 13px; 23 | margin: 0 0 3px 0; 24 | } 25 | 26 | #changelist-filter .admin-numeric-filter-wrapper p input { 27 | box-sizing: border-box; 28 | width: 100%; 29 | } 30 | 31 | #changelist-filter .admin-numeric-filter-wrapper p:first-child { 32 | margin-right: 5px; 33 | } 34 | 35 | #changelist-filter .admin-numeric-filter-wrapper p:last-child { 36 | margin-left: 5px; 37 | } 38 | 39 | #changelist-filter .admin-numeric-filter-wrapper button { 40 | background-color: transparent; 41 | border: 0; 42 | color: #447e9b; 43 | cursor: pointer; 44 | float: right; 45 | font-size: 12px; 46 | font-weight: 600; 47 | padding: 0; 48 | } 49 | 50 | #changelist-filter .admin-numeric-filter-wrapper-group { 51 | display: flex; 52 | flex-direction: row; 53 | } 54 | 55 | #changelist-filter .admin-numeric-filter-wrapper-group.hidden { 56 | display: none; 57 | } 58 | 59 | #changelist-filter .admin-numeric-filter-slider { 60 | background-color: rgba(0, 0, 0, .12); 61 | border: 0; 62 | box-shadow: none; 63 | height: 3px; 64 | margin: 15px 0; 65 | } 66 | 67 | #changelist-filter .admin-numeric-filter-slider .noUi-handle { 68 | background-color: #fff; 69 | border: 0; 70 | border-radius: 50%; 71 | box-shadow: 0 1px 3px rgba(0, 0, 0, .3); 72 | cursor: pointer; 73 | height: 14px; 74 | right: -13px; 75 | transition: all .2s ease; 76 | width: 14px; 77 | } 78 | 79 | #changelist-filter .admin-numeric-filter-slider .noUi-handle:focus, 80 | #changelist-filter .admin-numeric-filter-slider .noUi-handle:hover { 81 | box-shadow: 0 1px 3px rgba(0, 0, 0, .3), 0 0 0 6px rgba(121, 174, 200, .2); 82 | outline: none; 83 | } 84 | 85 | #changelist-filter .admin-numeric-filter-slider .noUi-handle-upper { 86 | right: -1px; 87 | } 88 | 89 | #changelist-filter .admin-numeric-filter-slider .noUi-handle:after { 90 | content: none; 91 | } 92 | 93 | #changelist-filter .admin-numeric-filter-slider .noUi-handle:before { 94 | content: none; 95 | } 96 | 97 | #changelist-filter .admin-numeric-filter-slider .noUi-connect { 98 | background-color: #79aec8; 99 | } 100 | 101 | #changelist-filter .admin-numeric-filter-slider-tooltips { 102 | color: #999; 103 | display: flex; 104 | font-size: 12px; 105 | flex-direction: row; 106 | margin: 10px 0; 107 | } 108 | 109 | #changelist-filter .admin-numeric-filter-slider-tooltip-from { 110 | margin: 0 auto 0 0; 111 | } -------------------------------------------------------------------------------- /admin_numeric_filter/static/js/admin-numeric-filter.js: -------------------------------------------------------------------------------- 1 | document.addEventListener('DOMContentLoaded', function() { 2 | Array.from(document.getElementsByClassName('admin-numeric-filter-slider')).forEach(function(slider) { 3 | var from = parseFloat(slider.closest('.admin-numeric-filter-wrapper').querySelectorAll('.admin-numeric-filter-wrapper-group input')[0].value); 4 | var to = parseFloat(slider.closest('.admin-numeric-filter-wrapper').querySelectorAll('.admin-numeric-filter-wrapper-group input')[1].value); 5 | 6 | noUiSlider.create(slider, { 7 | start: [from, to], 8 | step: parseFloat(slider.getAttribute('data-step')), 9 | connect: true, 10 | format: wNumb({ 11 | decimals: parseFloat(slider.getAttribute('data-decimals')) 12 | }), 13 | range: { 14 | 'min': parseFloat(slider.getAttribute('data-min')), 15 | 'max': parseFloat(slider.getAttribute('data-max')) 16 | } 17 | }); 18 | 19 | slider.noUiSlider.on('update', function(values, handle) { 20 | var parent = this.target.closest('.admin-numeric-filter-wrapper'); 21 | var from = parent.querySelectorAll('.admin-numeric-filter-wrapper-group input')[0]; 22 | var to = parent.querySelectorAll('.admin-numeric-filter-wrapper-group input')[1]; 23 | 24 | parent.querySelectorAll('.admin-numeric-filter-slider-tooltip-from')[0].innerHTML = values[0]; 25 | parent.querySelectorAll('.admin-numeric-filter-slider-tooltip-to')[0].innerHTML = values[1]; 26 | 27 | from.value = values[0]; 28 | to.value = values[1]; 29 | }); 30 | }); 31 | }); -------------------------------------------------------------------------------- /admin_numeric_filter/static/js/nouislider.min.css: -------------------------------------------------------------------------------- 1 | /*! nouislider - 11.1.0 - 2018-04-02 11:18:13 */.noUi-target,.noUi-target *{-webkit-touch-callout:none;-webkit-tap-highlight-color:transparent;-webkit-user-select:none;-ms-touch-action:none;touch-action:none;-ms-user-select:none;-moz-user-select:none;user-select:none;-moz-box-sizing:border-box;box-sizing:border-box}.noUi-target{position:relative;direction:ltr}.noUi-base,.noUi-connects{width:100%;height:100%;position:relative;z-index:1}.noUi-connects{overflow:hidden;z-index:0}.noUi-connect,.noUi-origin{will-change:transform;position:absolute;z-index:1;top:0;left:0;height:100%;width:100%;-ms-transform-origin:0 0;-webkit-transform-origin:0 0;transform-origin:0 0}html:not([dir=rtl]) .noUi-horizontal .noUi-origin{left:auto;right:0}.noUi-vertical .noUi-origin{width:0}.noUi-horizontal .noUi-origin{height:0}.noUi-handle{position:absolute}.noUi-state-tap .noUi-connect,.noUi-state-tap .noUi-origin{-webkit-transition:transform .3s;transition:transform .3s}.noUi-state-drag *{cursor:inherit!important}.noUi-horizontal{height:18px}.noUi-horizontal .noUi-handle{width:34px;height:28px;left:-17px;top:-6px}.noUi-vertical{width:18px}.noUi-vertical .noUi-handle{width:28px;height:34px;left:-6px;top:-17px}html:not([dir=rtl]) .noUi-horizontal .noUi-handle{right:-17px;left:auto}.noUi-target{background:#FAFAFA;border-radius:4px;border:1px solid #D3D3D3;box-shadow:inset 0 1px 1px #F0F0F0,0 3px 6px -5px #BBB}.noUi-connects{border-radius:3px}.noUi-connect{background:#3FB8AF}.noUi-draggable{cursor:ew-resize}.noUi-vertical .noUi-draggable{cursor:ns-resize}.noUi-handle{border:1px solid #D9D9D9;border-radius:3px;background:#FFF;cursor:default;box-shadow:inset 0 0 1px #FFF,inset 0 1px 7px #EBEBEB,0 3px 6px -3px #BBB}.noUi-active{box-shadow:inset 0 0 1px #FFF,inset 0 1px 7px #DDD,0 3px 6px -3px #BBB}.noUi-handle:after,.noUi-handle:before{content:"";display:block;position:absolute;height:14px;width:1px;background:#E8E7E6;left:14px;top:6px}.noUi-handle:after{left:17px}.noUi-vertical .noUi-handle:after,.noUi-vertical .noUi-handle:before{width:14px;height:1px;left:6px;top:14px}.noUi-vertical .noUi-handle:after{top:17px}[disabled] .noUi-connect{background:#B8B8B8}[disabled] .noUi-handle,[disabled].noUi-handle,[disabled].noUi-target{cursor:not-allowed}.noUi-pips,.noUi-pips *{-moz-box-sizing:border-box;box-sizing:border-box}.noUi-pips{position:absolute;color:#999}.noUi-value{position:absolute;white-space:nowrap;text-align:center}.noUi-value-sub{color:#ccc;font-size:10px}.noUi-marker{position:absolute;background:#CCC}.noUi-marker-large,.noUi-marker-sub{background:#AAA}.noUi-pips-horizontal{padding:10px 0;height:80px;top:100%;left:0;width:100%}.noUi-value-horizontal{-webkit-transform:translate(-50%,50%);transform:translate(-50%,50%)}.noUi-rtl .noUi-value-horizontal{-webkit-transform:translate(50%,50%);transform:translate(50%,50%)}.noUi-marker-horizontal.noUi-marker{margin-left:-1px;width:2px;height:5px}.noUi-marker-horizontal.noUi-marker-sub{height:10px}.noUi-marker-horizontal.noUi-marker-large{height:15px}.noUi-pips-vertical{padding:0 10px;height:100%;top:0;left:100%}.noUi-value-vertical{-webkit-transform:translate(0,-50%);transform:translate(0,-50%,0);padding-left:25px}.noUi-rtl .noUi-value-vertical{-webkit-transform:translate(0,50%);transform:translate(0,50%)}.noUi-marker-vertical.noUi-marker{width:5px;height:2px;margin-top:-1px}.noUi-marker-vertical.noUi-marker-sub{width:10px}.noUi-marker-vertical.noUi-marker-large{width:15px}.noUi-tooltip{display:block;position:absolute;border:1px solid #D9D9D9;border-radius:3px;background:#fff;color:#000;padding:5px;text-align:center;white-space:nowrap}.noUi-horizontal .noUi-tooltip{-webkit-transform:translate(-50%,0);transform:translate(-50%,0);left:50%;bottom:120%}.noUi-vertical .noUi-tooltip{-webkit-transform:translate(0,-50%);transform:translate(0,-50%);top:50%;right:120%} -------------------------------------------------------------------------------- /admin_numeric_filter/static/js/nouislider.min.js: -------------------------------------------------------------------------------- 1 | /*! nouislider - 11.1.0 - 2018-04-02 11:18:13 */ 2 | 3 | !function(a){"function"==typeof define&&define.amd?define([],a):"object"==typeof exports?module.exports=a():window.noUiSlider=a()}(function(){"use strict";function a(a){return"object"==typeof a&&"function"==typeof a.to&&"function"==typeof a.from}function b(a){a.parentElement.removeChild(a)}function c(a){return null!==a&&void 0!==a}function d(a){a.preventDefault()}function e(a){return a.filter(function(a){return!this[a]&&(this[a]=!0)},{})}function f(a,b){return Math.round(a/b)*b}function g(a,b){var c=a.getBoundingClientRect(),d=a.ownerDocument,e=d.documentElement,f=p(d);return/webkit.*Chrome.*Mobile/i.test(navigator.userAgent)&&(f.x=0),b?c.top+f.y-e.clientTop:c.left+f.x-e.clientLeft}function h(a){return"number"==typeof a&&!isNaN(a)&&isFinite(a)}function i(a,b,c){c>0&&(m(a,b),setTimeout(function(){n(a,b)},c))}function j(a){return Math.max(Math.min(a,100),0)}function k(a){return Array.isArray(a)?a:[a]}function l(a){a=String(a);var b=a.split(".");return b.length>1?b[1].length:0}function m(a,b){a.classList?a.classList.add(b):a.className+=" "+b}function n(a,b){a.classList?a.classList.remove(b):a.className=a.className.replace(new RegExp("(^|\\b)"+b.split(" ").join("|")+"(\\b|$)","gi")," ")}function o(a,b){return a.classList?a.classList.contains(b):new RegExp("\\b"+b+"\\b").test(a.className)}function p(a){var b=void 0!==window.pageXOffset,c="CSS1Compat"===(a.compatMode||"");return{x:b?window.pageXOffset:c?a.documentElement.scrollLeft:a.body.scrollLeft,y:b?window.pageYOffset:c?a.documentElement.scrollTop:a.body.scrollTop}}function q(){return window.navigator.pointerEnabled?{start:"pointerdown",move:"pointermove",end:"pointerup"}:window.navigator.msPointerEnabled?{start:"MSPointerDown",move:"MSPointerMove",end:"MSPointerUp"}:{start:"mousedown touchstart",move:"mousemove touchmove",end:"mouseup touchend"}}function r(){var a=!1;try{var b=Object.defineProperty({},"passive",{get:function(){a=!0}});window.addEventListener("test",null,b)}catch(a){}return a}function s(){return window.CSS&&CSS.supports&&CSS.supports("touch-action","none")}function t(a,b){return 100/(b-a)}function u(a,b){return 100*b/(a[1]-a[0])}function v(a,b){return u(a,a[0]<0?b+Math.abs(a[0]):b-a[0])}function w(a,b){return b*(a[1]-a[0])/100+a[0]}function x(a,b){for(var c=1;a>=b[c];)c+=1;return c}function y(a,b,c){if(c>=a.slice(-1)[0])return 100;var d=x(c,a),e=a[d-1],f=a[d],g=b[d-1],h=b[d];return g+v([e,f],c)/t(g,h)}function z(a,b,c){if(c>=100)return a.slice(-1)[0];var d=x(c,b),e=a[d-1],f=a[d],g=b[d-1];return w([e,f],(c-g)*t(g,b[d]))}function A(a,b,c,d){if(100===d)return d;var e=x(d,a),g=a[e-1],h=a[e];return c?d-g>(h-g)/2?h:g:b[e-1]?a[e-1]+f(d-a[e-1],b[e-1]):d}function B(a,b,c){var d;if("number"==typeof b&&(b=[b]),!Array.isArray(b))throw new Error("noUiSlider ("+$+"): 'range' contains invalid value.");if(d="min"===a?0:"max"===a?100:parseFloat(a),!h(d)||!h(b[0]))throw new Error("noUiSlider ("+$+"): 'range' value isn't numeric.");c.xPct.push(d),c.xVal.push(b[0]),d?c.xSteps.push(!isNaN(b[1])&&b[1]):isNaN(b[1])||(c.xSteps[0]=b[1]),c.xHighestCompleteStep.push(0)}function C(a,b,c){if(!b)return!0;c.xSteps[a]=u([c.xVal[a],c.xVal[a+1]],b)/t(c.xPct[a],c.xPct[a+1]);var d=(c.xVal[a+1]-c.xVal[a])/c.xNumSteps[a],e=Math.ceil(Number(d.toFixed(3))-1),f=c.xVal[a]+c.xNumSteps[a]*e;c.xHighestCompleteStep[a]=f}function D(a,b,c){this.xPct=[],this.xVal=[],this.xSteps=[c||!1],this.xNumSteps=[!1],this.xHighestCompleteStep=[],this.snap=b;var d,e=[];for(d in a)a.hasOwnProperty(d)&&e.push([a[d],d]);for(e.length&&"object"==typeof e[0][0]?e.sort(function(a,b){return a[0][0]-b[0][0]}):e.sort(function(a,b){return a[0]-b[0]}),d=0;d=100)throw new Error("noUiSlider ("+$+"): 'padding' option must not exceed 100% of the range.")}}function Q(a,b){switch(b){case"ltr":a.dir=0;break;case"rtl":a.dir=1;break;default:throw new Error("noUiSlider ("+$+"): 'direction' option was not recognized.")}}function R(a,b){if("string"!=typeof b)throw new Error("noUiSlider ("+$+"): 'behaviour' must be a string containing options.");var c=b.indexOf("tap")>=0,d=b.indexOf("drag")>=0,e=b.indexOf("fixed")>=0,f=b.indexOf("snap")>=0,g=b.indexOf("hover")>=0;if(e){if(2!==a.handles)throw new Error("noUiSlider ("+$+"): 'fixed' behaviour must be used with 2 handles");N(a,a.start[1]-a.start[0])}a.events={tap:c||f,drag:d,fixed:e,snap:f,hover:g}}function S(a,b){if(!1!==b)if(!0===b){a.tooltips=[];for(var c=0;c= 2) required for mode 'count'.");var d=b-1,e=100/d;for(b=[];d--;)b[d]=d*e;b.push(100),a="positions"}return"positions"===a?b.map(function(a){return va.fromStepping(c?va.getStep(a):a)}):"values"===a?c?b.map(function(a){return va.fromStepping(va.getStep(va.toStepping(a)))}):b:void 0}function A(a,b,c){function d(a,b){return(a+b).toFixed(7)/1}var f={},g=va.xVal[0],h=va.xVal[va.xVal.length-1],i=!1,j=!1,k=0;return c=e(c.slice().sort(function(a,b){return a-b})),c[0]!==g&&(c.unshift(g),i=!0),c[c.length-1]!==h&&(c.push(h),j=!0),c.forEach(function(e,g){var h,l,m,n,o,p,q,r,s,t,u=e,v=c[g+1];if("steps"===b&&(h=va.xNumSteps[g]),h||(h=v-u),!1!==u&&void 0!==v)for(h=Math.max(h,1e-7),l=u;l<=v;l=d(l,h)){for(n=va.toStepping(l),o=n-k,r=o/a,s=Math.round(r),t=o/s,m=1;m<=s;m+=1)p=k+m*t,f[p.toFixed(5)]=["x",0];q=c.indexOf(l)>-1?1:"steps"===b?2:0,!g&&i&&(q=0),l===v&&j||(f[n.toFixed(5)]=[l,q]),k=n}}),f}function B(a,b,d){function e(a,b){var d=b===c.cssClasses.value,e=d?k:l,f=d?i:j;return b+" "+e[c.ort]+" "+f[a]}function f(a,f){f[1]=f[1]&&b?b(f[0],f[1]):f[1];var i=h(g,!1);i.className=e(f[1],c.cssClasses.marker),i.style[c.style]=a+"%",f[1]&&(i=h(g,!1),i.className=e(f[1],c.cssClasses.value),i.setAttribute("data-value",f[0]),i.style[c.style]=a+"%",i.innerText=d.to(f[0]))}var g=ya.createElement("div"),i=[c.cssClasses.valueNormal,c.cssClasses.valueLarge,c.cssClasses.valueSub],j=[c.cssClasses.markerNormal,c.cssClasses.markerLarge,c.cssClasses.markerSub],k=[c.cssClasses.valueHorizontal,c.cssClasses.valueVertical],l=[c.cssClasses.markerHorizontal,c.cssClasses.markerVertical];return m(g,c.cssClasses.pips),m(g,0===c.ort?c.cssClasses.pipsHorizontal:c.cssClasses.pipsVertical),Object.keys(a).forEach(function(b){f(b,a[b])}),g}function C(){na&&(b(na),na=null)}function D(a){C();var b=a.mode,c=a.density||1,d=a.filter||!1,e=a.values||!1,f=a.stepped||!1,g=z(b,e,f),h=A(c,b,g),i=a.format||{to:Math.round};return na=ra.appendChild(B(h,d,i))}function E(){var a=ja.getBoundingClientRect(),b="offset"+["Width","Height"][c.ort];return 0===c.ort?a.width||ja[b]:a.height||ja[b]}function F(a,b,d,e){var f=function(f){return!!(f=G(f,e.pageOffset,e.target||b))&&(!(ra.hasAttribute("disabled")&&!e.doNotReject)&&(!(o(ra,c.cssClasses.tap)&&!e.doNotReject)&&(!(a===oa.start&&void 0!==f.buttons&&f.buttons>1)&&((!e.hover||!f.buttons)&&(qa||f.preventDefault(),f.calcPoint=f.points[c.ort],void d(f,e))))))},g=[];return a.split(" ").forEach(function(a){b.addEventListener(a,f,!!qa&&{passive:!0}),g.push([a,f])}),g}function G(a,b,c){var d,e,f=0===a.type.indexOf("touch"),g=0===a.type.indexOf("mouse"),h=0===a.type.indexOf("pointer");if(0===a.type.indexOf("MSPointer")&&(h=!0),f){var i=function(a){return a.target===c||c.contains(a.target)};if("touchstart"===a.type){var j=Array.prototype.filter.call(a.touches,i);if(j.length>1)return!1;d=j[0].pageX,e=j[0].pageY}else{var k=Array.prototype.find.call(a.changedTouches,i);if(!k)return!1;d=k.pageX,e=k.pageY}}return b=b||p(ya),(g||h)&&(d=a.clientX+b.x,e=a.clientY+b.y),a.pageOffset=b,a.points=[d,e],a.cursor=g||h,a}function H(a){var b=a-g(ja,c.ort),d=100*b/E();return d=j(d),c.dir?100-d:d}function I(a){var b=100,c=!1;return ka.forEach(function(d,e){if(!d.hasAttribute("disabled")){var f=Math.abs(sa[e]-a);(f0,100*d/b.baseSize,b.locations,b.handleNumbers)}function L(a,b){b.handle&&(n(b.handle,c.cssClasses.active),ua-=1),b.listeners.forEach(function(a){za.removeEventListener(a[0],a[1])}),0===ua&&(n(ra,c.cssClasses.drag),_(),a.cursor&&(Aa.style.cursor="",Aa.removeEventListener("selectstart",d))),b.handleNumbers.forEach(function(a){S("change",a),S("set",a),S("end",a)})}function M(a,b){var e;if(1===b.handleNumbers.length){var f=ka[b.handleNumbers[0]];if(f.hasAttribute("disabled"))return!1;e=f.children[0],ua+=1,m(e,c.cssClasses.active)}a.stopPropagation();var g=[],h=F(oa.move,za,K,{target:a.target,handle:e,listeners:g,startCalcPoint:a.calcPoint,baseSize:E(),pageOffset:a.pageOffset,handleNumbers:b.handleNumbers,buttonsProperty:a.buttons,locations:sa.slice()}),i=F(oa.end,za,L,{target:a.target,handle:e,listeners:g,doNotReject:!0,handleNumbers:b.handleNumbers}),j=F("mouseout",za,J,{target:a.target,handle:e,listeners:g,doNotReject:!0,handleNumbers:b.handleNumbers});g.push.apply(g,h.concat(i,j)),a.cursor&&(Aa.style.cursor=getComputedStyle(a.target).cursor,ka.length>1&&m(ra,c.cssClasses.drag),Aa.addEventListener("selectstart",d,!1)),b.handleNumbers.forEach(function(a){S("start",a)})}function N(a){a.stopPropagation();var b=H(a.calcPoint),d=I(b);if(!1===d)return!1;c.events.snap||i(ra,c.cssClasses.tap,c.animationDuration),aa(d,b,!0,!0),_(),S("slide",d,!0),S("update",d,!0),S("change",d,!0),S("set",d,!0),c.events.snap&&M(a,{handleNumbers:[d]})}function O(a){var b=H(a.calcPoint),c=va.getStep(b),d=va.fromStepping(c);Object.keys(xa).forEach(function(a){"hover"===a.split(".")[0]&&xa[a].forEach(function(a){a.call(ma,d)})})}function P(a){a.fixed||ka.forEach(function(a,b){F(oa.start,a.children[0],M,{handleNumbers:[b]})}),a.tap&&F(oa.start,ja,N,{}),a.hover&&F(oa.move,ja,O,{hover:!0}),a.drag&&la.forEach(function(b,d){if(!1!==b&&0!==d&&d!==la.length-1){var e=ka[d-1],f=ka[d],g=[b];m(b,c.cssClasses.draggable),a.fixed&&(g.push(e.children[0]),g.push(f.children[0])),g.forEach(function(a){F(oa.start,a,M,{handles:[e,f],handleNumbers:[d-1,d]})})}})}function Q(a,b){xa[a]=xa[a]||[],xa[a].push(b),"update"===a.split(".")[0]&&ka.forEach(function(a,b){S("update",b)})}function R(a){var b=a&&a.split(".")[0],c=b&&a.substring(b.length);Object.keys(xa).forEach(function(a){var d=a.split(".")[0],e=a.substring(d.length);b&&b!==d||c&&c!==e||delete xa[a]})}function S(a,b,d){Object.keys(xa).forEach(function(e){var f=e.split(".")[0];a===f&&xa[e].forEach(function(a){a.call(ma,wa.map(c.format.to),b,wa.slice(),d||!1,sa.slice())})})}function T(a){return a+"%"}function U(a,b,d,e,f,g){return ka.length>1&&(e&&b>0&&(d=Math.max(d,a[b-1]+c.margin)),f&&b1&&c.limit&&(e&&b>0&&(d=Math.min(d,a[b-1]+c.limit)),f&&b1?d.forEach(function(a,c){var d=U(e,a,e[a]+b,f[c],g[c],!1);!1===d?b=0:(b=d-e[a],e[a]=d)}):f=g=[!0];var h=!1;d.forEach(function(a,d){h=aa(a,c[a]+b,f[d],g[d])||h}),h&&d.forEach(function(a){S("update",a),S("slide",a)})}function Y(a,b){return c.dir?100-a-b:a}function Z(a,b){sa[a]=b,wa[a]=va.fromStepping(b);var d="translate("+V(T(Y(b,0)-Ba),"0")+")";ka[a].style[c.transformRule]=d,ba(a),ba(a+1)}function _(){ta.forEach(function(a){var b=sa[a]>50?-1:1,c=3+(ka.length+b*a);ka[a].style.zIndex=c})}function aa(a,b,c,d){return!1!==(b=U(sa,a,b,c,d,!1))&&(Z(a,b),!0)}function ba(a){if(la[a]){var b=0,d=100;0!==a&&(b=sa[a-1]),a!==la.length-1&&(d=sa[a]);var e=d-b,f="translate("+V(T(Y(b,e)),"0")+")",g="scale("+V(e/100,"1")+")";la[a].style[c.transformRule]=f+" "+g}}function ca(a,b){return null===a||!1===a||void 0===a?sa[b]:("number"==typeof a&&(a=String(a)),a=c.format.from(a),a=va.toStepping(a),!1===a||isNaN(a)?sa[b]:a)}function da(a,b){var d=k(a),e=void 0===sa[0];b=void 0===b||!!b,c.animate&&!e&&i(ra,c.cssClasses.tap,c.animationDuration),ta.forEach(function(a){aa(a,ca(d[a],a),!0,!1)}),ta.forEach(function(a){aa(a,sa[a],!0,!0)}),_(),ta.forEach(function(a){S("update",a),null!==d[a]&&b&&S("set",a)})}function ea(a){da(c.start,a)}function fa(){var a=wa.map(c.format.to);return 1===a.length?a[0]:a}function ga(){for(var a in c.cssClasses)c.cssClasses.hasOwnProperty(a)&&n(ra,c.cssClasses[a]);for(;ra.firstChild;)ra.removeChild(ra.firstChild);delete ra.noUiSlider}function ha(){return sa.map(function(a,b){var c=va.getNearbySteps(a),d=wa[b],e=c.thisStep.step,f=null;!1!==e&&d+e>c.stepAfter.startValue&&(e=c.stepAfter.startValue-d),f=d>c.thisStep.startValue?c.thisStep.step:!1!==c.stepBefore.step&&d-c.stepBefore.highestStep,100===a?e=null:0===a&&(f=null);var g=va.countStepDecimals();return null!==e&&!1!==e&&(e=Number(e.toFixed(g))),null!==f&&!1!==f&&(f=Number(f.toFixed(g))),[f,e]})}function ia(a,b){var d=fa(),e=["margin","limit","padding","range","animate","snap","step","format"];e.forEach(function(b){void 0!==a[b]&&(f[b]=a[b])});var g=X(f);e.forEach(function(b){void 0!==a[b]&&(c[b]=g[b])}),va=g.spectrum,c.margin=g.margin,c.limit=g.limit,c.padding=g.padding,c.pips&&D(c.pips),sa=[],da(a.start||d,b)}var ja,ka,la,ma,na,oa=q(),pa=s(),qa=pa&&r(),ra=a,sa=[],ta=[],ua=0,va=c.spectrum,wa=[],xa={},ya=a.ownerDocument,za=ya.documentElement,Aa=ya.body,Ba="rtl"===ya.dir||1===c.ort?0:100;return v(ra),u(c.connect,ja),P(c.events),da(c.start),ma={destroy:ga,steps:ha,on:Q,off:R,get:fa,set:da,reset:ea,__moveHandles:function(a,b,c){W(a,b,sa,c)},options:f,updateOptions:ia,target:ra,removePips:C,pips:D},c.pips&&D(c.pips),c.tooltips&&x(),y(),ma}function Z(a,b){if(!a||!a.nodeName)throw new Error("noUiSlider ("+$+"): create requires a single element, got: "+a);if(a.noUiSlider)throw new Error("noUiSlider ("+$+"): Slider was already initialized.");var c=X(b,a),d=Y(a,c,b);return a.noUiSlider=d,d}var $="11.1.0";D.prototype.getMargin=function(a){var b=this.xNumSteps[0];if(b&&a/b%1!=0)throw new Error("noUiSlider ("+$+"): 'limit', 'margin' and 'padding' must be divisible by step.");return 2===this.xPct.length&&u(this.xVal,a)},D.prototype.toStepping=function(a){return a=y(this.xVal,this.xPct,a)},D.prototype.fromStepping=function(a){return z(this.xVal,this.xPct,a)},D.prototype.getStep=function(a){return a=A(this.xPct,this.xSteps,this.snap,a)},D.prototype.getNearbySteps=function(a){var b=x(a,this.xPct);return{stepBefore:{startValue:this.xVal[b-2],step:this.xNumSteps[b-2],highestStep:this.xHighestCompleteStep[b-2]},thisStep:{startValue:this.xVal[b-1],step:this.xNumSteps[b-1],highestStep:this.xHighestCompleteStep[b-1]},stepAfter:{startValue:this.xVal[b-0],step:this.xNumSteps[b-0],highestStep:this.xHighestCompleteStep[b-0]}}},D.prototype.countStepDecimals=function(){var a=this.xNumSteps.map(l);return Math.max.apply(null,a)},D.prototype.convert=function(a){return this.getStep(this.toStepping(a))};var _={to:function(a){return void 0!==a&&a.toFixed(2)},from:Number};return{version:$,create:Z}}); -------------------------------------------------------------------------------- /admin_numeric_filter/static/js/wNumb.min.js: -------------------------------------------------------------------------------- 1 | !function(e){"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?module.exports=e():window.wNumb=e()}(function(){"use strict";function e(e){return e.split("").reverse().join("")}function t(e,t){return e.substring(0,t.length)===t}function n(e,t){return e.slice(-1*t.length)===t}function r(e,t,n){if((e[t]||e[n])&&e[t]===e[n])throw new Error(t)}function i(e){return"number"==typeof e&&isFinite(e)}function o(e,t){return e=e.toString().split("e"),e=Math.round(+(e[0]+"e"+(e[1]?+e[1]+t:t))),e=e.toString().split("e"),(+(e[0]+"e"+(e[1]?+e[1]-t:-t))).toFixed(t)}function f(t,n,r,f,u,s,c,a,d,p,l,h){var g,v,m,w=h,x="",y="";return s&&(h=s(h)),!!i(h)&&(t!==!1&&0===parseFloat(h.toFixed(t))&&(h=0),h<0&&(g=!0,h=Math.abs(h)),t!==!1&&(h=o(h,t)),h=h.toString(),h.indexOf(".")!==-1?(v=h.split("."),m=v[0],r&&(x=r+v[1])):m=h,n&&(m=e(m).match(/.{1,3}/g),m=e(m.join(e(n)))),g&&a&&(y+=a),f&&(y+=f),g&&d&&(y+=d),y+=m,y+=x,u&&(y+=u),p&&(y=p(y,w)),y)}function u(e,r,o,f,u,s,c,a,d,p,l,h){var g,v="";return l&&(h=l(h)),!(!h||"string"!=typeof h)&&(a&&t(h,a)&&(h=h.replace(a,""),g=!0),f&&t(h,f)&&(h=h.replace(f,"")),d&&t(h,d)&&(h=h.replace(d,""),g=!0),u&&n(h,u)&&(h=h.slice(0,-1*u.length)),r&&(h=h.split(r).join("")),o&&(h=h.replace(o,".")),g&&(v+="-"),v+=h,v=v.replace(/[^0-9\.\-.]/g,""),""!==v&&(v=Number(v),c&&(v=c(v)),!!i(v)&&v))}function s(e){var t,n,i,o={};for(void 0===e.suffix&&(e.suffix=e.postfix),t=0;t=0&&i<8))throw new Error(n);o[n]=i}else if("encoder"===n||"decoder"===n||"edit"===n||"undo"===n){if("function"!=typeof i)throw new Error(n);o[n]=i}else{if("string"!=typeof i)throw new Error(n);o[n]=i}return r(o,"mark","thousand"),r(o,"prefix","negative"),r(o,"prefix","negativeBefore"),o}function c(e,t,n){var r,i=[];for(r=0;r 5 | {% for k, v in choice.request.GET.items %} 6 | {% if not k == choice.parameter_name|add:'_from' and not k == choice.parameter_name|add:'_to' %} 7 | 8 | {% endif %} 9 | {% endfor %} 10 | 11 |

{% blocktrans with filter_title=title %}By {{ filter_title }}{% endblocktrans %}

12 | 13 |
14 | {{ choice.form.as_p }} 15 |
16 | 17 | 18 | 19 | {% endwith %} -------------------------------------------------------------------------------- /admin_numeric_filter/templates/admin/filter_numeric_single.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | 3 | {% with choices.0 as choice %} 4 |
5 | {% for k, v in choice.request.GET.items %} 6 | {% if not k == choice.parameter_name %} 7 | 8 | {% endif %} 9 | {% endfor %} 10 | 11 |

{% blocktrans with filter_title=title %}By {{ filter_title }}{% endblocktrans %}

12 | 13 | {{ choice.form.as_p }} 14 | 15 | 16 |
17 | {% endwith %} -------------------------------------------------------------------------------- /admin_numeric_filter/templates/admin/filter_numeric_slider.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% load l10n %} 3 | 4 | {% with choices.0 as choice %} 5 |
6 | {% for k, v in choice.request.GET.items %} 7 | {% if not k == choice.parameter_name|add:'_from' and not k == choice.parameter_name|add:'_to' %} 8 | 9 | {% endif %} 10 | {% endfor %} 11 | 12 |

{% blocktrans with filter_title=title %}By {{ filter_title }}{% endblocktrans %}

13 | 14 | {% if choice.min is not None and choice.max is not None and choice.step %} 15 |
16 | {{ choice.value_from }} 17 | {{ choice.value_to }} 18 |
19 | 20 |
21 |
22 | 23 | 26 | 27 | 28 | {% else %} 29 |
30 |

31 | {% trans 'Not enough data.' %} 32 |

33 |
34 | {% endif %} 35 |
36 | {% endwith %} 37 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "django-admin-numeric-filter" 3 | version = "0.1.9" 4 | description = "Numeric filters for Django admin" 5 | license = "MIT" 6 | readme = "README.md" 7 | authors = [] 8 | homepage = "https://github.com/lukasvinclav/django-admin-numeric-filter" 9 | repository = "https://github.com/lukasvinclav/django-admin-numeric-filter" 10 | packages = [ 11 | { include = "admin_numeric_filter" }, 12 | ] 13 | keywords = [ 14 | "django", 15 | "admin", 16 | "numeric", 17 | "filter", 18 | ] 19 | classifiers = [ 20 | "Programming Language :: Python", 21 | "Programming Language :: Python :: 3.6", 22 | "Programming Language :: Python :: 3.7", 23 | "Programming Language :: Python :: 3.8", 24 | "Programming Language :: Python :: 3.9", 25 | "Programming Language :: Python :: 3.10", 26 | "Operating System :: OS Independent", 27 | "Environment :: Web Environment", 28 | "Intended Audience :: Developers", 29 | "Framework :: Django", 30 | ] 31 | 32 | [tool.poetry.dependencies] 33 | python = ">=3.6" 34 | django = ">=3.2" 35 | 36 | [build-system] 37 | requires = ["poetry>=1.0"] 38 | build-backend = "poetry.masonry.api" 39 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukasvinclav/django-admin-numeric-filter/219ae41b81f54f69b6a65da4e10be54d58776c04/screenshot.png --------------------------------------------------------------------------------