From 6daedf60d8300bb7f8fb4bbe1ca7172eed28c611 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Thu, 26 Aug 2021 22:44:29 +0300 Subject: [PATCH 1/7] Drop support for EOL Python 2.7 and 3.5 --- .github/workflows/test.yml | 2 +- .../tests/test_get_field_choices_view.py | 13 ++----------- setup.py | 6 +++--- tox.ini | 6 ------ 4 files changed, 6 insertions(+), 21 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3473365..7d52064 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [2.7, 3.5, 3.6, 3.7, 3.8, 3.9] + python-version: [3.6, 3.7, 3.8, 3.9] steps: - uses: actions/checkout@v1 diff --git a/advanced_filters/tests/test_get_field_choices_view.py b/advanced_filters/tests/test_get_field_choices_view.py index 28947b3..2e6a5a7 100644 --- a/advanced_filters/tests/test_get_field_choices_view.py +++ b/advanced_filters/tests/test_get_field_choices_view.py @@ -39,6 +39,8 @@ def assert_view_error(client, error, exception=None, **view_kwargs): NO_APP_INSTALLED_ERROR = "No installed app with label 'foo'." +ARGUMENT_LENGTH_ERROR = "not enough values to unpack (expected 2, got 1)" +MISSING_FIELD_ERROR = "SalesRep has no field named 'baz'" if django.VERSION < (1, 11): NO_MODEL_ERROR = "App 'reps' doesn't have a 'foo' model." @@ -46,17 +48,6 @@ def assert_view_error(client, error, exception=None, **view_kwargs): NO_MODEL_ERROR = "App 'reps' doesn't have a 'Foo' model." -if sys.version_info >= (3, 5): - ARGUMENT_LENGTH_ERROR = "not enough values to unpack (expected 2, got 1)" -else: - ARGUMENT_LENGTH_ERROR = "need more than 1 value to unpack" - -if sys.version_info < (3,) and django.VERSION < (1, 11): - MISSING_FIELD_ERROR = "SalesRep has no field named u'baz'" -else: - MISSING_FIELD_ERROR = "SalesRep has no field named 'baz'" - - def test_invalid_view_kwargs(client): assert_view_error(client, "GetFieldChoices view requires 2 arguments") assert_view_error( diff --git a/setup.py b/setup.py index dfdec2b..baa9f1d 100644 --- a/setup.py +++ b/setup.py @@ -70,6 +70,7 @@ def get_full_description(): zip_safe=False, author='Pavel Savchenko', author_email='pavel@modlinltd.com', + python_requires='>=3.6', classifiers=[ 'Environment :: Web Environment', 'Framework :: Django', @@ -77,13 +78,12 @@ def get_full_description(): 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3 :: Only', 'Framework :: Django', 'Framework :: Django :: 1.9', 'Framework :: Django :: 1.10', diff --git a/tox.ini b/tox.ini index 1cb6e29..99a1136 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,5 @@ [tox] envlist = - py27-django{19,110,111} - py35-django{19,110,111,20,21,22} py36-django{111,20,21,22,30,31} py37-django{111,20,21,22,30,31} py38-django{22,30,31} @@ -43,8 +41,6 @@ commands = [travis] python = - 2.7: py27 - 3.5: py35 3.6: py36 3.7: py37 3.9: py39 @@ -64,8 +60,6 @@ DJANGO = [gh-actions] python = - 2.7: py27 - 3.5: py35 3.6: py36 3.7: py37 3.8: py38 From a470d4aa097ccaa8309807fae56d192859e697d6 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Thu, 26 Aug 2021 22:50:32 +0300 Subject: [PATCH 2/7] Drop support for EOL Django up to 2.2 --- README.rst | 2 +- advanced_filters/admin.py | 8 +----- advanced_filters/forms.py | 12 ++------- advanced_filters/models.py | 8 +----- .../tests/test_admin_change_form.py | 6 +---- advanced_filters/tests/test_creation.py | 6 +---- advanced_filters/tests/test_forms.py | 13 ++-------- .../tests/test_get_field_choices_view.py | 12 ++------- advanced_filters/tests/test_usage.py | 6 +---- setup.py | 5 ---- tests/customers/models.py | 8 +----- tox.ini | 26 ++----------------- 12 files changed, 15 insertions(+), 97 deletions(-) diff --git a/README.rst b/README.rst index b68ab6d..fc7c707 100644 --- a/README.rst +++ b/README.rst @@ -26,7 +26,7 @@ For release notes, see `Changelog = 1.9 (Django 1.9 - 3.0 on Python 2/3/PyPy3) +- Django 2.2, >= 3.1 on Python 3.6+/PyPy3 - simplejson >= 3.6.5, < 4 diff --git a/advanced_filters/admin.py b/advanced_filters/admin.py index 693d5be..e3908c6 100644 --- a/advanced_filters/admin.py +++ b/advanced_filters/admin.py @@ -5,17 +5,11 @@ from django.contrib.admin.utils import unquote from django.http import HttpResponseRedirect from django.shortcuts import resolve_url +from django.utils.translation import gettext_lazy as _ from .forms import AdvancedFilterForm from .models import AdvancedFilter -# django < 1.9 support -from django import VERSION -if VERSION >= (2, 0): - from django.utils.translation import gettext_lazy as _ -else: - from django.utils.translation import ugettext_lazy as _ - logger = logging.getLogger('advanced_filters.admin') diff --git a/advanced_filters/forms.py b/advanced_filters/forms.py index efb50d1..9945edc 100644 --- a/advanced_filters/forms.py +++ b/advanced_filters/forms.py @@ -17,20 +17,12 @@ from django.utils.functional import cached_property from six.moves import range, reduce from django.utils.text import capfirst +from django.utils.translation import gettext_lazy as _ from .models import AdvancedFilter from .form_helpers import CleanWhiteSpacesMixin, VaryingTypeCharField -# django < 1.9 support -from django import VERSION -if VERSION >= (2, 0): - from django.utils.translation import gettext_lazy as _ -else: - from django.utils.translation import ugettext_lazy as _ - -# django < 1.9 support -USE_VENDOR_DIR = VERSION >= (1, 9) logger = logging.getLogger('advanced_filters.forms') # select2 location can be modified via settings @@ -234,7 +226,7 @@ class Meta: class Media: required_js = [ - 'admin/js/%sjquery.min.js' % ('vendor/jquery/' if USE_VENDOR_DIR else ''), + 'admin/js/vendor/jquery/jquery.min.js', 'advanced-filters/jquery_adder.js', 'orig_inlines%s.js' % ('' if settings.DEBUG else '.min'), 'magnific-popup/jquery.magnific-popup.js', diff --git a/advanced_filters/models.py b/advanced_filters/models.py index 33f1b28..0e4f835 100644 --- a/advanced_filters/models.py +++ b/advanced_filters/models.py @@ -1,16 +1,10 @@ from django.conf import settings from django.db import models from django.db.models import Q +from django.utils.translation import gettext_lazy as _ from .q_serializer import QSerializer -# django < 1.9 support -from django import VERSION -if VERSION >= (2, 0): - from django.utils.translation import gettext_lazy as _ -else: - from django.utils.translation import ugettext_lazy as _ - class UserLookupManager(models.Manager): def filter_by_user(self, user): diff --git a/advanced_filters/tests/test_admin_change_form.py b/advanced_filters/tests/test_admin_change_form.py index 540376e..fd4c248 100644 --- a/advanced_filters/tests/test_admin_change_form.py +++ b/advanced_filters/tests/test_admin_change_form.py @@ -1,15 +1,11 @@ import pytest from django.contrib.auth.models import Permission from django.db.models import Q +from django.urls import reverse from ..models import AdvancedFilter from .factories import AdvancedFilterFactory -try: - from django.urls import reverse -except ImportError: # Django < 2.0 - from django.core.urlresolvers import reverse - URL_NAME_CHANGE = "admin:advanced_filters_advancedfilter_change" URL_NAME_ADD = "admin:advanced_filters_advancedfilter_add" URL_NAME_CLIENT_CHANGELIST = "admin:customers_client_changelist" diff --git a/advanced_filters/tests/test_creation.py b/advanced_filters/tests/test_creation.py index de3cab0..0384613 100644 --- a/advanced_filters/tests/test_creation.py +++ b/advanced_filters/tests/test_creation.py @@ -1,13 +1,9 @@ import pytest from django.contrib.auth.models import Permission +from django.urls import reverse_lazy from ..models import AdvancedFilter -try: - from django.urls import reverse_lazy -except ImportError: # Django < 2.0 - from django.core.urlresolvers import reverse_lazy - URL_CLIENT_CHANGELIST = reverse_lazy("admin:customers_client_changelist") diff --git a/advanced_filters/tests/test_forms.py b/advanced_filters/tests/test_forms.py index 7cb2e7f..cca3c82 100644 --- a/advanced_filters/tests/test_forms.py +++ b/advanced_filters/tests/test_forms.py @@ -106,17 +106,8 @@ def test_make_query(self): assert isinstance(q, Q) assert isinstance(q.children, list) assert q.connector == 'AND' - if django.VERSION >= (2, 0): - # django 2+ flattens nested empty Query - assert q.negated - assert q.children[0] == ('fname__iexact', 'john') - else: - # django <2 has a parent Query that stays default - assert not q.negated - subquery = q.children[0] - assert isinstance(subquery, Q) - assert subquery.negated - assert subquery.children[0] == ('fname__iexact', 'john') + assert q.negated + assert q.children[0] == ('fname__iexact', 'john') def test_invalid_existing_query(self): Rep = get_user_model() diff --git a/advanced_filters/tests/test_get_field_choices_view.py b/advanced_filters/tests/test_get_field_choices_view.py index 2e6a5a7..e126326 100644 --- a/advanced_filters/tests/test_get_field_choices_view.py +++ b/advanced_filters/tests/test_get_field_choices_view.py @@ -8,13 +8,9 @@ import pytest from django.utils import timezone from django.utils.encoding import force_str +from django.urls import reverse from tests.factories import ClientFactory -try: - from django.urls import reverse -except ImportError: # Django < 2.0 - from django.core.urlresolvers import reverse - URL_NAME = "afilters_get_field_choices" @@ -41,11 +37,7 @@ def assert_view_error(client, error, exception=None, **view_kwargs): NO_APP_INSTALLED_ERROR = "No installed app with label 'foo'." ARGUMENT_LENGTH_ERROR = "not enough values to unpack (expected 2, got 1)" MISSING_FIELD_ERROR = "SalesRep has no field named 'baz'" - -if django.VERSION < (1, 11): - NO_MODEL_ERROR = "App 'reps' doesn't have a 'foo' model." -else: - NO_MODEL_ERROR = "App 'reps' doesn't have a 'Foo' model." +NO_MODEL_ERROR = "App 'reps' doesn't have a 'Foo' model." def test_invalid_view_kwargs(client): diff --git a/advanced_filters/tests/test_usage.py b/advanced_filters/tests/test_usage.py index 0155093..edca0d8 100644 --- a/advanced_filters/tests/test_usage.py +++ b/advanced_filters/tests/test_usage.py @@ -1,17 +1,13 @@ import pytest from django.contrib.auth.models import Permission from django.db.models import Q +from django.urls import reverse from tests.factories import ClientFactory, SalesRepFactory from ..admin import AdvancedListFilters from ..models import AdvancedFilter from .factories import AdvancedFilterFactory -try: - from django.urls import reverse -except ImportError: # Django < 2.0 - from django.core.urlresolvers import reverse - URL_NAME_CLIENT_CHANGELIST = "admin:customers_client_changelist" diff --git a/setup.py b/setup.py index baa9f1d..559a5e9 100644 --- a/setup.py +++ b/setup.py @@ -85,11 +85,6 @@ def get_full_description(): 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3 :: Only', 'Framework :: Django', - 'Framework :: Django :: 1.9', - 'Framework :: Django :: 1.10', - 'Framework :: Django :: 1.11', - 'Framework :: Django :: 2.0', - 'Framework :: Django :: 2.1', 'Framework :: Django :: 2.2', 'Framework :: Django :: 3.0', 'Framework :: Django :: 3.1', diff --git a/tests/customers/models.py b/tests/customers/models.py index f4bf75f..31238aa 100644 --- a/tests/customers/models.py +++ b/tests/customers/models.py @@ -1,13 +1,7 @@ from django.contrib.auth.models import AbstractBaseUser from django.db import models from django.utils import timezone - -# django < 1.9 support -from django import VERSION -if VERSION >= (2, 0): - from django.utils.translation import gettext_lazy as _ -else: - from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ class Client(AbstractBaseUser): diff --git a/tox.ini b/tox.ini index 99a1136..dce1023 100644 --- a/tox.ini +++ b/tox.ini @@ -1,14 +1,7 @@ [tox] envlist = - py36-django{111,20,21,22,30,31} - py37-django{111,20,21,22,30,31} - py38-django{22,30,31} - py39-django{22,30,31} - ; py36-django{111,20,21,22,30,31,32} - ; py37-django{111,20,21,22,30,31,32} - ; py38-django{22,30,31,32} - ; py39-django{22,30,31,32} - pypy3-django{111,20,21,22,30,31} + py{36,37,38,39,py3}-django{22,30,31} + ; py{36,37,38,39}-django{22,30,31,32} report [pycodestyle] @@ -19,11 +12,6 @@ usedevelop = true passenv = TRAVIS TRAVIS_* deps = -rtest-reqs.txt - django19: Django>=1.9,<1.10 - django110: Django>=1.10,<1.11 - django111: Django>=1.11,<1.12 - django20: Django>=2.0,<2.1 - django21: Django>=2.1,<2.2 django22: Django>=2.2,<3.0 django30: Django>=3.0,<3.1 django31: Django>=3.1,<3.2 @@ -48,11 +36,6 @@ python = [travis:env] DJANGO = - 1.9: django19 - 1.10: django110 - 1.11: django111 - 2.0: django20 - 2.1: django21 2.2: django22 3.0: django30 3.1: django31 @@ -68,11 +51,6 @@ python = [gh-actions:env] DJANGO = - 1.9: django19 - 1.10: django110 - 1.11: django111 - 2.0: django20 - 2.1: django21 2.2: django22 3.0: django30 3.1: django31 From cc3467713304a6a8a2859592054f0944b4ffdad3 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Thu, 26 Aug 2021 23:00:50 +0300 Subject: [PATCH 3/7] Upgrade Python syntax with pyupgrade --py36-plus --- advanced_filters/admin.py | 25 +++++++++---------- advanced_filters/form_helpers.py | 8 +++--- advanced_filters/forms.py | 22 ++++++++-------- advanced_filters/migrations/0001_initial.py | 2 -- .../0002_advancedfilter_created_at.py | 3 --- advanced_filters/mixins.py | 12 ++++----- advanced_filters/q_serializer.py | 2 +- advanced_filters/tests/test_creation.py | 2 +- advanced_filters/tests/test_forms.py | 14 +++++------ .../tests/test_get_field_choices_view.py | 2 +- advanced_filters/tests/test_q_serializer.py | 4 +-- setup.py | 6 ++--- tests/customers/migrations/0001_initial.py | 2 -- tests/factories.py | 2 +- tests/reps/migrations/0001_initial.py | 2 -- 15 files changed, 49 insertions(+), 59 deletions(-) diff --git a/advanced_filters/admin.py b/advanced_filters/admin.py index e3908c6..56a6e6f 100644 --- a/advanced_filters/admin.py +++ b/advanced_filters/admin.py @@ -24,8 +24,8 @@ def lookups(self, request, model_admin): if not model_admin: raise Exception('Cannot use AdvancedListFilters without a ' 'model_admin') - model_name = "%s.%s" % (model_admin.model._meta.app_label, - model_admin.model._meta.object_name) + model_name = "{}.{}".format(model_admin.model._meta.app_label, + model_admin.model._meta.object_name) return AdvancedFilter.objects.filter_by_user(request.user).filter( model=model_name).values_list('id', 'title') @@ -43,13 +43,13 @@ def queryset(self, request, queryset): return queryset -class AdminAdvancedFiltersMixin(object): +class AdminAdvancedFiltersMixin: """ Generic AdvancedFilters mixin """ advanced_change_list_template = "admin/advanced_filters.html" advanced_filter_form = AdvancedFilterForm def __init__(self, *args, **kwargs): - super(AdminAdvancedFiltersMixin, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) if self.change_list_template: self.original_change_list_template = self.change_list_template else: @@ -98,8 +98,7 @@ def changelist_view(self, request, extra_context=None): extra_context=extra_context) if response: return response - return super(AdminAdvancedFiltersMixin, self - ).changelist_view(request, extra_context=extra_context) + return super().changelist_view(request, extra_context=extra_context) class AdvancedFilterAdmin(admin.ModelAdmin): @@ -118,20 +117,20 @@ def save_model(self, request, new_object, *args, **kwargs): if new_object and not new_object.pk: new_object.created_by = request.user - super(AdvancedFilterAdmin, self).save_model( + super().save_model( request, new_object, *args, **kwargs) def change_view(self, request, object_id, form_url='', extra_context=None): - orig_response = super(AdvancedFilterAdmin, self).change_view( + orig_response = super().change_view( request, object_id, form_url, extra_context) if '_save_goto' in request.POST: obj = self.get_object(request, unquote(object_id)) if obj: app, model = obj.model.split('.') - path = resolve_url('admin:%s_%s_changelist' % ( + path = resolve_url('admin:{}_{}_changelist'.format( app, model.lower())) url = "{path}{qparams}".format( - path=path, qparams="?_afilter={id}".format(id=object_id)) + path=path, qparams=f"?_afilter={object_id}") return HttpResponseRedirect(url) return orig_response @@ -142,18 +141,18 @@ def user_has_permission(user): def get_queryset(self, request): if self.user_has_permission(request.user): - return super(AdvancedFilterAdmin, self).get_queryset(request) + return super().get_queryset(request) else: return self.model.objects.filter_by_user(request.user) def has_change_permission(self, request, obj=None): if obj is None: - return super(AdvancedFilterAdmin, self).has_change_permission(request) + return super().has_change_permission(request) return self.user_has_permission(request.user) or obj in self.model.objects.filter_by_user(request.user) def has_delete_permission(self, request, obj=None): if obj is None: - return super(AdvancedFilterAdmin, self).has_delete_permission(request) + return super().has_delete_permission(request) return self.user_has_permission(request.user) or obj in self.model.objects.filter_by_user(request.user) diff --git a/advanced_filters/form_helpers.py b/advanced_filters/form_helpers.py index 681be77..61b3b49 100644 --- a/advanced_filters/form_helpers.py +++ b/advanced_filters/form_helpers.py @@ -29,7 +29,7 @@ def to_python(self, value): >>> assert field.to_python('and,me') == '(and|me)' >>> assert field.to_python('and,me;too') == '(and|me;too)' """ - res = super(VaryingTypeCharField, self).to_python(value) + res = super().to_python(value) split_res = res.split(self._default_separator) if not res or len(split_res) < 2: return res.strip() @@ -40,7 +40,7 @@ def to_python(self, value): return res -class CleanWhiteSpacesMixin(object): +class CleanWhiteSpacesMixin: """ This mixin, when added to any form subclass, adds a clean method which strips repeating spaces in and around each string value of "clean_data". @@ -55,9 +55,9 @@ def clean(self): >>> assert form.is_valid() >>> assert form.cleaned_data == {'some_field': 'a weird value'} """ - cleaned_data = super(CleanWhiteSpacesMixin, self).clean() + cleaned_data = super().clean() for k in self.cleaned_data: - if isinstance(self.cleaned_data[k], six.string_types): + if isinstance(self.cleaned_data[k], str): cleaned_data[k] = re.sub(extra_spaces_pattern, ' ', self.cleaned_data[k] or '').strip() return cleaned_data diff --git a/advanced_filters/forms.py b/advanced_filters/forms.py index 9945edc..d77d7b8 100644 --- a/advanced_filters/forms.py +++ b/advanced_filters/forms.py @@ -15,7 +15,7 @@ from django.db.models.fields import DateField from django.forms.formsets import formset_factory, BaseFormSet from django.utils.functional import cached_property -from six.moves import range, reduce +from six.moves import reduce from django.utils.text import capfirst from django.utils.translation import gettext_lazy as _ @@ -76,7 +76,7 @@ def _build_field_choices(self, fields): Iterate over passed model fields tuple and update initial choices. """ return tuple(sorted( - [(fquery, capfirst(fname)) for fquery, fname in fields.items()], + ((fquery, capfirst(fname)) for fquery, fname in fields.items()), key=lambda f: f[1].lower()) ) + self.FIELD_CHOICES @@ -158,7 +158,7 @@ def set_range_value(self, data): data['value'] = (dtfrom, dtto) def clean(self): - cleaned_data = super(AdvancedFilterQueryForm, self).clean() + cleaned_data = super().clean() if cleaned_data.get('operator') == "range": if ('value_from' in cleaned_data and 'value_to' in cleaned_data): @@ -176,7 +176,7 @@ def make_query(self, *args, **kwargs): return query def __init__(self, model_fields={}, *args, **kwargs): - super(AdvancedFilterQueryForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.FIELD_CHOICES = self._build_field_choices(model_fields) self.fields['field'].choices = self.FIELD_CHOICES if not self.fields['field'].initial: @@ -190,13 +190,13 @@ class AdvancedFilterFormSet(BaseFormSet): def __init__(self, *args, **kwargs): self.model_fields = kwargs.pop('model_fields', {}) - super(AdvancedFilterFormSet, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) if self.forms: form = self.forms[0] self.fields = form.visible_fields() def get_form_kwargs(self, index): - kwargs = super(AdvancedFilterFormSet, self).get_form_kwargs(index) + kwargs = super().get_form_kwargs(index) kwargs['model_fields'] = self.model_fields return kwargs @@ -286,7 +286,7 @@ def __init__(self, *args, **kwargs): self._filter_fields = filter_fields or getattr( model_admin, 'advanced_filter_fields', ()) - super(AdvancedFilterForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) # populate existing or empty forms formset data = None @@ -297,15 +297,15 @@ def __init__(self, *args, **kwargs): self.initialize_form(instance, self._model, data, extra_form) def clean(self): - cleaned_data = super(AdvancedFilterForm, self).clean() + cleaned_data = super().clean() if not self.fields_formset.is_valid(): logger.debug( "Errors validating advanced query filters: %s", pformat([(f.errors, f.non_field_errors()) for f in self.fields_formset.forms])) raise forms.ValidationError("Error validating filter forms") - cleaned_data['model'] = "%s.%s" % (self._model._meta.app_label, - self._model._meta.object_name) + cleaned_data['model'] = "{}.{}".format(self._model._meta.app_label, + self._model._meta.object_name) return cleaned_data @property @@ -356,4 +356,4 @@ def initialize_form(self, instance, model, data=None, extra=None): def save(self, commit=True): self.instance.query = self.generate_query() self.instance.model = self.cleaned_data.get('model') - return super(AdvancedFilterForm, self).save(commit) + return super().save(commit) diff --git a/advanced_filters/migrations/0001_initial.py b/advanced_filters/migrations/0001_initial.py index 2fd49d9..dd25c59 100644 --- a/advanced_filters/migrations/0001_initial.py +++ b/advanced_filters/migrations/0001_initial.py @@ -1,6 +1,4 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.9.4 on 2016-03-07 23:02 -from __future__ import unicode_literals from django.conf import settings from django.db import migrations, models diff --git a/advanced_filters/migrations/0002_advancedfilter_created_at.py b/advanced_filters/migrations/0002_advancedfilter_created_at.py index 092e2b9..8d1cfea 100644 --- a/advanced_filters/migrations/0002_advancedfilter_created_at.py +++ b/advanced_filters/migrations/0002_advancedfilter_created_at.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import models, migrations diff --git a/advanced_filters/mixins.py b/advanced_filters/mixins.py index 12b69a8..68e38b5 100644 --- a/advanced_filters/mixins.py +++ b/advanced_filters/mixins.py @@ -22,7 +22,7 @@ import six -class CsrfExemptMixin(object): +class CsrfExemptMixin: """ Exempts the view from CSRF requirements. NOTE: @@ -31,10 +31,10 @@ class CsrfExemptMixin(object): @method_decorator(csrf_exempt) def dispatch(self, *args, **kwargs): - return super(CsrfExemptMixin, self).dispatch(*args, **kwargs) + return super().dispatch(*args, **kwargs) -class AccessMixin(object): +class AccessMixin: """ 'Abstract' mixin that gives access mixins the same customizable functionality. @@ -104,11 +104,11 @@ def dispatch(self, request, *args, **kwargs): if not request.user.is_staff: return self.handle_no_permission(request) - return super(StaffuserRequiredMixin, self).dispatch( + return super().dispatch( request, *args, **kwargs) -class JSONResponseMixin(object): +class JSONResponseMixin: """ A mixin that allows you to easily serialize simple data such as a dict or Django models. @@ -120,7 +120,7 @@ class JSONResponseMixin(object): def get_content_type(self): if (self.content_type is not None and not isinstance(self.content_type, - (six.string_types, six.text_type))): + ((str,), str))): raise ImproperlyConfigured( '{0} is missing a content type. Define {0}.content_type, ' 'or override {0}.get_content_type().'.format( diff --git a/advanced_filters/q_serializer.py b/advanced_filters/q_serializer.py index c0bc668..9f2dac9 100644 --- a/advanced_filters/q_serializer.py +++ b/advanced_filters/q_serializer.py @@ -25,7 +25,7 @@ def dt2ts(obj): return time.mktime(obj.timetuple()) if isinstance(obj, date) else obj -class QSerializer(object): +class QSerializer: """ A Q object serializer base class. Pass base64=True when initializing to Base-64 encode/decode the returned/passed string. diff --git a/advanced_filters/tests/test_creation.py b/advanced_filters/tests/test_creation.py index 0384613..af1805c 100644 --- a/advanced_filters/tests/test_creation.py +++ b/advanced_filters/tests/test_creation.py @@ -79,6 +79,6 @@ def test_create_form_valid(user, client, good_data, query): created_filter = AdvancedFilter.objects.order_by("pk").last() url = res["location"] - assert url.endswith("%s?_afilter=%s" % (URL_CLIENT_CHANGELIST, created_filter.pk)) + assert url.endswith(f"{URL_CLIENT_CHANGELIST}?_afilter={created_filter.pk}") assert list(created_filter.query.children[0]) == query diff --git a/advanced_filters/tests/test_forms.py b/advanced_filters/tests/test_forms.py index cca3c82..4445e76 100644 --- a/advanced_filters/tests/test_forms.py +++ b/advanced_filters/tests/test_forms.py @@ -219,8 +219,8 @@ def setUp(self): created_by=self.user) def _create_query_form_data(self, form_number=0, data=None, **kwargs): - form_data = dict(('form-%d-%s' % (form_number, k), v) - for k, v in (data or self.formset_data).items()) + form_data = {'form-%d-%s' % (form_number, k): v + for k, v in (data or self.formset_data).items()} form_data.update(self.mgmg_form_data) form_data.update(dict(title='baz filter')) form_data.update(kwargs) @@ -236,9 +236,9 @@ def test_failed_validation(self): **self.default_error) assert form.non_field_errors() == self.default_non_field_err assert form.fields_formset.errors == [ - {'operator': [u'This field is required.'], - 'field': [u'This field is required.'], - 'value': [u'This field is required.']}] + {'operator': ['This field is required.'], + 'field': ['This field is required.'], + 'value': ['This field is required.']}] def test_invalid_field_validation(self): form = AdvancedFilterForm(self._create_query_form_data(), instance=self.af, @@ -279,7 +279,7 @@ def test_remove_existing_query(self): class TestAdminInitialization(CommonFormTest): def setUp(self): - super(TestAdminInitialization, self).setUp() + super().setUp() self.fdata = self._create_query_form_data(form_number=0, data={ 'field': 'groups__name', 'negate': False, 'operator': 'iexact', 'value': 'bar'}) @@ -323,7 +323,7 @@ def test_field_resolution(self): def test_create_instance_with_modeladmin(self): form = AdvancedFilterForm(data=self.fdata, model_admin=self.rep_model_admin) - assert form.is_valid(), 'errors: %s, %s' % (form.errors, form.fields_formset.errors) + assert form.is_valid(), f'errors: {form.errors}, {form.fields_formset.errors}' instance = form.save(commit=False) instance.created_by = self.user assert isinstance(instance, AdvancedFilter) diff --git a/advanced_filters/tests/test_get_field_choices_view.py b/advanced_filters/tests/test_get_field_choices_view.py index e126326..d4aa954 100644 --- a/advanced_filters/tests/test_get_field_choices_view.py +++ b/advanced_filters/tests/test_get_field_choices_view.py @@ -140,7 +140,7 @@ def test_choices_no_date_fields_support(user, client, settings): def test_choices_has_null(user, client, settings): settings.ADVANCED_FILTERS_MAX_CHOICES = 4 named_users = ClientFactory.create_batch(2, assigned_to=user) - names = [None] + sorted(set([nu.first_name for nu in named_users])) + names = [None] + sorted({nu.first_name for nu in named_users}) assert len(named_users) == 2 ClientFactory.create_batch(2, assigned_to=user, first_name=None) view_url = reverse( diff --git a/advanced_filters/tests/test_q_serializer.py b/advanced_filters/tests/test_q_serializer.py index 41bae32..9912a31 100644 --- a/advanced_filters/tests/test_q_serializer.py +++ b/advanced_filters/tests/test_q_serializer.py @@ -8,7 +8,7 @@ class QSerializerTest(TestCase): correct_query = { 'children': [('test', 1234)], - 'connector': u'AND', + 'connector': 'AND', 'negated': False, } @@ -28,7 +28,7 @@ def test_jsondump_q(self): def test_deserialize_q(self): qres = self.s.deserialize({ 'children': [('test', 1234)], - 'connector': u'AND', + 'connector': 'AND', 'negated': False, 'subtree_parents': [] }) diff --git a/setup.py b/setup.py index 559a5e9..56c8214 100644 --- a/setup.py +++ b/setup.py @@ -35,11 +35,11 @@ def get_full_description(): readme = 'README.rst' changelog = 'CHANGELOG.rst' base = os.path.dirname(__file__) - with io.open(os.path.join(base, readme), encoding='utf-8') as readme: + with open(os.path.join(base, readme), encoding='utf-8') as readme: README = readme.read() - with io.open(os.path.join(base, changelog), encoding='utf-8') as changelog: + with open(os.path.join(base, changelog), encoding='utf-8') as changelog: CHANGELOG = changelog.read() - return '%s\n%s' % (README, CHANGELOG) + return f'{README}\n{CHANGELOG}' # allow setup.py to be run from any path diff --git a/tests/customers/migrations/0001_initial.py b/tests/customers/migrations/0001_initial.py index e6f6867..de6f1c5 100644 --- a/tests/customers/migrations/0001_initial.py +++ b/tests/customers/migrations/0001_initial.py @@ -1,6 +1,4 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.9.4 on 2016-03-13 22:23 -from __future__ import unicode_literals from django.conf import settings from django.db import migrations, models diff --git a/tests/factories.py b/tests/factories.py index 8fb039d..d5b2eb3 100644 --- a/tests/factories.py +++ b/tests/factories.py @@ -15,7 +15,7 @@ class Meta: @classmethod def _prepare(cls, create, **kwargs): password = kwargs.pop('password', None) - user = super(SalesRepFactory, cls)._prepare(create, **kwargs) + user = super()._prepare(create, **kwargs) if password: user.set_password(password) if create: diff --git a/tests/reps/migrations/0001_initial.py b/tests/reps/migrations/0001_initial.py index f6a2407..099a1e5 100644 --- a/tests/reps/migrations/0001_initial.py +++ b/tests/reps/migrations/0001_initial.py @@ -1,6 +1,4 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.9.4 on 2016-03-13 22:23 -from __future__ import unicode_literals import django.contrib.auth.models import django.core.validators From 5f2fc23e35ec1bf18be8ec72b04a97fa59ebb8ba Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Thu, 26 Aug 2021 23:01:45 +0300 Subject: [PATCH 4/7] Remove six --- advanced_filters/form_helpers.py | 2 -- advanced_filters/forms.py | 2 +- advanced_filters/mixins.py | 5 ----- advanced_filters/q_serializer.py | 3 +-- 4 files changed, 2 insertions(+), 10 deletions(-) diff --git a/advanced_filters/form_helpers.py b/advanced_filters/form_helpers.py index 61b3b49..8fbe474 100644 --- a/advanced_filters/form_helpers.py +++ b/advanced_filters/form_helpers.py @@ -3,8 +3,6 @@ from django import forms -import six - logger = logging.getLogger('advanced_filters.form_helpers') extra_spaces_pattern = re.compile(r'\s+') diff --git a/advanced_filters/forms.py b/advanced_filters/forms.py index d77d7b8..e14794f 100644 --- a/advanced_filters/forms.py +++ b/advanced_filters/forms.py @@ -15,7 +15,7 @@ from django.db.models.fields import DateField from django.forms.formsets import formset_factory, BaseFormSet from django.utils.functional import cached_property -from six.moves import reduce +from functools import reduce from django.utils.text import capfirst from django.utils.translation import gettext_lazy as _ diff --git a/advanced_filters/mixins.py b/advanced_filters/mixins.py index 68e38b5..3a5be15 100644 --- a/advanced_filters/mixins.py +++ b/advanced_filters/mixins.py @@ -16,11 +16,6 @@ from django.utils.encoding import force_text as force_string from django.views.decorators.csrf import csrf_exempt -try: - from django.utils import six -except ImportError: - import six - class CsrfExemptMixin: """ diff --git a/advanced_filters/q_serializer.py b/advanced_filters/q_serializer.py index 9f2dac9..322cfcc 100644 --- a/advanced_filters/q_serializer.py +++ b/advanced_filters/q_serializer.py @@ -3,7 +3,6 @@ import base64 import time -import six from django.db.models import Q from django.core.serializers.base import SerializationError @@ -121,7 +120,7 @@ def dumps(self, obj): raise SerializationError string = json.dumps(self.serialize(obj), default=dt2ts) if self.b64_enabled: - return base64.b64encode(six.b(string)).decode("utf-8") + return base64.b64encode(string.encode("latin-1")).decode("utf-8") return string def loads(self, string, raw=False): From a8952dfd7f8e1886cf97a7c39fb0e3f1f63e4db1 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Fri, 27 Aug 2021 10:46:22 +0300 Subject: [PATCH 5/7] Drop support for EOL Django 3.0 --- setup.py | 1 - tox.ini | 7 ++----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/setup.py b/setup.py index 56c8214..92b2f1a 100644 --- a/setup.py +++ b/setup.py @@ -86,7 +86,6 @@ def get_full_description(): 'Programming Language :: Python :: 3 :: Only', 'Framework :: Django', 'Framework :: Django :: 2.2', - 'Framework :: Django :: 3.0', 'Framework :: Django :: 3.1', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', diff --git a/tox.ini b/tox.ini index dce1023..73ab869 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,7 @@ [tox] envlist = - py{36,37,38,39,py3}-django{22,30,31} - ; py{36,37,38,39}-django{22,30,31,32} + py{36,37,38,39,py3}-django{22,31} + ; py{36,37,38,39}-django{22,31,32} report [pycodestyle] @@ -13,7 +13,6 @@ passenv = TRAVIS TRAVIS_* deps = -rtest-reqs.txt django22: Django>=2.2,<3.0 - django30: Django>=3.0,<3.1 django31: Django>=3.1,<3.2 ; django32: Django>=3.2,<3.3 @@ -37,7 +36,6 @@ python = [travis:env] DJANGO = 2.2: django22 - 3.0: django30 3.1: django31 ; 3.2: django32 @@ -52,7 +50,6 @@ python = [gh-actions:env] DJANGO = 2.2: django22 - 3.0: django30 3.1: django31 ; 3.2: django32 From 48e772a634f1f8feccdf43dbac2b4354f1d30b25 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Fri, 27 Aug 2021 11:22:30 +0300 Subject: [PATCH 6/7] Remove unused import --- setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.py b/setup.py index 92b2f1a..fd1272f 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,3 @@ -import io import os import sys From a36aa8ac6e7a19b278c782c6e37b35cc48982027 Mon Sep 17 00:00:00 2001 From: Pavel Savchenko Date: Sun, 23 Jan 2022 13:19:35 +0000 Subject: [PATCH 7/7] feat: f-string for model_name string interpolation --- advanced_filters/admin.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/advanced_filters/admin.py b/advanced_filters/admin.py index 488baff..fce4831 100644 --- a/advanced_filters/admin.py +++ b/advanced_filters/admin.py @@ -22,16 +22,20 @@ class AdvancedListFilters(admin.SimpleListFilter): def lookups(self, request, model_admin): if not model_admin: - raise Exception('Cannot use AdvancedListFilters without a ' - 'model_admin') - model_name = "{}.{}".format(model_admin.model._meta.app_label, - model_admin.model._meta.object_name) + raise Exception( + "Cannot use AdvancedListFilters without a model_admin" + ) + model_name = ( + f"{model_admin.model._meta.app_label}." + f"{model_admin.model._meta.object_name}" + ) return AdvancedFilter.objects.filter_by_user(request.user).filter( model=model_name).values_list('id', 'title') def queryset(self, request, queryset): if self.value(): filters = AdvancedFilter.objects.filter(id=self.value()) + advfilter = None if hasattr(filters, 'first'): advfilter = filters.first() if not advfilter: