From d1543516e8414a2adea9d3980fabb067e5b0ec03 Mon Sep 17 00:00:00 2001 From: Betalos Oralyos Date: Tue, 3 Dec 2019 11:21:04 +0100 Subject: [PATCH 1/5] remove six dependecy (python 2 support) --- rest_framework_mongoengine/fields.py | 7 +++---- rest_framework_mongoengine/repr.py | 3 +-- rest_framework_mongoengine/serializers.py | 5 ++--- setup.py | 2 +- 4 files changed, 7 insertions(+), 10 deletions(-) diff --git a/rest_framework_mongoengine/fields.py b/rest_framework_mongoengine/fields.py index cbca88f..2bcac29 100644 --- a/rest_framework_mongoengine/fields.py +++ b/rest_framework_mongoengine/fields.py @@ -2,7 +2,6 @@ from bson import DBRef, ObjectId from bson.errors import InvalidId -from django.utils import six from django.utils.encoding import smart_text from django.utils.translation import ugettext_lazy as _ from mongoengine import fields as me_fields @@ -250,7 +249,7 @@ def choices(self): return OrderedDict([ ( - six.text_type(self.to_representation(item)), + str(self.to_representation(item)), self.display_value(item) ) for item in queryset @@ -261,7 +260,7 @@ def grouped_choices(self): return self.choices def display_value(self, instance): - return six.text_type(instance) + return str(instance) def parse_id(self, value): try: @@ -529,7 +528,7 @@ def to_internal_value(self, data): api_settings.NON_FIELD_ERRORS_KEY: [message] }) return { - six.text_type(key): self.child.run_validation(value) + str(key): self.child.run_validation(value) for key, value in data.items() } diff --git a/rest_framework_mongoengine/repr.py b/rest_framework_mongoengine/repr.py index 304b02d..7109d7c 100644 --- a/rest_framework_mongoengine/repr.py +++ b/rest_framework_mongoengine/repr.py @@ -6,7 +6,6 @@ import re -from django.utils import six from django.utils.encoding import force_str from mongoengine.base import BaseDocument from mongoengine.fields import BaseField @@ -33,7 +32,7 @@ def mongo_field_repr(value): def mongo_doc_repr(value): # mimic django models.Model.__repr__ try: - u = six.text_type(value) + u = str(value) except (UnicodeEncodeError, UnicodeDecodeError): u = '[Bad Unicode data]' return force_str('<%s: %s>' % (value.__class__.__name__, u)) diff --git a/rest_framework_mongoengine/serializers.py b/rest_framework_mongoengine/serializers.py index e123c19..644fcc4 100644 --- a/rest_framework_mongoengine/serializers.py +++ b/rest_framework_mongoengine/serializers.py @@ -2,7 +2,6 @@ import warnings from collections import OrderedDict, namedtuple -from django.utils.six import get_unbound_function from mongoengine import fields as me_fields from mongoengine.errors import ValidationError as me_ValidationError from rest_framework import fields as drf_fields @@ -225,7 +224,7 @@ def recursive_save(self, validated_data, instance=None): # for EmbeddedDocumentSerializers, call recursive_save if isinstance(field, EmbeddedDocumentSerializer): - me_data[key] = field.recursive_save(value) + me_data[key] = field.recursive_save(value) if value is not None else value # issue when the value is none # same for lists of EmbeddedDocumentSerializers i.e. # ListField(EmbeddedDocumentField) or EmbeddedDocumentListField @@ -534,7 +533,7 @@ def get_customization_for_nested_field(self, field_name): nested_validate_methods = {} for attr in dir(self.__class__): if attr.startswith('validate_%s__' % field_name.replace('.', '__')): - method = get_unbound_function(getattr(self.__class__, attr)) + method = getattr(self.__class__, attr) method_name = 'validate_' + attr[len('validate_%s__' % field_name.replace('.', '__')):] nested_validate_methods[method_name] = method diff --git a/setup.py b/setup.py index e2a4bdc..c8c80e8 100755 --- a/setup.py +++ b/setup.py @@ -29,7 +29,7 @@ def get_package_data(package): setup( name='django-rest-framework-mongoengine', - version='3.4.0', + version='3.4.1', description='MongoEngine support for Django Rest Framework.', packages=get_packages('rest_framework_mongoengine'), package_data=get_package_data('rest_framework_mongoengine'), From e681bef2c1e3310a8ded3da412b096d026d0dc69 Mon Sep 17 00:00:00 2001 From: Betalos Oralyos Date: Tue, 17 Dec 2019 17:34:16 +0100 Subject: [PATCH 2/5] fix validators (support latest drf) --- rest_framework_mongoengine/validators.py | 31 +++++++++++++++++------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/rest_framework_mongoengine/validators.py b/rest_framework_mongoengine/validators.py index 8a5009a..dcc6d14 100644 --- a/rest_framework_mongoengine/validators.py +++ b/rest_framework_mongoengine/validators.py @@ -8,9 +8,9 @@ class MongoValidatorMixin(): - def exclude_current_instance(self, queryset): - if self.instance is not None: - return queryset.filter(pk__ne=self.instance.pk) + def exclude_current_instance(self, queryset, instance): + if instance is not None: + return queryset.filter(pk__ne=instance.pk) return queryset @@ -19,10 +19,17 @@ class UniqueValidator(MongoValidatorMixin, validators.UniqueValidator): Used by :class:`DocumentSerializer` for fields, present in unique indexes. """ - def __call__(self, value): + def __call__(self, value, serializer_field): + # Determine the underlying model field name. This may not be the + # same as the serializer field name if `source=<>` is set. + field_name = serializer_field.source_attrs[-1] + # Determine the existing instance, if this is an update operation. + instance = getattr(serializer_field.parent, 'instance', None) + queryset = self.queryset - queryset = self.filter_queryset(value, queryset) - queryset = self.exclude_current_instance(queryset) + queryset = self.filter_queryset(value, queryset, field_name) + queryset = self.exclude_current_instance(queryset, instance) + if queryset.first(): raise ValidationError(self.message.format()) @@ -38,14 +45,20 @@ class UniqueTogetherValidator(MongoValidatorMixin, validators.UniqueTogetherVali Used by :class:`DocumentSerializer` for fields, present in unique indexes. """ - def __call__(self, attrs): + def __call__(self, attrs, serializer_field): try: self.enforce_required_fields(attrs) except SkipField: return + + field_name = serializer_field.source_attrs[-1] + # Determine the existing instance, if this is an update operation. + instance = getattr(serializer_field.parent, 'instance', None) + queryset = self.queryset - queryset = self.filter_queryset(attrs, queryset) - queryset = self.exclude_current_instance(queryset) + queryset = self.filter_queryset(value, queryset, field_name) + queryset = self.exclude_current_instance(queryset, instance) + # Ignore validation if any field is None checked_values = [ value for field, value in attrs.items() if field in self.fields From 032fb7cb9238ed4e5f77c7c5a1d48b9ec8cf0ba6 Mon Sep 17 00:00:00 2001 From: Betalos Oralyos Date: Wed, 25 Dec 2019 12:41:47 +0100 Subject: [PATCH 3/5] remove six dependency --- requirements/requirements-testing.txt | 1 - tests/test_basic.py | 21 +++------------------ 2 files changed, 3 insertions(+), 19 deletions(-) diff --git a/requirements/requirements-testing.txt b/requirements/requirements-testing.txt index f73aa2d..495bf10 100644 --- a/requirements/requirements-testing.txt +++ b/requirements/requirements-testing.txt @@ -2,5 +2,4 @@ pytest pytest-cov pytest-django mock -six pytz \ No newline at end of file diff --git a/tests/test_basic.py b/tests/test_basic.py index 7847e4b..5598df0 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -12,7 +12,6 @@ from uuid import UUID import pytest -import six from bson import ObjectId from django.core.exceptions import ImproperlyConfigured from django.test import TestCase @@ -101,11 +100,7 @@ class Meta: model = RegularModel fields = '__all__' - # in pythons 2 and 3 regex reprs are different - if six.PY2: - regex_repr = "<_sre.SRE_Pattern object>" - else: - regex_repr = "re.compile('^valid_regex')" + regex_repr = "re.compile('^valid_regex')" expected = dedent(""" TestSerializer(): @@ -155,11 +150,7 @@ class Meta: model = RegularModel exclude = ('decimal_field', 'custom_field') - # in pythons 2 and 3 regex reprs are different - if six.PY2: - regex_repr = "<_sre.SRE_Pattern object>" - else: - regex_repr = "re.compile('^valid_regex')" + regex_repr = "re.compile('^valid_regex')" expected = dedent(""" TestSerializer(): @@ -201,13 +192,7 @@ class Meta: value_limit_field = IntegerField(max_value=12, min_value=3, required=False) decimal_field = DecimalField(decimal_places=4, max_digits=8, max_value=9999, required=False) """) - # if six.PY2: - # # This particular case is too awkward to resolve fully across - # # both py2 and py3. - # expected = expected.replace( - # "('red', 'Red'), ('blue', 'Blue'), ('green', 'Green')", - # "(u'red', u'Red'), (u'blue', u'Blue'), (u'green', u'Green')" - # ) + assert repr(TestSerializer()) == expected def test_method_field(self): From b00891b9bf79b6a2938ef5a004d3c028edd2549b Mon Sep 17 00:00:00 2001 From: Nicolae Godina Date: Thu, 2 Jan 2020 15:33:53 +0200 Subject: [PATCH 4/5] Fix: Support latest drf@3.11.0 and django@3.0.0 --- README.md | 6 +++--- rest_framework_mongoengine/fields.py | 25 ++++++++++++------------ rest_framework_mongoengine/routers.py | 3 ++- rest_framework_mongoengine/validators.py | 23 +++++++++++++++------- tests/test_basic.py | 20 +++++++++++++++---- tox.ini | 12 +++++------- 6 files changed, 55 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index c174809..0af612c 100644 --- a/README.md +++ b/README.md @@ -17,9 +17,9 @@ The features and differences of this package are described in [API documentation ## Requirements -* Django == 1.* | 2.* +* Django == 2.* | 3.0 * djangorestframework == 3.* -* mongoengine == 0.16.* | 0.17.* | 0.18.* +* mongoengine == 0.18.* | 0.19.* * blinker == 1.* (for mongoengine referencefields to work) ## Installation @@ -93,6 +93,6 @@ Documentation available [here](https://github.com/umutbozkurt/django-rest-framew @qwiglydee @BurkovBA @Vayel -@uixou +@uoxiu Feel free to mail me if you consider being a maintainer. diff --git a/rest_framework_mongoengine/fields.py b/rest_framework_mongoengine/fields.py index 2bcac29..2d8497f 100644 --- a/rest_framework_mongoengine/fields.py +++ b/rest_framework_mongoengine/fields.py @@ -2,18 +2,19 @@ from bson import DBRef, ObjectId from bson.errors import InvalidId -from django.utils.encoding import smart_text -from django.utils.translation import ugettext_lazy as _ -from mongoengine import fields as me_fields +from django.utils.encoding import smart_str +from django.utils.translation import gettext_lazy as _ from mongoengine import Document, EmbeddedDocument +from mongoengine import fields as me_fields from mongoengine.base import get_document from mongoengine.base.common import _document_registry -from mongoengine.errors import ValidationError as MongoValidationError from mongoengine.errors import DoesNotExist, NotRegistered +from mongoengine.errors import ValidationError as MongoValidationError from mongoengine.queryset import QuerySet, QuerySetManager from rest_framework import serializers from rest_framework.exceptions import ValidationError -from rest_framework.fields import empty, html +from rest_framework.fields import empty +from rest_framework.utils import html from rest_framework.settings import api_settings @@ -22,12 +23,12 @@ class ObjectIdField(serializers.Field): def to_internal_value(self, value): try: - return ObjectId(smart_text(value)) + return ObjectId(smart_str(value)) except InvalidId: raise serializers.ValidationError("'%s' is not a valid ObjectId" % value) def to_representation(self, value): - return smart_text(value) + return smart_str(value) class DocumentField(serializers.Field): @@ -59,13 +60,13 @@ def to_representation(self, obj): DRF ModelField uses ``value_to_string`` for this purpose. Mongoengine fields do not have such method. - This implementation uses ``django.utils.encoding.smart_text`` to convert everything to text, while keeping json-safe types intact. + This implementation uses ``django.utils.encoding.smart_str`` to convert everything to text, while keeping json-safe types intact. NB: The argument is whole object, instead of attribute value. This is upstream feature. Probably because the field can be represented by a complicated method with nontrivial way to extract data. """ value = self.model_field.__get__(obj, None) - return smart_text(value, strings_only=True) + return smart_str(value, strings_only=True) def run_validators(self, value): """ validate value. @@ -120,7 +121,7 @@ class GenericField(serializers.Field): """ Field for generic values. Recursively traverses lists and dicts. - Primitive values are serialized using ``django.utils.encoding.smart_text`` (keeping json-safe intact). + Primitive values are serialized using ``django.utils.encoding.smart_str`` (keeping json-safe intact). Embedded documents handled using temporary GenericEmbeddedField. No validation performed. @@ -142,7 +143,7 @@ def represent_data(self, data): elif data is None: return None else: - return smart_text(data, strings_only=True) + return smart_str(data, strings_only=True) def to_internal_value(self, value): return self.parse_data(value) @@ -546,7 +547,7 @@ class FileField(serializers.FileField): """ def to_representation(self, value): - return smart_text(value.grid_id) if hasattr(value, 'grid_id') else None + return smart_str(value.grid_id) if hasattr(value, 'grid_id') else None class ImageField(FileField): diff --git a/rest_framework_mongoengine/routers.py b/rest_framework_mongoengine/routers.py index cf6a0da..4c3cdaa 100644 --- a/rest_framework_mongoengine/routers.py +++ b/rest_framework_mongoengine/routers.py @@ -6,7 +6,8 @@ class MongoRouterMixin(object): Determines base_name from mongo queryset """ - def get_default_base_name(self, viewset): + + def get_default_basename(self, viewset): queryset = getattr(viewset, 'queryset', None) assert queryset is not None, ('`base_name` argument not specified, and could ' 'not automatically determine the name from the viewset, as ' diff --git a/rest_framework_mongoengine/validators.py b/rest_framework_mongoengine/validators.py index dcc6d14..26a7c8a 100644 --- a/rest_framework_mongoengine/validators.py +++ b/rest_framework_mongoengine/validators.py @@ -19,6 +19,15 @@ class UniqueValidator(MongoValidatorMixin, validators.UniqueValidator): Used by :class:`DocumentSerializer` for fields, present in unique indexes. """ + + def __init__(self, queryset, message=None, lookup=''): + """ + Setting empty string as default lookup for UniqueValidator. + For Mongoengine exact is a shortcut to query with regular experission. + This fixes https://github.com/umutbozkurt/django-rest-framework-mongoengine/issues/264 + """ + super(UniqueValidator, self).__init__(queryset, message, lookup) + def __call__(self, value, serializer_field): # Determine the underlying model field name. This may not be the # same as the serializer field name if `source=<>` is set. @@ -45,18 +54,17 @@ class UniqueTogetherValidator(MongoValidatorMixin, validators.UniqueTogetherVali Used by :class:`DocumentSerializer` for fields, present in unique indexes. """ - def __call__(self, attrs, serializer_field): + def __call__(self, attrs, serializer): try: - self.enforce_required_fields(attrs) + self.enforce_required_fields(attrs, serializer) except SkipField: return - field_name = serializer_field.source_attrs[-1] # Determine the existing instance, if this is an update operation. - instance = getattr(serializer_field.parent, 'instance', None) + instance = getattr(serializer, 'instance', None) queryset = self.queryset - queryset = self.filter_queryset(value, queryset, field_name) + queryset = self.filter_queryset(attrs, queryset, serializer) queryset = self.exclude_current_instance(queryset, instance) # Ignore validation if any field is None @@ -79,9 +87,10 @@ class OptionalUniqueTogetherValidator(UniqueTogetherValidator): """ This validator passes validation if all of validation fields are missing. (for use with partial data) """ - def enforce_required_fields(self, attrs): + + def enforce_required_fields(self, attrs, serializer): try: - super(OptionalUniqueTogetherValidator, self).enforce_required_fields(attrs) + super(OptionalUniqueTogetherValidator, self).enforce_required_fields(attrs, serializer) except ValidationError as e: if set(e.detail.keys()) == set(self.fields): raise SkipField() diff --git a/tests/test_basic.py b/tests/test_basic.py index 5598df0..618691d 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -19,7 +19,6 @@ from rest_framework import serializers from rest_framework_mongoengine.serializers import DocumentSerializer - from .utils import dedent @@ -81,11 +80,13 @@ class FieldOptionsModel(Document): value_limit_field = fields.IntField(min_value=3, max_value=12) decimal_field = fields.DecimalField(precision=4, max_value=9999) + DECIMAL_CHOICES = (('low', Decimal('0.1')), ('medium', Decimal('0.5')), ('high', Decimal('0.9'))) class ComplexChoicesModel(Document): - choices_field_with_nonstandard_args = fields.DecimalField(precision=1, choices=DECIMAL_CHOICES, verbose_name='A label') + choices_field_with_nonstandard_args = fields.DecimalField(precision=1, choices=DECIMAL_CHOICES, + verbose_name='A label') class TestRegularFieldMappings(TestCase): @@ -95,6 +96,7 @@ def test_regular_fields(self): """ Model fields should map to their equivelent serializer fields. """ + class TestSerializer(DocumentSerializer): class Meta: model = RegularModel @@ -128,6 +130,7 @@ def test_meta_fields(self): """ Serializer should respect Meta.fields """ + class TestSerializer(DocumentSerializer): class Meta: model = RegularModel @@ -145,6 +148,7 @@ def test_meta_exclude(self): """ Serializer should respect Meta.exclude """ + class TestSerializer(DocumentSerializer): class Meta: model = RegularModel @@ -192,7 +196,7 @@ class Meta: value_limit_field = IntegerField(max_value=12, min_value=3, required=False) decimal_field = DecimalField(decimal_places=4, max_digits=8, max_value=9999, required=False) """) - + assert repr(TestSerializer()) == expected def test_method_field(self): @@ -200,6 +204,7 @@ def test_method_field(self): Properties and methods on the model should be allowed as `Meta.fields` values, and should map to `ReadOnlyField`. """ + class TestSerializer(DocumentSerializer): class Meta: model = RegularModel @@ -216,6 +221,7 @@ def test_pk_fields(self): """ Both `pk` and the actual primary key name are valid in `Meta.fields`. """ + class TestSerializer(DocumentSerializer): class Meta: model = AutoFieldModel @@ -232,6 +238,7 @@ def test_id_field(self): """ The autocreated id field should be mapped properly """ + class TestSerializer(DocumentSerializer): class Meta: model = OneFieldModel @@ -247,6 +254,7 @@ def test_extra_field_kwargs(self): """ Ensure `extra_kwargs` are passed to generated fields. """ + class TestSerializer(DocumentSerializer): class Meta: model = RegularModel @@ -265,6 +273,7 @@ def test_invalid_field(self): Field names that do not map to a model field or relationship should raise a configuration errror. """ + class TestSerializer(DocumentSerializer): class Meta: model = RegularModel @@ -280,6 +289,7 @@ def test_missing_field(self): Fields that have been declared on the serializer class must be included in the `Meta.fields` if it exists. """ + class TestSerializer(DocumentSerializer): missing = serializers.ReadOnlyField() @@ -300,6 +310,7 @@ def test_missing_superclass_field_not_included(self): Fields that have been declared on a parent of the serializer class may be excluded from the `Meta.fields` option. """ + class TestSerializer(DocumentSerializer): missing = serializers.ReadOnlyField() @@ -321,6 +332,7 @@ def test_missing_superclass_field_excluded(self): Fields that have been declared on a parent of the serializer class may be excluded from the `Meta.fields` option. """ + class TestSerializer(DocumentSerializer): missing = serializers.ReadOnlyField() @@ -333,7 +345,7 @@ class ChildSerializer(TestSerializer): class Meta: model = RegularModel - exclude = ('missing', ) + exclude = ('missing',) ChildSerializer().fields diff --git a/tox.ini b/tox.ini index 86eb371..5ba3dae 100644 --- a/tox.ini +++ b/tox.ini @@ -1,18 +1,16 @@ [tox] -envlist=dj{111,22}-py{36}-me{016,018} +envlist=dj{2,30}-py{36}-me{019} [testenv] commands = ./runtests.py --nolint {posargs} --coverage deps = -rrequirements/requirements-testing.txt - dj111: Django==1.11.* - dj22: Django==2.2.* + dj2: Django==2.* + dj30: Django==3.0.* djangorestframework==3.* blinker==1.* - me016: mongoengine==0.16.* - me016: pymongo==3.* - me018: mongoengine==0.18.* - me018: pymongo==3.* + me019: mongoengine==0.19.* + me019: pymongo==3.* # We don't have any documentation source in the repository yet, so # documentation generation is commented-out. Sphinx or Markdown? From 95d93ab363aae15ac26ed3b66c28ea5da3736700 Mon Sep 17 00:00:00 2001 From: Nicolae Godina Date: Thu, 2 Jan 2020 15:38:15 +0200 Subject: [PATCH 5/5] update tox for travis ci --- .travis.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3774b6d..cb73938 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,10 +8,8 @@ services: mongodb sudo: false env: - - TOX_ENV=dj111-py36-me016 - - TOX_ENV=dj111-py36-me018 - - TOX_ENV=dj22-py36-me016 - - TOX_ENV=dj22-py36-me018 + - TOX_ENV=dj2-py36-me019 + - TOX_ENV=dj30-py36-me019 matrix: fast_finish: true