From e6cb3c98532b57ed891493a000793527805a4ff4 Mon Sep 17 00:00:00 2001 From: sax Date: Wed, 18 Oct 2023 17:17:02 +0200 Subject: [PATCH 1/6] drop --- CHANGES | 7 +++ src/strategy_field/fields.py | 43 +++++++-------- src/strategy_field/forms.py | 3 +- tests/conftest.py | 2 +- tests/demo/demoproject/backends.py | 14 +++-- tests/demo/demoproject/compat.py | 2 - tests/demo/demoproject/demoapp/admin.py | 17 ++++-- tests/test_choice_as_class.py | 19 ++++--- tests/test_choice_as_instance.py | 5 +- tests/test_classloader.py | 4 +- tests/test_dumpdata.py | 20 +++++++ tests/test_field.py | 2 +- tests/test_multiple.py | 7 +-- tests/test_multiple_custom.py | 5 +- tests/test_registry.py | 37 ++++++++----- tests/test_utils.py | 70 ++++++++++++++++--------- tests/test_validators.py | 14 ++--- tox.ini | 6 +-- 18 files changed, 172 insertions(+), 105 deletions(-) delete mode 100644 tests/demo/demoproject/compat.py create mode 100644 tests/test_dumpdata.py diff --git a/CHANGES b/CHANGES index c052d3a..93b5379 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,10 @@ +Release 3.1 +=========== +* removes `display_attribute` from the Field, use Registry(label_attribute=...) +* add support for Django 4.2 +* drop support python <3.8 + + Release 3.0 =========== * WARNING: some internal api changed. Possible backward incompatibility if some "internals" have been used diff --git a/src/strategy_field/fields.py b/src/strategy_field/fields.py index cc25230..57bc69b 100644 --- a/src/strategy_field/fields.py +++ b/src/strategy_field/fields.py @@ -4,6 +4,7 @@ from django.db import models from django.db.models.fields import BLANK_CHOICE_DASH, NOT_PROVIDED from django.db.models.lookups import Contains, IContains, In +from django.utils.module_loading import import_string from django.utils.text import capfirst from inspect import isclass from operator import itemgetter @@ -106,12 +107,10 @@ def __set__(self, obj, value): obj.__dict__[self.field.name] = value -# @deconstructible class AbstractStrategyField(models.Field): registry = None def __init__(self, *args, **kwargs): - self.display_attribute = kwargs.pop('display_attribute', None) self.import_error = kwargs.pop('import_error', None) kwargs['max_length'] = 200 @@ -133,6 +132,22 @@ def contribute_to_class(self, cls, name, private_only=False, virtual_only=NOT_PR # if isinstance(other, Field): # return self.creation_counter == other.creation_counter # return self.registry == other.registry + def deconstruct(self): + name, path, args, kwargs = super().deconstruct() + del kwargs["max_length"] + if "registry" in kwargs: + del kwargs["registry"] + if "choices" in kwargs: + del kwargs["choices"] + return name, path, args, kwargs + + def get_prep_value(self, value): + if value is None: + return None + return fqn(value) + def value_to_string(self, obj): + value = self.value_from_object(obj) + return fqn(value) def get_internal_type(self): return 'CharField' @@ -142,10 +157,7 @@ def _check_choices(self): def _get_choices(self): if self.registry: - return sorted([(klass, get_display_string(klass, self.display_attribute)) - for klass in self.registry], key=itemgetter(1)) - # return [(klass, get_display_string(klass, self.display_attribute)) - # for klass in self.registry] + return self.registry.as_choices() return [] def _set_choices(self, value): @@ -157,25 +169,14 @@ def get_choices(self, include_blank=True, blank_choice=BLANK_CHOICE_DASH, limit_choices_to=None, **kwargs): first_choice = blank_choice if include_blank else [] - return first_choice + [(fqn(klass), l) - for klass, l in self.choices] + return first_choice + self.choices def validate(self, value, model_instance): return value in self.registry - def deconstruct(self): - name, path, args, kwargs = super().deconstruct() - del kwargs["max_length"] - if "registry" in kwargs: - del kwargs["registry"] - if "choices" in kwargs: - del kwargs["choices"] - return name, path, args, kwargs - def formfield(self, form_class=None, choices_form_class=None, **kwargs): defaults = {'required': not self.blank, 'label': capfirst(self.verbose_name), - 'display_attribute': self.display_attribute, 'help_text': self.help_text, 'registry': self.registry} if self.has_default(): @@ -191,7 +192,7 @@ def formfield(self, form_class=None, choices_form_class=None, **kwargs): form_class = choices_form_class or self.form_class for k in list(kwargs): if k not in ('empty_value', 'required', 'choices', - 'registry', 'display_attribute', + 'registry', 'widget', 'label', 'initial', 'help_text', 'error_messages', 'show_hidden_initial'): del kwargs[k] @@ -207,10 +208,6 @@ class StrategyClassField(AbstractStrategyField): form_class = StrategyFormField descriptor = StrategyClassFieldDescriptor - def get_prep_value(self, value): - if value is None: - return None - return fqn(value) # def get_prep_lookup(self, lookup_type, value): # if lookup_type == 'exact': diff --git a/src/strategy_field/forms.py b/src/strategy_field/forms.py index 3f538ec..c57cf8e 100644 --- a/src/strategy_field/forms.py +++ b/src/strategy_field/forms.py @@ -6,9 +6,9 @@ class StrategyFormField(ChoiceField): def __init__(self, *args, **kwargs): - self.display_attribute = kwargs.pop('display_attribute', None) self.registry = kwargs.pop('registry') self.empty_value = kwargs.pop('empty_value', '') + kwargs["choices"] = self.registry.as_choices() super().__init__(*args, **kwargs) def prepare_value(self, value): @@ -49,7 +49,6 @@ def clean(self, value): class StrategyMultipleChoiceFormField(TypedMultipleChoiceField): def __init__(self, *args, **kwargs): self.registry = kwargs.pop('registry') - self.display_attribute = kwargs.pop('display_attribute', None) super().__init__(*args, **kwargs) diff --git a/tests/conftest.py b/tests/conftest.py index ae610fa..899567b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,7 +6,7 @@ def registry(): from demoproject.demoapp.models import AbstractSender, Sender1, Sender2 from strategy_field.registry import Registry - r = Registry(AbstractSender) + r = Registry(AbstractSender, label_attribute="label") r.register(Sender1) r.register(Sender2) return r diff --git a/tests/demo/demoproject/backends.py b/tests/demo/demoproject/backends.py index 952daa5..0f6e72c 100644 --- a/tests/demo/demoproject/backends.py +++ b/tests/demo/demoproject/backends.py @@ -8,14 +8,20 @@ class AnyUserBackend(ModelBackend): def get_all_permissions(self, user_obj, obj=None): # if settings.DEBUG: - return Permission.objects.all().values_list('content_type__app_label', - 'codename').order_by() + return ( + Permission.objects.all() + .values_list("content_type__app_label", "codename") + .order_by() + ) # return super().get_all_permissions(user_obj, obj) def get_group_permissions(self, user_obj, obj=None): # if settings.DEBUG: - return Permission.objects.all().values_list('content_type__app_label', - 'codename').order_by() + return ( + Permission.objects.all() + .values_list("content_type__app_label", "codename") + .order_by() + ) # return super().get_group_permissions(user_obj, # obj) diff --git a/tests/demo/demoproject/compat.py b/tests/demo/demoproject/compat.py deleted file mode 100644 index 16b4d60..0000000 --- a/tests/demo/demoproject/compat.py +++ /dev/null @@ -1,2 +0,0 @@ -def get_edit_form(response): - return response.forms[0] diff --git a/tests/demo/demoproject/demoapp/admin.py b/tests/demo/demoproject/demoapp/admin.py index 006e5b3..e8f3730 100644 --- a/tests/demo/demoproject/demoapp/admin.py +++ b/tests/demo/demoproject/demoapp/admin.py @@ -1,16 +1,23 @@ from django.contrib import admin from django.forms import ModelForm, TextInput -from .models import (DemoAllModel, DemoCustomModel, DemoModel, - DemoModelCallableDefault, DemoModelDefault, DemoModelProxy, - DemoMultipleCustomModel, DemoMultipleModel,) +from .models import ( + DemoAllModel, + DemoCustomModel, + DemoModel, + DemoModelCallableDefault, + DemoModelDefault, + DemoModelProxy, + DemoMultipleCustomModel, + DemoMultipleModel, +) class DemoModelForm(ModelForm): class Meta: model = DemoModelProxy - widgets = {'sender': TextInput} - fields = '__all__' + widgets = {"sender": TextInput} + fields = "__all__" class DemoModelProxyAdmin(admin.ModelAdmin): diff --git a/tests/test_choice_as_class.py b/tests/test_choice_as_class.py index f8d50b5..189e6e9 100644 --- a/tests/test_choice_as_class.py +++ b/tests/test_choice_as_class.py @@ -1,6 +1,5 @@ # flake8: noqa import pytest -from demoproject.compat import get_edit_form from django.forms.models import modelform_factory from django.urls import reverse @@ -135,7 +134,7 @@ def test_form_default(demomodel): @pytest.mark.django_db def test_admin_demomodel_add(webapp, admin_user): res = webapp.get('/demoapp/demomodel/add/', user=admin_user) - form = get_edit_form(res) + form = res.forms["demomodel_form"] form['sender'] = 'demoproject.demoapp.models.Sender1' # import pdb; pdb.set_trace() @@ -148,7 +147,7 @@ def test_admin_demomodel_add(webapp, admin_user): def test_admin_demomodel_edit(webapp, admin_user, demomodel): url = reverse('admin:demoapp_demomodel_change', args=[demomodel.pk]) res = webapp.get(url, user=admin_user) - form = get_edit_form(res) + form = res.forms["demomodel_form"] form['sender'] = 'demoproject.demoapp.models.Sender2' form.submit().follow() assert DemoModel.objects.filter( @@ -159,7 +158,7 @@ def test_admin_demomodel_edit(webapp, admin_user, demomodel): def test_admin_demomodel_validate(webapp, admin_user, demomodel): url = reverse('admin:demoapp_demomodel_change', args=[demomodel.pk]) res = webapp.get(url, user=admin_user) - form = get_edit_form(res) + form = res.forms["demomodel_form"] form['sender'].force_value('invalid_strategy_classname') res = form.submit() assert 'Select a valid choice' in res.context[ @@ -187,11 +186,17 @@ def test_display_attribute(demomodel, registry, monkeypatch): monkeypatch.setattr(SenderNotRegistered, 'label', classmethod(lambda s: fqn(s).split('.')[-1]), raising=False) - - DemoModel._meta.get_field('sender').display_attribute = 'label' + # DemoModel._meta.get_field('sender').display_attribute = 'label' DemoModel._meta.get_field('sender').registry = registry registry.register(SenderNotRegistered) + assert registry.as_choices() == [ + ("demoproject.demoapp.models.Sender1", "demoproject.demoapp.models.Sender1"), + ("demoproject.demoapp.models.Sender2", "demoproject.demoapp.models.Sender2"), + ("demoproject.demoapp.models.SenderNotRegistered", "SenderNotRegistered"), + ] form_class = modelform_factory(DemoModel, exclude=[]) form = form_class(instance=demomodel) - assert form.fields['sender'].choices[1][1] == 'SenderNotRegistered' + assert form.fields['sender'].choices == registry.as_choices() + + assert form.fields['sender'].choices[2][1] == 'SenderNotRegistered' diff --git a/tests/test_choice_as_instance.py b/tests/test_choice_as_instance.py index 808c117..00a749c 100644 --- a/tests/test_choice_as_instance.py +++ b/tests/test_choice_as_instance.py @@ -1,6 +1,5 @@ # !qa: E501 import pytest -from demoproject.compat import get_edit_form from django.forms.models import modelform_factory from django.urls import reverse @@ -112,7 +111,7 @@ def test_form_default(democustommodel): @pytest.mark.django_db def test_admin_demomodel_add(webapp, admin_user): res = webapp.get('/demoapp/democustommodel/add/', user=admin_user) - form = get_edit_form(res) + form = res.forms["democustommodel_form"] form['sender'] = 'demoproject.demoapp.models.Strategy' form.submit().follow() @@ -125,7 +124,7 @@ def test_admin_demomodel_edit(webapp, admin_user, democustommodel): url = reverse('admin:demoapp_democustommodel_change', args=[democustommodel.pk]) res = webapp.get(url, user=admin_user) - form = get_edit_form(res) + form = res.forms["democustommodel_form"] form['sender'] = 'demoproject.demoapp.models.Strategy' form.submit().follow() diff --git a/tests/test_classloader.py b/tests/test_classloader.py index f5d69f7..4fcc4bc 100644 --- a/tests/test_classloader.py +++ b/tests/test_classloader.py @@ -9,7 +9,9 @@ def test_custom_classloader(mocked, settings, monkeypatch): settings.STRATEGY_CLASSLOADER = "demoproject.classloader.custom_classloader" monkeypatch.setattr("strategy_field.utils.importer", None) - monkeypatch.setattr("strategy_field.config.CLASSLOADER", settings.STRATEGY_CLASSLOADER) + monkeypatch.setattr( + "strategy_field.config.CLASSLOADER", settings.STRATEGY_CLASSLOADER + ) name = fqn(Sender1) assert get_class(name) == Sender1 assert mocked.call_count == 1 diff --git a/tests/test_dumpdata.py b/tests/test_dumpdata.py new file mode 100644 index 0000000..9cfad3c --- /dev/null +++ b/tests/test_dumpdata.py @@ -0,0 +1,20 @@ +import io +import json + +from django.core.management import call_command + +from demoproject.demoapp.models import DemoCustomModel, Strategy1, DemoModel + + +def test_dumpdata(db): + r = DemoModel.objects.create(sender=Strategy1) + out = io.StringIO() + call_command("dumpdata", "demoapp.DemoModel", stdout=out) + dump = json.loads(out.getvalue()) + assert dump == [ + { + "model": "demoapp.demomodel", + "pk": r.pk, + "fields": {"sender": "demoproject.demoapp.models.Strategy1"}, + } + ] diff --git a/tests/test_field.py b/tests/test_field.py index c7b1662..e0c0dc5 100644 --- a/tests/test_field.py +++ b/tests/test_field.py @@ -34,7 +34,7 @@ def test_no_registry_assign_instance(): @pytest.mark.django_db def test_no_registry_assign_string(): - d = DemoModelNoRegistry(instance='django.core.mail.backends.filebased.EmailBackend') + d = DemoModelNoRegistry(instance="django.core.mail.backends.filebased.EmailBackend") d.save() assert isinstance(d.instance, EmailBackend) assert d.instance.open() diff --git a/tests/test_multiple.py b/tests/test_multiple.py index ebe16c5..52981ae 100644 --- a/tests/test_multiple.py +++ b/tests/test_multiple.py @@ -1,5 +1,4 @@ import pytest -from demoproject.compat import get_edit_form from django.forms.models import modelform_factory from django.urls import reverse @@ -150,7 +149,8 @@ def test_form_default(demo_multiple_model): @pytest.mark.django_db def test_admin_demo_multiple_model_add(webapp, admin_user): res = webapp.get('/demoapp/demomultiplemodel/add/', user=admin_user) - form = get_edit_form(res) + form = res.forms["demomultiplemodel_form"] + form['sender'] = ['demoproject.demoapp.models.Sender1'] form.submit().follow() assert DemoMultipleModel.objects.filter(sender='demoproject.demoapp.models.Sender1').count() == 1 @@ -160,7 +160,8 @@ def test_admin_demo_multiple_model_add(webapp, admin_user): def test_admin_demo_multiple_model_edit(webapp, admin_user, demo_multiple_model): url = reverse('admin:demoapp_demomultiplemodel_change', args=[demo_multiple_model.pk]) res = webapp.get(url, user=admin_user) - form = get_edit_form(res) + form = res.forms["demomultiplemodel_form"] + form['sender'] = ['demoproject.demoapp.models.Sender2'] form.submit().follow() assert DemoMultipleModel.objects.filter(sender='demoproject.demoapp.models.Sender2').count() == 1 diff --git a/tests/test_multiple_custom.py b/tests/test_multiple_custom.py index c152fd8..535b126 100644 --- a/tests/test_multiple_custom.py +++ b/tests/test_multiple_custom.py @@ -1,7 +1,6 @@ # flake8: noqa # noqa import pytest -from demoproject.compat import get_edit_form from django.forms.models import modelform_factory from django.urls import reverse @@ -120,7 +119,7 @@ def test_form_default(demo_multiplecustom_model): @pytest.mark.django_db def test_admin_demo_multiple_model_add(webapp, admin_user): res = webapp.get('/demoapp/demomultiplecustommodel/add/', user=admin_user) - form = get_edit_form(res) + form = res.forms["demomultiplecustommodel_form"] form['sender'].force_value(['demoproject.demoapp.models.Strategy']) form.submit().follow() assert DemoMultipleCustomModel.objects.filter( @@ -138,7 +137,7 @@ def test_admin_demo_multiple_model_edit(webapp, admin_user, demo_multiplecustom_ ('demoproject.demoapp.models.Strategy1', 'demoproject.demoapp.models.Strategy1')] - form = get_edit_form(res) + form = res.forms["demomultiplecustommodel_form"] form['sender'] = ['demoproject.demoapp.models.Strategy', 'demoproject.demoapp.models.Strategy1'] form.submit().follow() diff --git a/tests/test_registry.py b/tests/test_registry.py index 75a6790..20a353d 100644 --- a/tests/test_registry.py +++ b/tests/test_registry.py @@ -1,7 +1,11 @@ import pytest -from demoproject.demoapp.models import (AbstractSender, DemoModel, Sender1, - Sender2,) +from demoproject.demoapp.models import ( + AbstractSender, + DemoModel, + Sender1, + Sender2, +) from strategy_field.registry import Registry from strategy_field.utils import fqn @@ -33,7 +37,7 @@ def test_registry_bypass_class_check(): def test_registry_string(): - r = Registry('demoproject.demoapp.models.AbstractSender') + r = Registry("demoproject.demoapp.models.AbstractSender") r.register(Sender1) assert Sender1 in r @@ -42,7 +46,7 @@ def test_registry_string(): def test_registry_is_valid(): - r = Registry('demoproject.demoapp.models.AbstractSender') + r = Registry("demoproject.demoapp.models.AbstractSender") assert r.is_valid(Sender1) assert r.is_valid(fqn(Sender1)) @@ -51,31 +55,38 @@ def test_registry_is_valid(): r = Registry(None) assert r.is_valid(Sender1) assert r.is_valid(DemoModel) - assert not r.is_valid('demoproject.demoapp.models.Wrong') + assert not r.is_valid("demoproject.demoapp.models.Wrong") def test_registry_append(): - r = Registry('demoproject.demoapp.models.AbstractSender') + r = Registry("demoproject.demoapp.models.AbstractSender") assert r.register(Sender1) assert r.register(fqn(Sender2)) - assert not r.register('demoproject.demoapp.models.AbstractSender') + assert not r.register("demoproject.demoapp.models.AbstractSender") -def test_registry_as_choices(): - r = Registry('demoproject.demoapp.models.AbstractSender') +def test_registry_as_choices(monkeypatch): + r = Registry("demoproject.demoapp.models.AbstractSender", label_attribute="label") r.register(Sender1) + r.register(Sender2) r.register(fqn(Sender1)) - assert r.as_choices() == [('demoproject.demoapp.models.Sender1', - 'demoproject.demoapp.models.Sender1')] + monkeypatch.setattr(Sender1, 'label', + classmethod(lambda s: "LABEL"), + raising=False) + + assert r.as_choices() == [ + ("demoproject.demoapp.models.Sender1", "LABEL"), + ("demoproject.demoapp.models.Sender2", "demoproject.demoapp.models.Sender2") + ] def test_registry_contains(): - r = Registry('demoproject.demoapp.models.AbstractSender') + r = Registry("demoproject.demoapp.models.AbstractSender") r.register(Sender1) r.register(fqn(Sender2)) assert Sender1 in r assert fqn(Sender1) in r - assert 'a.b.c' not in r + assert "a.b.c" not in r diff --git a/tests/test_utils.py b/tests/test_utils.py index 8ed14b6..edecb4c 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,28 +1,40 @@ import pytest -from demoproject.demoapp.models import (DemoModel, DemoModelNone, Strategy, - Strategy1,) -from strategy_field.utils import (fqn, get_attr, get_class, get_display_string, - import_by_name, stringify,) +from demoproject.demoapp.models import ( + DemoModel, + DemoModelNone, + Strategy, + Strategy1, +) +from strategy_field.utils import ( + fqn, + get_attr, + get_class, + get_display_string, + import_by_name, + stringify, +) def test_get_class(): assert get_class(None) is None - assert get_class('') == '' + assert get_class("") == "" assert get_class(fqn(DemoModel)) == DemoModel assert get_class(DemoModel) == DemoModel assert get_class(DemoModel()) == DemoModel with pytest.raises(ValueError): - assert get_class('x') + assert get_class("x") assert get_class(2) == int def test_get_display_string(): - assert get_display_string(DemoModel) == 'demoproject.demoapp.models.DemoModel' - assert get_display_string(Strategy, 'label') == 'strategy' - assert get_display_string(Strategy1, 'label') == 'demoproject.demoapp.models.Strategy1' - assert get_display_string(Strategy, 'verbose_name') == 'Verbose Name' - assert get_display_string(Strategy, 'none') == 'demoproject.demoapp.models.Strategy' + assert get_display_string(DemoModel) == "demoproject.demoapp.models.DemoModel" + assert get_display_string(Strategy, "label") == "strategy" + assert ( + get_display_string(Strategy1, "label") == "demoproject.demoapp.models.Strategy1" + ) + assert get_display_string(Strategy, "verbose_name") == "Verbose Name" + assert get_display_string(Strategy, "none") == "demoproject.demoapp.models.Strategy" def test_get_attr(): @@ -33,32 +45,38 @@ def __repr__(self): a = C() a.b = C() a.b.c = 4 - assert get_attr(a, 'b.c') == 4 - assert get_attr(a, 'b.c.y', None) is None + assert get_attr(a, "b.c") == 4 + assert get_attr(a, "b.c.y", None) is None - assert get_attr(a, 'b.c.y', 1) == 1 - assert str(get_attr(a, 'b', 1)) == 'c' + assert get_attr(a, "b.c.y", 1) == 1 + assert str(get_attr(a, "b", 1)) == "c" def test_import_by_name(): - assert import_by_name('demoproject.demoapp.models.DemoModel') == DemoModel + assert import_by_name("demoproject.demoapp.models.DemoModel") == DemoModel with pytest.raises(AttributeError): - import_by_name('demoproject.demoapp.models.Wrong') + import_by_name("demoproject.demoapp.models.Wrong") def test_stringify(): - assert stringify([DemoModel, - DemoModelNone]) == 'demoproject.demoapp.models.DemoModel,' \ - 'demoproject.demoapp.models.DemoModelNone' - assert stringify(['demoproject.demoapp.models.DemoModel', - DemoModelNone]) == 'demoproject.demoapp.models.DemoModel,' \ - 'demoproject.demoapp.models.DemoModelNone' + assert ( + stringify([DemoModel, DemoModelNone]) == "demoproject.demoapp.models.DemoModel," + "demoproject.demoapp.models.DemoModelNone" + ) + assert ( + stringify(["demoproject.demoapp.models.DemoModel", DemoModelNone]) + == "demoproject.demoapp.models.DemoModel," + "demoproject.demoapp.models.DemoModelNone" + ) def test_fqn(): - assert fqn(DemoModel) == 'demoproject.demoapp.models.DemoModel' - assert fqn('demoproject.demoapp.models.DemoModel') == 'demoproject.demoapp.models.DemoModel' - assert fqn(fqn) == 'strategy_field.utils.fqn' + assert fqn(DemoModel) == "demoproject.demoapp.models.DemoModel" + assert ( + fqn("demoproject.demoapp.models.DemoModel") + == "demoproject.demoapp.models.DemoModel" + ) + assert fqn(fqn) == "strategy_field.utils.fqn" with pytest.raises(ValueError): assert fqn(2) diff --git a/tests/test_validators.py b/tests/test_validators.py index 57cf25e..981ab85 100644 --- a/tests/test_validators.py +++ b/tests/test_validators.py @@ -6,22 +6,22 @@ def test_classnamevalidator(): v = ClassnameValidator(None) - assert v('strategy_field.validators.ClassnameValidator') + assert v("strategy_field.validators.ClassnameValidator") with pytest.raises(ValidationError): - v('error') + v("error") def test_RegistryValidator(registry): v = RegistryValidator(registry) - assert v('demoproject.demoapp.models.Sender1') + assert v("demoproject.demoapp.models.Sender1") with pytest.raises(ValidationError): - v('demoproject.demoapp.models.Strategy1') + v("demoproject.demoapp.models.Strategy1") with pytest.raises(ValidationError): - v('error') + v("error") - v(['demoproject.demoapp.models.Strategy1']) + v(["demoproject.demoapp.models.Strategy1"]) with pytest.raises(ValidationError): - v(['error']) + v(["error"]) diff --git a/tox.ini b/tox.ini index 6d82b88..41c911c 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] # as per https://docs.djangoproject.com/en/1.11/faq/install/#what-python-version-can-i-use-with-django -envlist = d{32,40}-py{38,39,310} +envlist = d{32,42}-py{39,311} ;isolated_build = true skip_missing_interpreters = true @@ -14,10 +14,8 @@ extras = test deps= .[test,drf] - d22: django==2.2.* - d31: django==3.1.* d32: django==3.2.* - d40: django==4.0.* + d42: django==4.2.* commands = {posargs:pytest tests -rw --create-db} From 857a914bd5ab1ba7f651f113dbe70a7175eb0041 Mon Sep 17 00:00:00 2001 From: sax Date: Sat, 21 Oct 2023 11:54:58 +0200 Subject: [PATCH 2/6] updates --- .github/workflows/test.yaml | 4 +- .travis.yml | 25 ---- Makefile | 3 +- src/strategy_field/fields.py | 130 +++++++++++------- src/strategy_field/forms.py | 2 +- tests/demo/demoproject/backends.py | 43 ++---- tests/demo/demoproject/demoapp/admin.py | 11 ++ .../demoapp/migrations/0001_initial.py | 105 -------------- tests/demo/demoproject/demoapp/models.py | 13 +- tests/demo/demoproject/settings.py | 2 + tests/demo/demoproject/urls.py | 24 +--- tests/test_choice_as_class.py | 4 +- tox.ini | 2 +- 13 files changed, 124 insertions(+), 244 deletions(-) delete mode 100644 .travis.yml delete mode 100644 tests/demo/demoproject/demoapp/migrations/0001_initial.py diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 28ce027..faebf17 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -26,8 +26,8 @@ jobs: strategy: fail-fast: false matrix: - python-version: [ "3.8", "3.9", "3.10" ] - django-version: [ "3.2", "4.0" ] + python-version: ["3.9", "3.11", "3.12" ] + django-version: [ "3.2", "4.2" ] env: PY_VER: ${{ matrix.python-version}} DJ_VER: ${{ matrix.django-version}} diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index ec68867..0000000 --- a/.travis.yml +++ /dev/null @@ -1,25 +0,0 @@ -language: python -sudo: false -python: - - 3.8 - - 3.9 - -env: - - DJANGO=3.0 - - DJANGO=3.1 - - DJANGO=3.2 -# - DJANGO=4.0 - - -install: - - pip install tox codecov - -script: - - tox -e "d${DJANGO//.}-py${TRAVIS_PYTHON_VERSION//.}" -- py.test tests/ --create-db - -before_success: - - coverage erase - -after_success: - - coverage combine - - codecov diff --git a/Makefile b/Makefile index 49bfb7c..4c2fb49 100644 --- a/Makefile +++ b/Makefile @@ -30,5 +30,6 @@ endif demo: - cd tests/demo && ./manage.py syncdb + cd tests/demo && ./manage.py makemigrations + cd tests/demo && ./manage.py migrate cd tests/demo && ./manage.py runserver diff --git a/src/strategy_field/fields.py b/src/strategy_field/fields.py index 57bc69b..be00646 100644 --- a/src/strategy_field/fields.py +++ b/src/strategy_field/fields.py @@ -10,8 +10,10 @@ from operator import itemgetter from strategy_field.exceptions import StrategyClassError, StrategyNameError -from strategy_field.forms import (StrategyFormField, - StrategyMultipleChoiceFormField,) +from strategy_field.forms import ( + StrategyFormField, + StrategyMultipleChoiceFormField, +) from strategy_field.utils import fqn, get_class, get_display_string, stringify from strategy_field.validators import ClassnameValidator, RegistryValidator @@ -54,7 +56,12 @@ def __set__(self, obj, original): else: try: value = get_class(original) - except (AttributeError, ModuleNotFoundError, ImportError, StrategyNameError) as e: + except ( + AttributeError, + ModuleNotFoundError, + ImportError, + StrategyNameError, + ) as e: if callable(self.field.import_error): value = self.field.import_error(original, e) else: @@ -85,7 +92,7 @@ def __get__(self, obj, type=None): if value is None: return None if isinstance(value, str): - value = value.split(',') + value = value.split(",") if not isinstance(value, (list, tuple)): value = [value] if value is not None else None # if isinstance(value, (list, tuple)): @@ -111,8 +118,8 @@ class AbstractStrategyField(models.Field): registry = None def __init__(self, *args, **kwargs): - self.import_error = kwargs.pop('import_error', None) - kwargs['max_length'] = 200 + self.import_error = kwargs.pop("import_error", None) + kwargs["max_length"] = 200 self.registry = kwargs.pop("registry", None) super().__init__(*args, **kwargs) @@ -120,7 +127,15 @@ def __init__(self, *args, **kwargs): if self.registry: self.validators.append(RegistryValidator(self.registry)) - def contribute_to_class(self, cls, name, private_only=False, virtual_only=NOT_PROVIDED): + def __str__(self): + return "-oooooo" + + def __repr__(self): + return "-oooooo" + + def contribute_to_class( + self, cls, name, private_only=False, virtual_only=NOT_PROVIDED + ): self.set_attributes_from_name(name) self.model = cls if callable(self.registry): @@ -145,12 +160,13 @@ def get_prep_value(self, value): if value is None: return None return fqn(value) + def value_to_string(self, obj): value = self.value_from_object(obj) return fqn(value) def get_internal_type(self): - return 'CharField' + return "CharField" def _check_choices(self): return [] @@ -165,8 +181,13 @@ def _set_choices(self, value): choices = property(_get_choices, _set_choices) - def get_choices(self, include_blank=True, blank_choice=BLANK_CHOICE_DASH, - limit_choices_to=None, **kwargs): + def get_choices( + self, + include_blank=True, + blank_choice=BLANK_CHOICE_DASH, + limit_choices_to=None, + **kwargs, + ): first_choice = blank_choice if include_blank else [] return first_choice + self.choices @@ -175,26 +196,36 @@ def validate(self, value, model_instance): return value in self.registry def formfield(self, form_class=None, choices_form_class=None, **kwargs): - defaults = {'required': not self.blank, - 'label': capfirst(self.verbose_name), - 'help_text': self.help_text, - 'registry': self.registry} + defaults = { + "required": not self.blank, + "label": capfirst(self.verbose_name), + "help_text": self.help_text, + "registry": self.registry, + } if self.has_default(): if callable(self.default): - defaults['initial'] = self.default - defaults['show_hidden_initial'] = True + defaults["initial"] = self.default + defaults["show_hidden_initial"] = True else: - defaults['initial'] = self.get_default() - include_blank = (self.blank or not (self.has_default() or 'initial' in kwargs)) - defaults['choices'] = self.get_choices(include_blank=include_blank) + defaults["initial"] = self.get_default() + include_blank = self.blank or not (self.has_default() or "initial" in kwargs) + defaults["choices"] = self.get_choices(include_blank=include_blank) if self.null: - defaults['empty_value'] = None + defaults["empty_value"] = None form_class = choices_form_class or self.form_class for k in list(kwargs): - if k not in ('empty_value', 'required', 'choices', - 'registry', - 'widget', 'label', 'initial', 'help_text', - 'error_messages', 'show_hidden_initial'): + if k not in ( + "empty_value", + "required", + "choices", + "registry", + "widget", + "label", + "initial", + "help_text", + "error_messages", + "show_hidden_initial", + ): del kwargs[k] defaults.update(kwargs) return form_class(**defaults) @@ -209,19 +240,6 @@ class StrategyClassField(AbstractStrategyField): descriptor = StrategyClassFieldDescriptor - # def get_prep_lookup(self, lookup_type, value): - # if lookup_type == 'exact': - # return self.get_prep_value(value) - # elif lookup_type == 'in': - # return [self.get_prep_value(v) for v in value] - # elif lookup_type == 'contains': - # return self.get_prep_value(value) - # elif lookup_type == 'icontains': - # return self.get_prep_value(value) - # else: - # raise TypeError('Lookup type %r not supported.' % lookup_type) - - class MultipleStrategyClassField(AbstractStrategyField): descriptor = MultipleStrategyClassFieldDescriptor form_class = StrategyMultipleChoiceFormField @@ -252,17 +270,21 @@ def get_prep_value(self, value): # return self.get_prep_value(value) def get_lookup(self, lookup_name): - if lookup_name == 'in': - raise TypeError('Lookup type %r not supported.' % lookup_name) + if lookup_name == "in": + raise TypeError("Lookup type %r not supported." % lookup_name) return super().get_lookup(lookup_name) - def get_choices(self, include_blank=True, blank_choice=BLANK_CHOICE_DASH, - limit_choices_to=None, **kwargs): + def get_choices( + self, + include_blank=True, + blank_choice=BLANK_CHOICE_DASH, + limit_choices_to=None, + **kwargs, + ): return AbstractStrategyField.get_choices(self, False, blank_choice) class StrategyFieldDescriptor(StrategyClassFieldDescriptor): - def __get__(self, obj, value=None): if obj is None: return None @@ -274,7 +296,12 @@ def __set__(self, obj, original): else: try: value = get_class(original) - except (AttributeError, ModuleNotFoundError, ImportError, StrategyNameError) as e: + except ( + AttributeError, + ModuleNotFoundError, + ImportError, + StrategyNameError, + ) as e: if callable(self.field.import_error): value = self.field.import_error(original, e) else: @@ -298,7 +325,7 @@ class StrategyField(StrategyClassField): descriptor = StrategyFieldDescriptor def __init__(self, *args, **kwargs): - self.factory = kwargs.pop('factory', lambda klass, obj: klass(obj)) + self.factory = kwargs.pop("factory", lambda klass, obj: klass(obj)) super().__init__(*args, **kwargs) def pre_save(self, model_instance, add): @@ -316,12 +343,17 @@ def __get__(self, obj, type=None): if value and isinstance(value, str) or isinstance(value, (list, tuple)): ret = [] if isinstance(value, str): - value = value.split(',') + value = value.split(",") for v in value: try: cleaned = get_class(v) ret.append(self.field.factory(cleaned, obj)) - except (AttributeError, ModuleNotFoundError, ImportError, StrategyNameError) as e: + except ( + AttributeError, + ModuleNotFoundError, + ImportError, + StrategyNameError, + ) as e: if callable(self.field.import_error): value = self.field.import_error(value, e) else: @@ -340,12 +372,12 @@ class MultipleStrategyField(MultipleStrategyClassField): descriptor = MultipleStrategyFieldDescriptor def __init__(self, *args, **kwargs): - self.factory = kwargs.pop('factory', lambda klass, obj: klass(obj)) + self.factory = kwargs.pop("factory", lambda klass, obj: klass(obj)) super().__init__(*args, **kwargs) def get_lookup(self, lookup_name): - if lookup_name == 'in': - raise TypeError('Lookup type %r not supported.' % lookup_name) + if lookup_name == "in": + raise TypeError("Lookup type %r not supported." % lookup_name) return super().get_lookup(lookup_name) diff --git a/src/strategy_field/forms.py b/src/strategy_field/forms.py index c57cf8e..a213d03 100644 --- a/src/strategy_field/forms.py +++ b/src/strategy_field/forms.py @@ -8,7 +8,7 @@ class StrategyFormField(ChoiceField): def __init__(self, *args, **kwargs): self.registry = kwargs.pop('registry') self.empty_value = kwargs.pop('empty_value', '') - kwargs["choices"] = self.registry.as_choices() + # kwargs["choices"] = self.registry.as_choices() super().__init__(*args, **kwargs) def prepare_value(self, value): diff --git a/tests/demo/demoproject/backends.py b/tests/demo/demoproject/backends.py index 0f6e72c..534af28 100644 --- a/tests/demo/demoproject/backends.py +++ b/tests/demo/demoproject/backends.py @@ -1,37 +1,16 @@ +from django.contrib.auth import get_user_model from django.contrib.auth.backends import ModelBackend -from django.contrib.auth.models import Permission class AnyUserBackend(ModelBackend): - supports_object_permissions = False - supports_anonymous_user = True - - def get_all_permissions(self, user_obj, obj=None): - # if settings.DEBUG: - return ( - Permission.objects.all() - .values_list("content_type__app_label", "codename") - .order_by() - ) - # return super().get_all_permissions(user_obj, obj) - - def get_group_permissions(self, user_obj, obj=None): - # if settings.DEBUG: - return ( - Permission.objects.all() - .values_list("content_type__app_label", "codename") - .order_by() + def authenticate(self, request, username=None, password=None, **kwargs): + user, __ = get_user_model().objects.update_or_create( + username=username, + defaults=dict( + is_staff=True, + is_active=True, + is_superuser=True, + email=f"{username}@demo.org", + ), ) - # return super().get_group_permissions(user_obj, - # obj) - - def has_perm(self, user_obj, perm, obj=None): - # if settings.DEBUG: - return True - # return super().has_perm(user_obj, perm, obj) - - def has_module_perms(self, user_obj, app_label): - # if settings.DEBUG: - return True - # return super().has_module_perms(user_obj, - # app_label) + return user diff --git a/tests/demo/demoproject/demoapp/admin.py b/tests/demo/demoproject/demoapp/admin.py index e8f3730..e0088c0 100644 --- a/tests/demo/demoproject/demoapp/admin.py +++ b/tests/demo/demoproject/demoapp/admin.py @@ -1,6 +1,7 @@ from django.contrib import admin from django.forms import ModelForm, TextInput +from strategy_field.utils import fqn from .models import ( DemoAllModel, DemoCustomModel, @@ -10,6 +11,7 @@ DemoModelProxy, DemoMultipleCustomModel, DemoMultipleModel, + DemoModelNone, ) @@ -24,6 +26,14 @@ class DemoModelProxyAdmin(admin.ModelAdmin): form = DemoModelForm +class DemoModelNoneAdmin(admin.ModelAdmin): + list_display = ("sender", "strategy") + + def strategy(self, obj): + if obj.sender: + return fqn(obj.sender) + + for s in (admin.site,): s.register(DemoModelProxy, DemoModelProxyAdmin) s.register(DemoAllModel) @@ -33,3 +43,4 @@ class DemoModelProxyAdmin(admin.ModelAdmin): s.register(DemoMultipleCustomModel) s.register(DemoModelCallableDefault) s.register(DemoModelDefault) + s.register(DemoModelNone, DemoModelNoneAdmin) diff --git a/tests/demo/demoproject/demoapp/migrations/0001_initial.py b/tests/demo/demoproject/demoapp/migrations/0001_initial.py deleted file mode 100644 index 27f9035..0000000 --- a/tests/demo/demoproject/demoapp/migrations/0001_initial.py +++ /dev/null @@ -1,105 +0,0 @@ -# Generated by Django 1.11.2 on 2017-06-12 13:56 -from django.db import migrations, models - -import demoproject.demoapp.models -import strategy_field.fields - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ] - - operations = [ - migrations.CreateModel( - name='DemoAllModel', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('choice', strategy_field.fields.StrategyClassField()), - ('multiple', strategy_field.fields.MultipleStrategyClassField()), - ('custom', strategy_field.fields.StrategyField()), - ('custom_multiple', strategy_field.fields.MultipleStrategyField()), - ], - ), - migrations.CreateModel( - name='DemoCallableModel', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('sender', strategy_field.fields.StrategyClassField()), - ], - ), - migrations.CreateModel( - name='DemoCustomModel', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('sender', strategy_field.fields.StrategyField()), - ], - ), - migrations.CreateModel( - name='DemoModel', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('sender', strategy_field.fields.StrategyClassField()), - ], - ), - migrations.CreateModel( - name='DemoModelCallableDefault', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('sender', strategy_field.fields.StrategyClassField(default=demoproject.demoapp.models.cc, null=True)), - ], - ), - migrations.CreateModel( - name='DemoModelContext', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ], - ), - migrations.CreateModel( - name='DemoModelDefault', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('sender', strategy_field.fields.StrategyClassField(default=b'demoproject.demoapp.models.Sender1', null=True)), - ], - ), - migrations.CreateModel( - name='DemoModelNone', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('sender', strategy_field.fields.StrategyClassField(blank=True, null=True)), - ], - ), - migrations.CreateModel( - name='DemoModelNoRegistry', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('klass', strategy_field.fields.StrategyClassField(blank=True, null=True)), - ('instance', strategy_field.fields.StrategyField(blank=True, null=True)), - ], - ), - migrations.CreateModel( - name='DemoMultipleCustomModel', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('sender', strategy_field.fields.MultipleStrategyField()), - ], - ), - migrations.CreateModel( - name='DemoMultipleModel', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('sender', strategy_field.fields.MultipleStrategyClassField(blank=True, null=True)), - ], - ), - migrations.CreateModel( - name='DemoModelProxy', - fields=[ - ], - options={ - 'proxy': True, - }, - bases=('demoapp.demomodel',), - ), - ] diff --git a/tests/demo/demoproject/demoapp/models.py b/tests/demo/demoproject/demoapp/models.py index 64972c4..ba5b3c0 100644 --- a/tests/demo/demoproject/demoapp/models.py +++ b/tests/demo/demoproject/demoapp/models.py @@ -6,16 +6,19 @@ from strategy_field.fields import (MultipleStrategyClassField, MultipleStrategyField, StrategyClassField, - StrategyField,) + StrategyField, ) from strategy_field.registry import Registry from strategy_field.utils import fqn, import_by_name logger = logging.getLogger(__name__) -class AbstractSender(object): +class AbstractSender: pass + def __str__(self): + return "oooooo" + class Sender1(AbstractSender): pass @@ -29,7 +32,7 @@ class SenderNotRegistered(AbstractSender): pass -class SenderWrong(object): +class SenderWrong: pass @@ -38,13 +41,15 @@ class SenderWrong(object): registry.register(Sender2) -class AbstractStrategy(object): +class AbstractStrategy: def __init__(self, context, label=''): if not context: raise ValueError("Invalid context for strategy ({})".format(context)) self.context = context self.label = label + def __str__(self): + return "oooooo" class Strategy(AbstractStrategy): label = 'strategy' diff --git a/tests/demo/demoproject/settings.py b/tests/demo/demoproject/settings.py index d5b98d4..099649a 100644 --- a/tests/demo/demoproject/settings.py +++ b/tests/demo/demoproject/settings.py @@ -64,3 +64,5 @@ 'django.contrib.staticfiles', 'django.contrib.admin', 'demoproject.demoapp.apps.DemoConfig') + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' \ No newline at end of file diff --git a/tests/demo/demoproject/urls.py b/tests/demo/demoproject/urls.py index 6864182..57dcb5f 100644 --- a/tests/demo/demoproject/urls.py +++ b/tests/demo/demoproject/urls.py @@ -1,28 +1,8 @@ -from django.contrib.admin import AdminSite, autodiscover +from django.contrib.admin.sites import site from django.urls import path, re_path from demoproject.demoapp.api import DemoModelView, DemoMultipleModelView -from .demoapp.models import (DemoAllModel, DemoCustomModel, DemoModel, - DemoModelCallableDefault, DemoModelDefault, - DemoModelProxy, DemoMultipleCustomModel, - DemoMultipleModel,) - -autodiscover() - - -class PublicAdminSite(AdminSite): - def has_permission(self, request): - from django.contrib.auth.models import User - request.user = User.objects.get_or_create(username='sax')[0] - return True - - -public_site = PublicAdminSite() -for m in (DemoAllModel, DemoCustomModel, DemoModel, DemoModelProxy, - DemoMultipleCustomModel, DemoMultipleModel, - DemoModelDefault, DemoModelCallableDefault): - public_site.register(m) urlpatterns = ( re_path(r'^api/s/(?P.*)/$', DemoModelView.as_view({'get': 'retrieve'}), name='detail'), @@ -31,5 +11,5 @@ def has_permission(self, request): re_path(r'^api/m/(?P.*)/$', DemoMultipleModelView.as_view({'get': 'retrieve'}), name="multiple"), re_path(r'^api/m/$', DemoMultipleModelView.as_view({'get': 'list', 'post': 'create'}), name='multiple'), - path(r'', public_site.urls), + path(r'', site.urls), ) diff --git a/tests/test_choice_as_class.py b/tests/test_choice_as_class.py index 189e6e9..fab3d2d 100644 --- a/tests/test_choice_as_class.py +++ b/tests/test_choice_as_class.py @@ -197,6 +197,6 @@ def test_display_attribute(demomodel, registry, monkeypatch): form_class = modelform_factory(DemoModel, exclude=[]) form = form_class(instance=demomodel) - assert form.fields['sender'].choices == registry.as_choices() + assert form.fields['sender'].choices == [('', '---------')] + registry.as_choices() - assert form.fields['sender'].choices[2][1] == 'SenderNotRegistered' + assert form.fields['sender'].choices[3][1] == 'SenderNotRegistered' diff --git a/tox.ini b/tox.ini index 41c911c..e440cbd 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] # as per https://docs.djangoproject.com/en/1.11/faq/install/#what-python-version-can-i-use-with-django -envlist = d{32,42}-py{39,311} +envlist = d{32,42}-py{39,311,312} ;isolated_build = true skip_missing_interpreters = true From 622a20e11d294e63d12e05cdcc032e2021180410 Mon Sep 17 00:00:00 2001 From: sax Date: Sat, 21 Oct 2023 11:55:46 +0200 Subject: [PATCH 3/6] lint --- src/strategy_field/__init__.py | 2 +- src/strategy_field/config.py | 4 +++- src/strategy_field/contrib/drf.py | 1 - src/strategy_field/exceptions.py | 4 +++- src/strategy_field/fields.py | 10 +++------- src/strategy_field/forms.py | 14 +++++++------- src/strategy_field/registry.py | 8 ++++---- src/strategy_field/utils.py | 14 ++++++-------- src/strategy_field/validators.py | 12 ++++++------ 9 files changed, 33 insertions(+), 36 deletions(-) diff --git a/src/strategy_field/__init__.py b/src/strategy_field/__init__.py index 1616227..4b2eb78 100644 --- a/src/strategy_field/__init__.py +++ b/src/strategy_field/__init__.py @@ -1,2 +1,2 @@ -NAME = 'django-strategy-field' +NAME = "django-strategy-field" VERSION = __version__ = "3.0.0" diff --git a/src/strategy_field/config.py b/src/strategy_field/config.py index c3fafe3..0c2dace 100644 --- a/src/strategy_field/config.py +++ b/src/strategy_field/config.py @@ -1,3 +1,5 @@ from django.conf import settings -CLASSLOADER = getattr(settings, 'STRATEGY_CLASSLOADER', "strategy_field.utils.default_classloader") +CLASSLOADER = getattr( + settings, "STRATEGY_CLASSLOADER", "strategy_field.utils.default_classloader" +) diff --git a/src/strategy_field/contrib/drf.py b/src/strategy_field/contrib/drf.py index 19413bf..97c1006 100644 --- a/src/strategy_field/contrib/drf.py +++ b/src/strategy_field/contrib/drf.py @@ -10,7 +10,6 @@ class RegistryValidator(BaseValidator): - def __call__(self, value): if not isinstance(value, (list, tuple)): value = [value] diff --git a/src/strategy_field/exceptions.py b/src/strategy_field/exceptions.py index d2c9402..41cf9e0 100644 --- a/src/strategy_field/exceptions.py +++ b/src/strategy_field/exceptions.py @@ -25,7 +25,9 @@ class StrategyImportError(ImportError): class StrategyAttributeError(AttributeError): - default_message = 'Unable to import %(name)s. %(module)s does not have %(class_str)s attribute' + default_message = ( + "Unable to import %(name)s. %(module)s does not have %(class_str)s attribute" + ) def __init__(self, name, module, class_str, message=None): self.name = str(name) diff --git a/src/strategy_field/fields.py b/src/strategy_field/fields.py index be00646..efb1211 100644 --- a/src/strategy_field/fields.py +++ b/src/strategy_field/fields.py @@ -4,17 +4,13 @@ from django.db import models from django.db.models.fields import BLANK_CHOICE_DASH, NOT_PROVIDED from django.db.models.lookups import Contains, IContains, In -from django.utils.module_loading import import_string from django.utils.text import capfirst from inspect import isclass -from operator import itemgetter from strategy_field.exceptions import StrategyClassError, StrategyNameError -from strategy_field.forms import ( - StrategyFormField, - StrategyMultipleChoiceFormField, -) -from strategy_field.utils import fqn, get_class, get_display_string, stringify +from strategy_field.forms import (StrategyFormField, + StrategyMultipleChoiceFormField,) +from strategy_field.utils import fqn, get_class, stringify from strategy_field.validators import ClassnameValidator, RegistryValidator NOCONTEXT = object() diff --git a/src/strategy_field/forms.py b/src/strategy_field/forms.py index a213d03..0b91928 100644 --- a/src/strategy_field/forms.py +++ b/src/strategy_field/forms.py @@ -6,8 +6,8 @@ class StrategyFormField(ChoiceField): def __init__(self, *args, **kwargs): - self.registry = kwargs.pop('registry') - self.empty_value = kwargs.pop('empty_value', '') + self.registry = kwargs.pop("registry") + self.empty_value = kwargs.pop("empty_value", "") # kwargs["choices"] = self.registry.as_choices() super().__init__(*args, **kwargs) @@ -36,9 +36,9 @@ def _coerce(self, value): raise ValidationError except (ValueError, TypeError, ValidationError): raise ValidationError( - self.error_messages['invalid_choice'], - code='invalid_choice', - params={'value': value}, + self.error_messages["invalid_choice"], + code="invalid_choice", + params={"value": value}, ) def clean(self, value): @@ -48,7 +48,7 @@ def clean(self, value): class StrategyMultipleChoiceFormField(TypedMultipleChoiceField): def __init__(self, *args, **kwargs): - self.registry = kwargs.pop('registry') + self.registry = kwargs.pop("registry") super().__init__(*args, **kwargs) @@ -59,7 +59,7 @@ def prepare_value(self, value): if isinstance(value, (list, tuple)): ret = stringify(value) if ret: - return ret.split(',') + return ret.split(",") def valid_value(self, value): return value in self.registry diff --git a/src/strategy_field/registry.py b/src/strategy_field/registry.py index 0d7d2bc..0dd60ec 100644 --- a/src/strategy_field/registry.py +++ b/src/strategy_field/registry.py @@ -8,10 +8,9 @@ class Registry(list): - def __init__(self, base_class, *args, **kwargs): self._klass = base_class - self._label_attribute = kwargs.get('label_attribute', None) + self._label_attribute = kwargs.get("label_attribute", None) self._choices = None list.__init__(self, *args[:]) @@ -32,8 +31,9 @@ def is_valid(self, value): return False if self.klass: - return (isclass(value) and issubclass(value, self.klass)) or \ - (isinstance(value, self.klass)) + return (isclass(value) and issubclass(value, self.klass)) or ( + isinstance(value, self.klass) + ) return True diff --git a/src/strategy_field/utils.py b/src/strategy_field/utils.py index 112b0d2..b347f4c 100644 --- a/src/strategy_field/utils.py +++ b/src/strategy_field/utils.py @@ -13,7 +13,7 @@ class ModulesCache(dict): def __missing__(self, name): - if '.' not in name: + if "." not in name: raise StrategyNameError(name) module_path, class_str = name.rsplit(".", 1) @@ -64,14 +64,12 @@ def get_display_string(klass, display_attribute=None): def get_attr(obj, attr, default=None): - """Recursive get object's attribute. May use dot notation. - - """ - if '.' not in attr: + """Recursive get object's attribute. May use dot notation.""" + if "." not in attr: return getattr(obj, attr, default) else: - L = attr.split('.') - return get_attr(getattr(obj, L[0], default), '.'.join(L[1:]), default) + L = attr.split(".") + return get_attr(getattr(obj, L[0], default), ".".join(L[1:]), default) def fqn(o): @@ -83,7 +81,7 @@ def fqn(o): parts = [] if isinstance(o, str): return o - if not hasattr(o, '__module__'): + if not hasattr(o, "__module__"): raise StrategyClassError(o) parts.append(o.__module__) if isclass(o): diff --git a/src/strategy_field/validators.py b/src/strategy_field/validators.py index 5ed00bf..aa47c0e 100644 --- a/src/strategy_field/validators.py +++ b/src/strategy_field/validators.py @@ -9,12 +9,12 @@ @deconstructible class ClassnameValidator(BaseValidator): - message = _('Ensure this value is valid class name (it is %(show_value)s).') - code = 'classname' + message = _("Ensure this value is valid class name (it is %(show_value)s).") + code = "classname" def __call__(self, value): cleaned = self.clean(value) - params = {'show_value': cleaned, 'value': value} + params = {"show_value": cleaned, "value": value} try: get_class(cleaned) except (ImportError, TypeError, StrategyNameError): @@ -24,8 +24,8 @@ def __call__(self, value): @deconstructible class RegistryValidator(ClassnameValidator): - message = _('Invalid entry `%(show_value)s`') - code = 'registry' + message = _("Invalid entry `%(show_value)s`") + code = "registry" def __init__(self, registry, message=None): super().__init__(registry, message) @@ -33,7 +33,7 @@ def __init__(self, registry, message=None): def __call__(self, value): cleaned = self.clean(value) - params = {'show_value': cleaned, 'value': value} + params = {"show_value": cleaned, "value": value} try: if isinstance(value, (list, tuple)): for c in cleaned: From 5faf9602b4dc353148292d837cff40f8fee85e74 Mon Sep 17 00:00:00 2001 From: sax Date: Sat, 21 Oct 2023 12:28:51 +0200 Subject: [PATCH 4/6] lint --- .flake8 | 11 ++ .github/workflows/test.yaml | 2 +- .isort.cfg | 12 ++ pytest.ini | 1 - setup.cfg | 39 +++--- src/strategy_field/__init__.py | 2 +- src/strategy_field/contrib/drf.py | 5 +- src/strategy_field/fields.py | 18 +-- src/strategy_field/forms.py | 2 +- src/strategy_field/registry.py | 21 +-- src/strategy_field/utils.py | 6 +- src/strategy_field/validators.py | 4 +- tests/conftest.py | 5 +- tests/demo/demoproject/demoapp/__init__.py | 2 +- tests/demo/demoproject/demoapp/admin.py | 4 +- tests/demo/demoproject/demoapp/api.py | 8 +- tests/demo/demoproject/demoapp/apps.py | 2 +- tests/demo/demoproject/demoapp/models.py | 35 ++--- tests/demo/demoproject/settings.py | 78 +++++------ tests/demo/demoproject/urls.py | 32 +++-- tests/demo/demoproject/wsgi.py | 1 + tests/test_choice_as_class.py | 121 +++++++++-------- tests/test_choice_as_instance.py | 121 +++++++++-------- tests/test_classloader.py | 2 +- tests/test_descriptors.py | 88 ++++++++----- tests/test_drf.py | 73 +++++------ tests/test_dumpdata.py | 3 +- tests/test_field.py | 4 +- tests/test_form.py | 7 +- tests/test_hash.py | 23 +++- tests/test_multiple.py | 103 +++++++++------ tests/test_multiple_custom.py | 143 +++++++++++++-------- tests/test_registry.py | 14 +- tests/test_utils.py | 8 +- tests/test_validators.py | 1 - tox.ini | 4 +- 36 files changed, 567 insertions(+), 438 deletions(-) create mode 100644 .flake8 create mode 100644 .isort.cfg diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..628307c --- /dev/null +++ b/.flake8 @@ -0,0 +1,11 @@ +[flake8] +max-complexity = 20 +max-line-length = 120 +exclude = ~* +ignore = E401,W391,E128,E261,E731,Q000,W504,W606,W503,E203 +;putty-ignore = + ; tests/test_choice_as_instance.py : E501 + +per-file-ignores = + */__init__.py:F401,F403 + */migrations/*:E501 diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index faebf17..150fb1c 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -26,7 +26,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.9", "3.11", "3.12" ] + python-version: ["3.9", "3.11"] django-version: [ "3.2", "4.2" ] env: PY_VER: ${{ matrix.python-version}} diff --git a/.isort.cfg b/.isort.cfg new file mode 100644 index 0000000..9cb3dc7 --- /dev/null +++ b/.isort.cfg @@ -0,0 +1,12 @@ +[isort] +profile = black +;combine_as_imports = true +;default_section = THIRDPARTY +;include_trailing_comma = true +;line_length=80 +;known_future_library=future,pies +;known_standard_library = six +;known_third_party = django +;known_first_party = strategy_field, demoproject.demoapp +;multi_line_output = 0 +;sections=FUTURE,STDLIB,THIRDPARTY,FIRSTPARTY,LOCALFOLDER diff --git a/pytest.ini b/pytest.ini index adaf915..60cfc4c 100644 --- a/pytest.ini +++ b/pytest.ini @@ -4,7 +4,6 @@ DJANGO_SETTINGS_MODULE=demoproject.settings django_find_project = false norecursedirs = .tox .venv python_files=tests/test_*.py -log_print = false log_cli = false addopts = -rs diff --git a/setup.cfg b/setup.cfg index e1b27ca..76bb11b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,15 +1,3 @@ -[isort] -combine_as_imports = true -default_section = THIRDPARTY -include_trailing_comma = true -line_length=80 -known_future_library=future,pies -known_standard_library = six -known_third_party = django -known_first_party = strategy_field, demoproject.demoapp -multi_line_output = 0 -sections=FUTURE,STDLIB,THIRDPARTY,FIRSTPARTY,LOCALFOLDER - [bdist_wheel] universal=1 @@ -28,11 +16,22 @@ ignore = *.sqlite .tox/* -[flake8] -max-complexity = 12 -max-line-length = 110 -exclude = .tox,migrations,.git,docs,diff_match_patch.py, deploy/**,settings,~* -ignore = E401,W391,E128,E261,E731 -putty-ignore = -; tests/test_choice_as_instance.py : E501 - tests/test_multiple.py : E501 +[black] +line-length = 120 +include = '\.pyi?$' +exclude = .git +;/( +; \.git +; | \.hg +; | \.mypy_cache +; | \.tox +; | \.venv +; | venv +; | _build +; | buck-out +; | build +; | dist +; | migrations +; | snapshots +;)/ +;''' \ No newline at end of file diff --git a/src/strategy_field/__init__.py b/src/strategy_field/__init__.py index 4b2eb78..d544c1e 100644 --- a/src/strategy_field/__init__.py +++ b/src/strategy_field/__init__.py @@ -1,2 +1,2 @@ NAME = "django-strategy-field" -VERSION = __version__ = "3.0.0" +VERSION = __version__ = "3.1.0" diff --git a/src/strategy_field/contrib/drf.py b/src/strategy_field/contrib/drf.py index 97c1006..ceb2d71 100644 --- a/src/strategy_field/contrib/drf.py +++ b/src/strategy_field/contrib/drf.py @@ -1,10 +1,11 @@ import logging + from django.core.validators import BaseValidator from rest_framework import serializers from rest_framework.exceptions import ValidationError -from strategy_field.fields import ClassnameValidator -from strategy_field.utils import fqn, import_by_name +from ..fields import ClassnameValidator +from ..utils import fqn, import_by_name logger = logging.getLogger(__name__) diff --git a/src/strategy_field/fields.py b/src/strategy_field/fields.py index efb1211..7bb4692 100644 --- a/src/strategy_field/fields.py +++ b/src/strategy_field/fields.py @@ -1,17 +1,17 @@ import logging +from inspect import isclass + from django import forms from django.core.exceptions import ValidationError from django.db import models from django.db.models.fields import BLANK_CHOICE_DASH, NOT_PROVIDED from django.db.models.lookups import Contains, IContains, In from django.utils.text import capfirst -from inspect import isclass -from strategy_field.exceptions import StrategyClassError, StrategyNameError -from strategy_field.forms import (StrategyFormField, - StrategyMultipleChoiceFormField,) -from strategy_field.utils import fqn, get_class, stringify -from strategy_field.validators import ClassnameValidator, RegistryValidator +from .exceptions import StrategyClassError, StrategyNameError +from .forms import StrategyFormField, StrategyMultipleChoiceFormField +from .utils import fqn, get_class, stringify +from .validators import ClassnameValidator, RegistryValidator NOCONTEXT = object() @@ -123,12 +123,6 @@ def __init__(self, *args, **kwargs): if self.registry: self.validators.append(RegistryValidator(self.registry)) - def __str__(self): - return "-oooooo" - - def __repr__(self): - return "-oooooo" - def contribute_to_class( self, cls, name, private_only=False, virtual_only=NOT_PROVIDED ): diff --git a/src/strategy_field/forms.py b/src/strategy_field/forms.py index 0b91928..cdea4dd 100644 --- a/src/strategy_field/forms.py +++ b/src/strategy_field/forms.py @@ -1,7 +1,7 @@ from django.core.exceptions import ValidationError from django.forms.fields import ChoiceField, TypedMultipleChoiceField -from strategy_field.utils import fqn, stringify +from .utils import fqn, stringify class StrategyFormField(ChoiceField): diff --git a/src/strategy_field/registry.py b/src/strategy_field/registry.py index 0dd60ec..38cd0e7 100644 --- a/src/strategy_field/registry.py +++ b/src/strategy_field/registry.py @@ -1,7 +1,8 @@ import logging -from django.utils.functional import cached_property from inspect import isclass +from django.utils.functional import cached_property + from .utils import fqn, get_attr, get_display_string, import_by_name # noqa logger = logging.getLogger(__name__) @@ -71,12 +72,12 @@ def __contains__(self, y): return False return super().__contains__(y) - def get_class(self, value): - if not value: - return value - elif isinstance(value, str): - return import_by_name(value) - elif isclass(value): - return value - else: - return type(value) + # def get_class(self, value): + # if not value: + # return value + # elif isinstance(value, str): + # return import_by_name(value) + # elif isclass(value): + # return value + # else: + # return type(value) diff --git a/src/strategy_field/utils.py b/src/strategy_field/utils.py index b347f4c..a0afe8c 100644 --- a/src/strategy_field/utils.py +++ b/src/strategy_field/utils.py @@ -1,12 +1,12 @@ import importlib import logging import types -from django.utils.module_loading import import_string from inspect import isclass +from django.utils.module_loading import import_string + from . import config -from .exceptions import (StrategyAttributeError, StrategyClassError, - StrategyNameError,) +from .exceptions import StrategyAttributeError, StrategyClassError, StrategyNameError logger = logging.getLogger(__name__) diff --git a/src/strategy_field/validators.py b/src/strategy_field/validators.py index aa47c0e..35b2928 100644 --- a/src/strategy_field/validators.py +++ b/src/strategy_field/validators.py @@ -3,8 +3,8 @@ from django.utils.deconstruct import deconstructible from django.utils.translation import gettext_lazy as _ -from strategy_field.exceptions import StrategyNameError -from strategy_field.utils import get_class +from .exceptions import StrategyNameError +from .utils import get_class @deconstructible diff --git a/tests/conftest.py b/tests/conftest.py index 899567b..09bedab 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -41,6 +41,7 @@ def democustommodel(): def demo_multiplecustom_model(): from demoproject.demoapp.models import DemoMultipleCustomModel, Strategy from strategy_field.utils import fqn + return DemoMultipleCustomModel.objects.get_or_create(sender=[fqn(Strategy)])[0] @@ -51,7 +52,7 @@ def demo_multiple_model(): return DemoMultipleModel.objects.get_or_create(sender=[Sender1])[0] -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def webapp(django_app): return django_app # import django_webtest @@ -60,4 +61,6 @@ def webapp(django_app): # wtm.csrf_checks = False # wtm._patch_settings() # request.addfinalizer(wtm._unpatch_settings) + + # return django_webtest.DjangoTestApp() diff --git a/tests/demo/demoproject/demoapp/__init__.py b/tests/demo/demoproject/demoapp/__init__.py index ea3394d..ec24726 100644 --- a/tests/demo/demoproject/demoapp/__init__.py +++ b/tests/demo/demoproject/demoapp/__init__.py @@ -1 +1 @@ -default_app_config = 'demoproject.demoapp.apps.DemoConfig' +default_app_config = "demoproject.demoapp.apps.DemoConfig" diff --git a/tests/demo/demoproject/demoapp/admin.py b/tests/demo/demoproject/demoapp/admin.py index e0088c0..cdff13f 100644 --- a/tests/demo/demoproject/demoapp/admin.py +++ b/tests/demo/demoproject/demoapp/admin.py @@ -1,17 +1,17 @@ from django.contrib import admin from django.forms import ModelForm, TextInput - from strategy_field.utils import fqn + from .models import ( DemoAllModel, DemoCustomModel, DemoModel, DemoModelCallableDefault, DemoModelDefault, + DemoModelNone, DemoModelProxy, DemoMultipleCustomModel, DemoMultipleModel, - DemoModelNone, ) diff --git a/tests/demo/demoproject/demoapp/api.py b/tests/demo/demoproject/demoapp/api.py index b5eb5a3..4c7ab3c 100644 --- a/tests/demo/demoproject/demoapp/api.py +++ b/tests/demo/demoproject/demoapp/api.py @@ -1,11 +1,9 @@ import logging + +from demoproject.demoapp.models import DemoModelNone, DemoMultipleModel, registry from rest_framework import serializers from rest_framework.viewsets import ModelViewSet - -from demoproject.demoapp.models import (DemoModelNone, DemoMultipleModel, - registry,) -from strategy_field.contrib.drf import (DrfMultipleStrategyField, - DrfStrategyField,) +from strategy_field.contrib.drf import DrfMultipleStrategyField, DrfStrategyField logger = logging.getLogger(__name__) diff --git a/tests/demo/demoproject/demoapp/apps.py b/tests/demo/demoproject/demoapp/apps.py index 7af52b7..969124d 100644 --- a/tests/demo/demoproject/demoapp/apps.py +++ b/tests/demo/demoproject/demoapp/apps.py @@ -2,5 +2,5 @@ class DemoConfig(AppConfig): - name = 'demoproject.demoapp' + name = "demoproject.demoapp" # verbose_name = "demoapp" diff --git a/tests/demo/demoproject/demoapp/models.py b/tests/demo/demoproject/demoapp/models.py index ba5b3c0..b8fc9d6 100644 --- a/tests/demo/demoproject/demoapp/models.py +++ b/tests/demo/demoproject/demoapp/models.py @@ -1,12 +1,14 @@ -import six - import logging + +import six from django.core.mail.backends.base import BaseEmailBackend from django.db import models - -from strategy_field.fields import (MultipleStrategyClassField, - MultipleStrategyField, StrategyClassField, - StrategyField, ) +from strategy_field.fields import ( + MultipleStrategyClassField, + MultipleStrategyField, + StrategyClassField, + StrategyField, +) from strategy_field.registry import Registry from strategy_field.utils import fqn, import_by_name @@ -42,7 +44,7 @@ class SenderWrong: class AbstractStrategy: - def __init__(self, context, label=''): + def __init__(self, context, label=""): if not context: raise ValueError("Invalid context for strategy ({})".format(context)) self.context = context @@ -51,8 +53,9 @@ def __init__(self, context, label=''): def __str__(self): return "oooooo" + class Strategy(AbstractStrategy): - label = 'strategy' + label = "strategy" none = None @classmethod @@ -68,7 +71,7 @@ class StrategyRegistry(Registry): def deserialize(self, value, obj=None): ret = [] if isinstance(value, six.string_types): - value = value.split(',') + value = value.split(",") for v in value: if isinstance(v, six.string_types): v = import_by_name(v) @@ -110,19 +113,17 @@ class DemoModelNone(models.Model): class DemoModelDefault(models.Model): - sender = StrategyClassField(null=True, - registry=registry, - default='demoproject.demoapp.models.Sender1') + sender = StrategyClassField( + null=True, registry=registry, default="demoproject.demoapp.models.Sender1" + ) def cc(): - return 'demoproject.demoapp.models.Sender1' + return "demoproject.demoapp.models.Sender1" class DemoModelCallableDefault(models.Model): - sender = StrategyClassField(registry=registry, null=True, - default=cc - ) + sender = StrategyClassField(registry=registry, null=True, default=cc) class DemoModelProxy(DemoModel): @@ -149,7 +150,7 @@ class DemoModelContext(models.Model): # funny code. just for tests def factory(klass, context): if issubclass(klass, BaseEmailBackend): - return klass(file_path='') + return klass(file_path="") return klass() diff --git a/tests/demo/demoproject/settings.py b/tests/demo/demoproject/settings.py index 099649a..ee515d5 100644 --- a/tests/demo/demoproject/settings.py +++ b/tests/demo/demoproject/settings.py @@ -7,62 +7,64 @@ TEMPLATES = [ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': ['django.contrib.messages.context_processors.messages', - 'django.contrib.auth.context_processors.auth', - "django.template.context_processors.request", - ] + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.contrib.messages.context_processors.messages", + "django.contrib.auth.context_processors.auth", + "django.template.context_processors.request", + ] }, }, ] # STRATEGY_CLASSLOADER = "demoproject.classloader.custom_classloader" DATABASES = { - 'default': { + "default": { # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'. - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': 'DEMODB.sqlite', # Not used with sqlite3. + "ENGINE": "django.db.backends.sqlite3", + "NAME": "DEMODB.sqlite", # Not used with sqlite3. # Set to empty string for localhost. Not used with sqlite3. - 'HOST': '', - 'PORT': '', # Set to empty string for default. Not used with sqlite3. + "HOST": "", + "PORT": "", # Set to empty string for default. Not used with sqlite3. } } -TIME_ZONE = 'Asia/Bangkok' -LANGUAGE_CODE = 'en-us' +TIME_ZONE = "Asia/Bangkok" +LANGUAGE_CODE = "en-us" SITE_ID = 1 USE_I18N = True USE_L10N = True USE_TZ = True -MEDIA_ROOT = os.path.join(here, 'media') -MEDIA_URL = '' -STATIC_ROOT = '' -STATIC_URL = '/static/' -SECRET_KEY = 'c73*n!y=)tziu^2)y*@5i2^)$8z$tx#b9*_r3i6o1ohxo%*2^a' +MEDIA_ROOT = os.path.join(here, "media") +MEDIA_URL = "" +STATIC_ROOT = "" +STATIC_URL = "/static/" +SECRET_KEY = "c73*n!y=)tziu^2)y*@5i2^)$8z$tx#b9*_r3i6o1ohxo%*2^a" MIDDLEWARE = ( - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", ) -ROOT_URLCONF = 'demoproject.urls' -WSGI_APPLICATION = 'demoproject.wsgi.application' +ROOT_URLCONF = "demoproject.urls" +WSGI_APPLICATION = "demoproject.wsgi.application" -AUTHENTICATION_BACKENDS = ('demoproject.backends.AnyUserBackend',) +AUTHENTICATION_BACKENDS = ("demoproject.backends.AnyUserBackend",) INSTALLED_APPS = ( - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.sites', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'django.contrib.admin', - 'demoproject.demoapp.apps.DemoConfig') + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.sites", + "django.contrib.messages", + "django.contrib.staticfiles", + "django.contrib.admin", + "demoproject.demoapp.apps.DemoConfig", +) -DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' \ No newline at end of file +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" diff --git a/tests/demo/demoproject/urls.py b/tests/demo/demoproject/urls.py index 57dcb5f..4254887 100644 --- a/tests/demo/demoproject/urls.py +++ b/tests/demo/demoproject/urls.py @@ -1,15 +1,27 @@ +from demoproject.demoapp.api import DemoModelView, DemoMultipleModelView from django.contrib.admin.sites import site from django.urls import path, re_path -from demoproject.demoapp.api import DemoModelView, DemoMultipleModelView - - urlpatterns = ( - re_path(r'^api/s/(?P.*)/$', DemoModelView.as_view({'get': 'retrieve'}), name='detail'), - re_path(r'^api/s/$', DemoModelView.as_view({'get': 'list', 'post': 'create'}), name='single'), - - re_path(r'^api/m/(?P.*)/$', DemoMultipleModelView.as_view({'get': 'retrieve'}), name="multiple"), - re_path(r'^api/m/$', DemoMultipleModelView.as_view({'get': 'list', 'post': 'create'}), name='multiple'), - - path(r'', site.urls), + re_path( + r"^api/s/(?P.*)/$", + DemoModelView.as_view({"get": "retrieve"}), + name="detail", + ), + re_path( + r"^api/s/$", + DemoModelView.as_view({"get": "list", "post": "create"}), + name="single", + ), + re_path( + r"^api/m/(?P.*)/$", + DemoMultipleModelView.as_view({"get": "retrieve"}), + name="multiple", + ), + re_path( + r"^api/m/$", + DemoMultipleModelView.as_view({"get": "list", "post": "create"}), + name="multiple", + ), + path(r"", site.urls), ) diff --git a/tests/demo/demoproject/wsgi.py b/tests/demo/demoproject/wsgi.py index 8da22d7..a8a4511 100644 --- a/tests/demo/demoproject/wsgi.py +++ b/tests/demo/demoproject/wsgi.py @@ -14,6 +14,7 @@ """ import os + # This application object is used by any WSGI server configured to use this # file. This includes Django's development server, if the WSGI_APPLICATION # setting points here. diff --git a/tests/test_choice_as_class.py b/tests/test_choice_as_class.py index fab3d2d..3472d9d 100644 --- a/tests/test_choice_as_class.py +++ b/tests/test_choice_as_class.py @@ -1,37 +1,37 @@ # flake8: noqa import pytest +from demoproject.demoapp.models import ( + DemoModel, + DemoModelCallableDefault, + DemoModelDefault, + DemoModelNone, + Sender1, + Sender2, + SenderNotRegistered, +) from django.forms.models import modelform_factory from django.urls import reverse - -from demoproject.demoapp.models import (DemoModel, DemoModelCallableDefault, - DemoModelDefault, DemoModelNone, - Sender1, Sender2, SenderNotRegistered,) from strategy_field.utils import fqn def pytest_generate_tests(metafunc): func_name = metafunc.function.__name__ values = ids = [] - if 'target' in metafunc.fixturenames: - if func_name.endswith('_lookup_in'): - values = [lambda o: [fqn(o.sender)], - lambda o: [o.sender], - lambda o: [fqn(Sender1), fqn(Sender2)], - lambda o: [Sender1, Sender2]] - ids = ['fqn(target.sender)', - 'target.sender', - 'fqn(Sender1)', - 'Sender1'] + if "target" in metafunc.fixturenames: + if func_name.endswith("_lookup_in"): + values = [ + lambda o: [fqn(o.sender)], + lambda o: [o.sender], + lambda o: [fqn(Sender1), fqn(Sender2)], + lambda o: [Sender1, Sender2], + ] + ids = ["fqn(target.sender)", "target.sender", "fqn(Sender1)", "Sender1"] else: - values = [lambda o: fqn(Sender1), - lambda o: Sender1] - ids = [fqn(Sender1), - 'Sender1'] - if 'demomodel' in metafunc.fixturenames: - values.extend([lambda o: fqn(o.sender), - lambda o: o.sender]) - ids.extend(['fqn(target.sender)', - 'target.sender']) + values = [lambda o: fqn(Sender1), lambda o: Sender1] + ids = [fqn(Sender1), "Sender1"] + if "demomodel" in metafunc.fixturenames: + values.extend([lambda o: fqn(o.sender), lambda o: o.sender]) + ids.extend(["fqn(target.sender)", "target.sender"]) metafunc.parametrize("target", values, ids=ids) @@ -60,7 +60,7 @@ def test_model_save_default(): d = DemoModelDefault() d.save() # registry = d._meta.get_field_by_name('sender')[0].registry - registry = d._meta.get_field('sender').registry + registry = d._meta.get_field("sender").registry assert d.sender == registry[0] @@ -69,7 +69,7 @@ def test_model_save_default_with_callable(): d = DemoModelCallableDefault() d.save() # registry = d._meta.get_field_by_name('sender')[0].registry - registry = d._meta.get_field('sender').registry + registry = d._meta.get_field("sender").registry assert d.sender == registry[0] @@ -89,16 +89,16 @@ def test_model_load(demomodel): @pytest.mark.django_db def test_form(demomodel, registry): # demomodel._meta.get_field_by_name('sender')[0].registry = registry - demomodel._meta.get_field('sender').registry = registry + demomodel._meta.get_field("sender").registry = registry form_class = modelform_factory(DemoModel, exclude=[]) form = form_class(instance=demomodel) - assert form.fields['sender'].choices[1:] == registry.as_choices() + assert form.fields["sender"].choices[1:] == registry.as_choices() @pytest.mark.django_db def test_form_save(demomodel): form_class = modelform_factory(DemoModel, exclude=[]) - form = form_class({'sender': fqn(demomodel.sender)}, instance=demomodel) + form = form_class({"sender": fqn(demomodel.sender)}, instance=demomodel) assert form.is_valid(), form.errors instance = form.save() assert instance.sender == demomodel.sender @@ -107,22 +107,24 @@ def test_form_save(demomodel): @pytest.mark.django_db def test_form_not_valid(demomodel): form_class = modelform_factory(DemoModel, exclude=[]) - form = form_class({'sender': fqn(DemoModel)}, instance=demomodel) + form = form_class({"sender": fqn(DemoModel)}, instance=demomodel) assert not form.is_valid() - assert form.errors['sender'] == ['Select a valid choice. ' - 'demoproject.demoapp.models.DemoModel ' - 'is not one of the available choices.'] + assert form.errors["sender"] == [ + "Select a valid choice. " + "demoproject.demoapp.models.DemoModel " + "is not one of the available choices." + ] @pytest.mark.django_db def test_form_default(demomodel): form_class = modelform_factory(DemoModel, exclude=[]) form = form_class(instance=demomodel) - assert form.fields['sender'].choices == [(u'', u'---------'), - ('demoproject.demoapp.models.Sender1', - 'demoproject.demoapp.models.Sender1'), - ('demoproject.demoapp.models.Sender2', - 'demoproject.demoapp.models.Sender2')] + assert form.fields["sender"].choices == [ + ("", "---------"), + ("demoproject.demoapp.models.Sender1", "demoproject.demoapp.models.Sender1"), + ("demoproject.demoapp.models.Sender2", "demoproject.demoapp.models.Sender2"), + ] # assert form.as_table() == u'' \ # u'\n' \ # u'\n' \ @@ -118,34 +122,49 @@ def test_form_default(demo_multiplecustom_model): @pytest.mark.django_db def test_admin_demo_multiple_model_add(webapp, admin_user): - res = webapp.get('/demoapp/demomultiplecustommodel/add/', user=admin_user) + res = webapp.get("/demoapp/demomultiplecustommodel/add/", user=admin_user) form = res.forms["demomultiplecustommodel_form"] - form['sender'].force_value(['demoproject.demoapp.models.Strategy']) + form["sender"].force_value(["demoproject.demoapp.models.Strategy"]) form.submit().follow() - assert DemoMultipleCustomModel.objects.filter( - sender='demoproject.demoapp.models.Strategy').count() == 1 + assert ( + DemoMultipleCustomModel.objects.filter( + sender="demoproject.demoapp.models.Strategy" + ).count() + == 1 + ) @pytest.mark.django_db def test_admin_demo_multiple_model_edit(webapp, admin_user, demo_multiplecustom_model): demo_multiplecustom_model.sender = [Strategy, Strategy1] demo_multiplecustom_model.save() - url = reverse('admin:demoapp_demomultiplecustommodel_change', args=[demo_multiplecustom_model.pk]) + url = reverse( + "admin:demoapp_demomultiplecustommodel_change", + args=[demo_multiplecustom_model.pk], + ) res = webapp.get(url, user=admin_user) - assert res.context['adminform'].form.fields['sender'].choices == [('demoproject.demoapp.models.Strategy', - 'demoproject.demoapp.models.Strategy'), - ('demoproject.demoapp.models.Strategy1', - 'demoproject.demoapp.models.Strategy1')] + assert res.context["adminform"].form.fields["sender"].choices == [ + ("demoproject.demoapp.models.Strategy", "demoproject.demoapp.models.Strategy"), + ( + "demoproject.demoapp.models.Strategy1", + "demoproject.demoapp.models.Strategy1", + ), + ] form = res.forms["demomultiplecustommodel_form"] - form['sender'] = ['demoproject.demoapp.models.Strategy', - 'demoproject.demoapp.models.Strategy1'] + form["sender"] = [ + "demoproject.demoapp.models.Strategy", + "demoproject.demoapp.models.Strategy1", + ] form.submit().follow() res = webapp.get(url, user=admin_user) - assert res.context['adminform'].form.fields['sender'].choices == [('demoproject.demoapp.models.Strategy', - 'demoproject.demoapp.models.Strategy'), - ('demoproject.demoapp.models.Strategy1', - 'demoproject.demoapp.models.Strategy1')] + assert res.context["adminform"].form.fields["sender"].choices == [ + ("demoproject.demoapp.models.Strategy", "demoproject.demoapp.models.Strategy"), + ( + "demoproject.demoapp.models.Strategy1", + "demoproject.demoapp.models.Strategy1", + ), + ] # assert res.context['adminform'].form.as_table() == u'' \ # u'