From bad770077ddc8f85cd7b46adba7411b4c8a8f435 Mon Sep 17 00:00:00 2001 From: pf69348 Date: Mon, 19 Dec 2022 10:36:52 -0800 Subject: [PATCH 1/2] Implement QuerysetField include tests, fix runtests.py to ignore venv directory, make messages (update missing for BR and RU) --- locale/pt_BR/LC_MESSAGES/django.po | 80 ++++++++++++++++++++++++++++-- locale/ru_RU/LC_MESSAGES/django.po | 76 +++++++++++++++++++++------- makemessages.py | 3 +- runtests.py | 3 +- service_objects/fields.py | 70 ++++++++++++++++++++++++++ tests/test_fields.py | 43 +++++++++++++++- tox.ini | 1 + 7 files changed, 251 insertions(+), 25 deletions(-) diff --git a/locale/pt_BR/LC_MESSAGES/django.po b/locale/pt_BR/LC_MESSAGES/django.po index 9ff848d..a298222 100644 --- a/locale/pt_BR/LC_MESSAGES/django.po +++ b/locale/pt_BR/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-08-17 00:57-0300\n" +"POT-Creation-Date: 2022-12-19 10:30-0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: Breno Uchôa \n" "Language-Team: LANGUAGE \n" @@ -18,16 +18,90 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" -#: service_objects/fields.py:10 +#: service_objects/fields.py:32 #, python-format msgid "There needs to be at least %(num)d item." msgid_plural "There needs to be at least %(num)d items." msgstr[0] "É necessário pelo menos %(num)d item." msgstr[1] "São necessários pelo menos %(num)d itens." -#: service_objects/fields.py:13 +#: service_objects/fields.py:35 #, python-format msgid "There needs to be at most %(num)d item." msgid_plural "There needs to be at most %(num)d items." msgstr[0] "Só pode conter %(num)d item." msgstr[1] "Só pode conter %(num)d itens." + +#: service_objects/fields.py:38 +#, python-format +msgid "Input is required. Expected not empty list but got %(values)r." +msgstr "" + +#: service_objects/fields.py:108 +#, python-format +msgid "" +"%(cls_name)s(%(model_class)r) is invalid. First parameter of ModelField " +"must be either a model or a model name." +msgstr "" +"%(cls_name)s(%(model_class)r) is invalid. First parameter of ModelField " +"must be either a model or a model name." + +#: service_objects/fields.py:111 +#, python-format +msgid "Objects needs to be of type %(model_class)r" +msgstr "Objects needs to be of type %(model_class)r" + +#: service_objects/fields.py:112 +msgid "Unsaved objects are not allowed." +msgstr "Unsaved objects are not allowed." + +#: service_objects/fields.py:113 +#, python-format +msgid "Input is required. Expected model but got %(value)r." +msgstr "Input is required. Expected model but got %(value)r." + +#: service_objects/fields.py:190 +msgid "Object is not iterable." +msgstr "Object is not iterable." + +#: service_objects/fields.py:191 +#, python-format +msgid "Input is required expected list of models but got %(values)r." +msgstr "Input is required expected list of models but got %(values)r." + +#: service_objects/fields.py:235 +#, python-format +msgid "" +"%(cls_name)s(%(model_class)r) is invalid. First parameter of QuerysetField " +"must be either a model or a model name." +msgstr "" +"%(cls_name)s(%(model_class)r) is invalid. First parameter of QuerysetField " +"must be either a model or a model name." + +#: service_objects/fields.py:238 +#, python-format +msgid "Input is required. Expected queryset but got %(value)r." +msgstr "Input is required. Expected queryset but got %(value)r." + +#: service_objects/fields.py:240 +#, python-format +msgid "Input is required expected queryset of models but got %(values)r." +msgstr "Input is required expected queryset of models but got %(values)r." + +#: service_objects/fields.py:299 +#, python-format +msgid "Input is required. Expected dict but got %(value)r." +msgstr "Input is required. Expected dict but got %(value)r." + +#: service_objects/fields.py:300 +msgid "Input needs to be of type dict." +msgstr "Input needs to be of type dict." + +#: service_objects/fields.py:333 +#, python-format +msgid "Input is required. Expected list but got %(value)r." +msgstr "Input is required. Expected list but got %(value)r." + +#: service_objects/fields.py:334 +msgid "Input needs to be of type list." +msgstr "Input needs to be of type list." diff --git a/locale/ru_RU/LC_MESSAGES/django.po b/locale/ru_RU/LC_MESSAGES/django.po index 1b9a6e8..abe3884 100644 --- a/locale/ru_RU/LC_MESSAGES/django.po +++ b/locale/ru_RU/LC_MESSAGES/django.po @@ -1,7 +1,8 @@ msgid "" msgstr "" "Project-Id-Version: \n" -"POT-Creation-Date: 2021-03-20 20:35+0700\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2022-12-19 10:33-0800\n" "PO-Revision-Date: 2021-03-20 21:05+0700\n" "Last-Translator: \n" "Language-Team: \n" @@ -15,14 +16,30 @@ msgstr "" "%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);\n" "X-Poedit-SearchPath-0: .\n" -#: service_objects/fields.py:37 +#: service_objects/fields.py:32 +#, python-format +msgid "There needs to be at least %(num)d item." +msgid_plural "There needs to be at least %(num)d items." +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +#: service_objects/fields.py:35 +#, python-format +msgid "There needs to be at most %(num)d item." +msgid_plural "There needs to be at most %(num)d items." +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +#: service_objects/fields.py:38 #, python-format msgid "Input is required. Expected not empty list but got %(values)r." msgstr "" "Входной параметр обязателен. Ожидается непустой список, но получено " "%(values)r." -#: service_objects/fields.py:107 +#: service_objects/fields.py:108 #, python-format msgid "" "%(cls_name)s(%(model_class)r) is invalid. First parameter of ModelField " @@ -31,48 +48,71 @@ msgstr "" "%(cls_name)s(%(model_class)r) некорректно. Первый параметр ModelField " "должен быть моделью или именем модели." -#: service_objects/fields.py:110 +#: service_objects/fields.py:111 #, python-format msgid "Objects needs to be of type %(model_class)r" msgstr "Объекты должны быть типа %(model_class)r" -#: service_objects/fields.py:111 +#: service_objects/fields.py:112 msgid "Unsaved objects are not allowed." msgstr "Несохраненные объекты не разрешены." -#: service_objects/fields.py:112 +#: service_objects/fields.py:113 #, python-format msgid "Input is required. Expected model but got %(value)r." -msgstr "" -"Входной параметр обязателен. Ожидается модель, но получено %(value)r." +msgstr "Входной параметр обязателен. Ожидается модель, но получено %(value)r." -#: service_objects/fields.py:189 +#: service_objects/fields.py:190 msgid "Object is not iterable." msgstr "Объект не является итерируемым." -#: service_objects/fields.py:190 +#: service_objects/fields.py:191 #, python-format msgid "Input is required expected list of models but got %(values)r." msgstr "" "Входной параметр обязателен и ожидается список моделей, но получено " "%(values)r." -#: service_objects/fields.py:229 +#: service_objects/fields.py:235 +#, fuzzy, python-format +#| msgid "" +#| "%(cls_name)s(%(model_class)r) is invalid. First parameter of ModelField " +#| "must be either a model or a model name." +msgid "" +"%(cls_name)s(%(model_class)r) is invalid. First parameter of QuerysetField " +"must be either a model or a model name." +msgstr "" +"%(cls_name)s(%(model_class)r) некорректно. Первый параметр ModelField " +"должен быть моделью или именем модели." + +#: service_objects/fields.py:238 +#, fuzzy, python-format +#| msgid "Input is required. Expected list but got %(value)r." +msgid "Input is required. Expected queryset but got %(value)r." +msgstr "Входной параметр обязателен. Ожидается список, но получено %(value)r." + +#: service_objects/fields.py:240 +#, fuzzy, python-format +#| msgid "Input is required expected list of models but got %(values)r." +msgid "Input is required expected queryset of models but got %(values)r." +msgstr "" +"Входной параметр обязателен и ожидается список моделей, но получено " +"%(values)r." + +#: service_objects/fields.py:299 #, python-format msgid "Input is required. Expected dict but got %(value)r." -msgstr "" -"Входной параметр обязателен. Ожидается словарь, но получено %(value)r." +msgstr "Входной параметр обязателен. Ожидается словарь, но получено %(value)r." -#: service_objects/fields.py:230 +#: service_objects/fields.py:300 msgid "Input needs to be of type dict." msgstr "Входной параметр должен быть типа словарь." -#: service_objects/fields.py:263 +#: service_objects/fields.py:333 #, python-format msgid "Input is required. Expected list but got %(value)r." -msgstr "" -"Входной параметр обязателен. Ожидается список, но получено %(value)r." +msgstr "Входной параметр обязателен. Ожидается список, но получено %(value)r." -#: service_objects/fields.py:264 +#: service_objects/fields.py:334 msgid "Input needs to be of type list." msgstr "Входной параметр должен быть типа список." diff --git a/makemessages.py b/makemessages.py index 795907e..73c5892 100644 --- a/makemessages.py +++ b/makemessages.py @@ -19,5 +19,4 @@ django.setup() -management.call_command('makemessages', "--locale=pt_BR", verbosity=0, - interactive=False) +management.call_command('makemessages', "--locale=pt_BR", "--locale=ru_RU", "--ignore=venv/*", verbosity=0) diff --git a/runtests.py b/runtests.py index ccd5db1..1d1afca 100644 --- a/runtests.py +++ b/runtests.py @@ -11,7 +11,8 @@ 'NAME': ':memory:' } }, - INSTALLED_APPS=('tests',) + INSTALLED_APPS=('tests',), + DEFAULT_AUTO_FIELD='django.db.models.AutoField', ) django.setup() diff --git a/service_objects/fields.py b/service_objects/fields.py index 6d4eed4..37e9588 100644 --- a/service_objects/fields.py +++ b/service_objects/fields.py @@ -1,6 +1,7 @@ from django import forms from django.apps import apps from django.core.exceptions import ValidationError +from django.db.models.query import QuerySet from django.utils.translation import ngettext_lazy, gettext_lazy as _ @@ -210,6 +211,75 @@ def clean(self, values): return values +class QuerysetField(forms.Field): + """ + A field for :class:`Service` that accepts a queryset of the specified + :class:`Model`:: + + class Person(models.Model): + first_name = models.CharField(max_length=30) + last_name = models.CharField(max_length=30) + + + class AssociatePeople(Service): + people = QuerysetField(Person) + + AssociatePeople.execute({ + 'people': Users.objects.all() + }) + + :param model_class: Django :class:`Model` or dotted string of : + class:`Model` name that matches the incoming queryset + + """ + error_model_class = _("%(cls_name)s(%(model_class)r) is invalid. First " + "parameter of QuerysetField must be either a model " + "or a model name.") + error_required = _("Input is required. Expected queryset but got " + "%(value)r.") + error_type = _("Input is required expected queryset " + "of models but got %(values)r.") + + def __init__(self, model_class, *args, **kwargs): + super().__init__(*args, **kwargs) + + try: + model_class._meta.model_name + except AttributeError: + assert isinstance(model_class, str), self.error_model_class % { + 'cls_name': self.__class__.__name__, + 'model_class': model_class + } + + self.model_class = model_class + if isinstance(model_class, str): + label = model_class.split('.') + app_label = ".".join(label[:-1]) + model_name = label[-1] + self.model_class = apps.get_model(app_label, model_name) + + def clean(self, values): + if self.required: + # Check for queryset and queryset type + if not isinstance(values, QuerySet): + raise ValidationError(self.error_required % + {'value': type(values)}) + + # Check for queryset and queryset type + if values.model != self.model_class: + raise ValidationError(self.error_model_class % { + 'cls_name': self.__class__.__name__, + 'model_class': self.model_class + }) + + if self.required and not values.count(): + raise ValidationError(self.error_required % { + 'value': type(values) + }) + + return values + + class DictField(forms.Field): """ A field for :class:`Service` that accepts a dictionary: diff --git a/tests/test_fields.py b/tests/test_fields.py index cb2d783..2a84f05 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -3,7 +3,7 @@ from django.core.exceptions import ValidationError from service_objects.fields import MultipleFormField, ModelField, MultipleModelField, \ - DictField, ListField + DictField, ListField, QuerysetField from tests.forms import FooForm from tests.models import FooModel, BarModel, NonModel @@ -196,6 +196,47 @@ def test_is_not_required(self): f.clean(None) +class QuerysetFieldTest(TestCase): + + def test_multiple_invalid_type(self): + model_field = QuerysetField(FooModel) + queryset = [ + 'A', + 'B', + 'C' + ] + + with self.assertRaisesRegexp(ValidationError, ""): + model_field.clean(queryset) + + def test_multiple_incorrect_queryset_type(self): + model_field = QuerysetField(FooModel) + queryset = BarModel.objects.all() + + with self.assertRaisesRegexp(ValidationError, "FooModel"): + model_field.clean(queryset) + + def test_is_required(self): + # Add one object + FooModel.objects.create(one='A') + + f = QuerysetField(FooModel, required=True) + with self.assertRaises(ValidationError) as cm: + f.clean(FooModel.objects.none()) + + self.assertEqual( + "Input is required. Expected queryset but got .", cm.exception.message) + + values = f.clean(FooModel.objects.all()) + + self.assertTrue(values.count() == 1, 'Expecting queryset with 1 item in it.') + + def test_is_not_required(self): + f = MultipleModelField(FooModel, required=False) + # should not raise any exception + f.clean(None) + + class DictFieldTest(TestCase): def test_is_required(self): dict_field = DictField(required=True) diff --git a/tox.ini b/tox.ini index 74ec37d..39f2fa9 100644 --- a/tox.ini +++ b/tox.ini @@ -57,4 +57,5 @@ exclude = .ropeproject, runtests.py setup.py, + venv, max-line-length = 80 From fde23963651b82f8e927db59ca18a996c610c2f3 Mon Sep 17 00:00:00 2001 From: pf69348 Date: Mon, 19 Dec 2022 10:48:27 -0800 Subject: [PATCH 2/2] Updated history --- HISTORY.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/HISTORY.md b/HISTORY.md index 845e83f..560eea2 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -2,6 +2,10 @@ ## Unreleased +**Features and Improvements** + +* Added support for QuerysetField - peterfarrell + ## 0.7.1 (2022-02-23) **Features and Improvements**