Skip to content

Commit 2b3cbcc

Browse files
authored
Merge pull request #115 from gsakkis/test-coverage
Drop support for EOL Django versions < 2 and increase test coverage
2 parents fe5c990 + ff3aec6 commit 2b3cbcc

14 files changed

+60
-116
lines changed

.travis.yml

Lines changed: 7 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,6 @@ language: python
33
dist: xenial
44
matrix:
55
include:
6-
- python: '3.5'
7-
env: TOXENV=py35-django111
8-
- python: '3.6'
9-
env: TOXENV=py36-django111
10-
- python: '3.7'
11-
env: TOXENV=py37-django111
12-
13-
- python: '3.5'
14-
env: TOXENV=py35-django20
15-
- python: '3.6'
16-
env: TOXENV=py36-django20
17-
- python: '3.7'
18-
env: TOXENV=py37-django20
19-
20-
- python: '3.5'
21-
env: TOXENV=py35-django21
22-
- python: '3.6'
23-
env: TOXENV=py36-django21
24-
- python: '3.7'
25-
env: TOXENV=py37-django21
26-
27-
- python: '3.5'
28-
env: TOXENV=py35-django22
296
- python: '3.6'
307
env: TOXENV=py36-django22
318
- python: '3.7'
@@ -40,6 +17,13 @@ matrix:
4017
- python: '3.8'
4118
env: TOXENV=py38-django30
4219

20+
- python: '3.6'
21+
env: TOXENV=py36-django31
22+
- python: '3.7'
23+
env: TOXENV=py37-django31
24+
- python: '3.8'
25+
env: TOXENV=py38-django31
26+
4327
install:
4428
- pip install -U pip wheel setuptools
4529
- pip install tox

enumfields/drf/fields.py

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
class EnumField(ChoiceField):
77
def __init__(self, enum, lenient=False, ints_as_names=False, **kwargs):
88
"""
9-
:param enum: The enumeration class.
9+
:param enum: The enumeration class.
1010
:param lenient: Whether to allow lenient parsing (case-insensitive, by value or name)
1111
:type lenient: bool
1212
:param ints_as_names: Whether to serialize integer-valued enums by their name, not the integer value
@@ -19,17 +19,11 @@ def __init__(self, enum, lenient=False, ints_as_names=False, **kwargs):
1919
super().__init__(**kwargs)
2020

2121
def to_representation(self, instance):
22-
if instance in ('', None):
23-
return instance
24-
try:
25-
if not isinstance(instance, self.enum):
26-
instance = self.enum(instance) # Try to cast it
27-
if self.ints_as_names and isinstance(instance.value, int):
28-
# If the enum value is an int, assume the name is more representative
29-
return instance.name.lower()
30-
return instance.value
31-
except ValueError:
32-
raise ValueError('Invalid value [{!r}] of enum {}'.format(instance, self.enum.__name__))
22+
assert isinstance(instance, self.enum), instance
23+
if self.ints_as_names and isinstance(instance.value, int):
24+
# If the enum value is an int, assume the name is more representative
25+
return instance.name.lower()
26+
return instance.value
3327

3428
def to_internal_value(self, data):
3529
if isinstance(data, self.enum):

enumfields/fields.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from enum import Enum
22

3-
import django
43
from django.core import checks
54
from django.core.exceptions import ValidationError
65
from django.db import models
@@ -79,10 +78,7 @@ def value_to_string(self, obj):
7978
Since most of the enum values are strings or integers we WILL NOT convert it to string
8079
to enable integers to be serialized natively.
8180
"""
82-
if django.VERSION >= (2, 0):
83-
value = self.value_from_object(obj)
84-
else:
85-
value = self._get_val_from_obj(obj)
81+
value = self.value_from_object(obj)
8682
return value.value if value else None
8783

8884
def get_default(self):

enumfields/forms.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,6 @@ def prepare_value(self, value):
1717
value = value.value
1818
return force_str(value)
1919

20-
def valid_value(self, value):
21-
if hasattr(value, "value"): # Try validation using the enum value first.
22-
if super().valid_value(value.value):
23-
return True
24-
return super().valid_value(value)
25-
2620
def to_python(self, value):
2721
if isinstance(value, Enum):
2822
value = value.value

setup.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,9 @@ def read_version(fname):
2929
'License :: OSI Approved :: MIT License',
3030
'Environment :: Web Environment',
3131
'Framework :: Django',
32-
'Framework :: Django :: 1.11',
33-
'Framework :: Django :: 2.0',
34-
'Framework :: Django :: 2.1',
3532
'Framework :: Django :: 2.2',
3633
'Framework :: Django :: 3.0',
34+
'Framework :: Django :: 3.1',
3735
'Intended Audience :: Developers',
3836
'Operating System :: OS Independent',
3937
'Programming Language :: Python',

tests/models.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,10 @@ class MyModel(models.Model):
1919

2020
random_code = models.TextField(null=True, blank=True)
2121

22-
zero_field = EnumIntegerField(ZeroEnum, null=True, default=None, blank=True)
22+
zero = EnumIntegerField(ZeroEnum, default=ZeroEnum.ZERO)
23+
zero2 = EnumIntegerField(ZeroEnum, default=0, blank=True)
24+
2325
int_enum = EnumIntegerField(IntegerEnum, null=True, default=None, blank=True)
2426
int_enum_not_editable = EnumIntegerField(IntegerEnum, default=IntegerEnum.A, editable=False)
2527

26-
zero2 = EnumIntegerField(ZeroEnum, default=ZeroEnum.ZERO)
2728
labeled_enum = EnumField(LabeledEnum, blank=True, null=True)

tests/settings.py

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import django
2-
31
SECRET_KEY = 'SEKRIT'
42

53
INSTALLED_APPS = (
@@ -19,18 +17,11 @@
1917
},
2018
}
2119

22-
if django.VERSION[0] < 2:
23-
MIDDLEWARE_CLASSES = (
24-
'django.contrib.sessions.middleware.SessionMiddleware',
25-
'django.contrib.auth.middleware.AuthenticationMiddleware',
26-
'django.contrib.messages.middleware.MessageMiddleware',
27-
)
28-
else:
29-
MIDDLEWARE = (
30-
'django.contrib.sessions.middleware.SessionMiddleware',
31-
'django.contrib.auth.middleware.AuthenticationMiddleware',
32-
'django.contrib.messages.middleware.MessageMiddleware',
33-
)
20+
MIDDLEWARE = (
21+
'django.contrib.sessions.middleware.SessionMiddleware',
22+
'django.contrib.auth.middleware.AuthenticationMiddleware',
23+
'django.contrib.messages.middleware.MessageMiddleware',
24+
)
3425

3526
# Speed up tests by using a deliberately weak hasher instead of pbkdf/bcrypt:
3627
PASSWORD_HASHERS = ['django.contrib.auth.hashers.MD5PasswordHasher']

tests/test_checks.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@
66
def test_shortness_check():
77
class TestModel(models.Model):
88
f = EnumField(LabeledEnum, max_length=3, blank=True, null=True)
9+
f2 = EnumField(LabeledEnum, blank=True, null=True)
910
assert any([m.id == 'enumfields.max_length_fit' for m in TestModel.check()])

tests/test_django_admin.py

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,12 @@
22
import uuid
33

44
import pytest
5+
from django.urls import reverse
56
from enumfields import EnumIntegerField
67

78
from .enums import Color, IntegerEnum, Taste, ZeroEnum
89
from .models import MyModel
910

10-
try:
11-
from django.core.urlresolvers import reverse # Django 1.x
12-
except ImportError:
13-
from django.urls import reverse # Django 2.x
14-
15-
16-
1711

1812
@pytest.mark.django_db
1913
@pytest.mark.urls('tests.urls')
@@ -25,18 +19,15 @@ def test_model_admin_post(admin_client):
2519
'taste': Taste.UMAMI.value,
2620
'taste_int': Taste.SWEET.value,
2721
'random_code': secret_uuid,
28-
'zero2': ZeroEnum.ZERO.value,
22+
'zero': ZeroEnum.ZERO.value,
2923
}
3024
response = admin_client.post(url, follow=True, data=post_data)
3125
response.render()
3226
text = response.content
3327

3428
assert b"This field is required" not in text
3529
assert b"Select a valid choice" not in text
36-
try:
37-
inst = MyModel.objects.get(random_code=secret_uuid)
38-
except DoesNotExist:
39-
assert False, "Object wasn't created in the database"
30+
inst = MyModel.objects.get(random_code=secret_uuid)
4031
assert inst.color == Color.RED, "Redness not assured"
4132
assert inst.taste == Taste.UMAMI, "Umami not there"
4233
assert inst.taste_int == Taste.SWEET, "Not sweet enough"

tests/test_django_models.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
from enum import IntEnum
2-
31
from django.db import connection
42

53
import pytest
@@ -26,6 +24,10 @@ def test_field_value():
2624
MyModel.objects.filter(color='xx')[0]
2725

2826

27+
def test_descriptor():
28+
assert MyModel.color.field.enum is Color
29+
30+
2931
@pytest.mark.django_db
3032
def test_db_value():
3133
m = MyModel(color=Color.RED)
@@ -63,13 +65,14 @@ def test_enum_int_field_validators():
6365
@pytest.mark.django_db
6466
def test_zero_enum_loads():
6567
# Verifies that we can save and load enums with the value of 0 (zero).
66-
m = MyModel(zero_field=ZeroEnum.ZERO,
67-
color=Color.GREEN)
68+
m = MyModel(zero=ZeroEnum.ZERO, color=Color.GREEN)
6869
m.save()
69-
assert m.zero_field == ZeroEnum.ZERO
70+
assert m.zero == ZeroEnum.ZERO
71+
assert m.zero2 == ZeroEnum.ZERO
7072

7173
m = MyModel.objects.get(id=m.id)
72-
assert m.zero_field == ZeroEnum.ZERO
74+
assert m.zero == ZeroEnum.ZERO
75+
assert m.zero2 == ZeroEnum.ZERO
7376

7477

7578
@pytest.mark.django_db

tests/test_form_fields.py

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import django
21
from django.db.models import BLANK_CHOICE_DASH
32
from django.forms.models import model_to_dict, modelform_factory
43

@@ -10,39 +9,39 @@
109

1110
def get_form(**kwargs):
1211
instance = MyModel(color=Color.RED)
13-
FormClass = modelform_factory(MyModel, fields=("color", "zero2", "int_enum"))
12+
FormClass = modelform_factory(MyModel, fields=("color", "zero", "int_enum"))
1413
return FormClass(instance=instance, **kwargs)
1514

1615

1716
@pytest.mark.django_db
1817
def test_unbound_form_with_instance():
1918
form = get_form()
20-
if django.VERSION >= (1, 11):
21-
assert 'value="r" selected' in str(form["color"])
22-
else:
23-
assert 'value="r" selected="selected"' in str(form["color"])
19+
assert 'value="r" selected' in str(form["color"])
2420

2521

2622
@pytest.mark.django_db
2723
def test_bound_form_with_instance():
2824
form = get_form(data={"color": "g"})
29-
if django.VERSION >= (1, 11):
30-
assert 'value="g" selected' in str(form["color"])
31-
else:
32-
assert 'value="g" selected="selected"' in str(form["color"])
25+
assert 'value="g" selected' in str(form["color"])
26+
27+
28+
@pytest.mark.django_db
29+
def test_bound_form_with_instance_empty():
30+
form = get_form(data={"color": None})
31+
assert 'value="" selected' in str(form["color"])
3332

3433

3534
def test_choices():
3635
form = get_form()
37-
assert form.base_fields["zero2"].choices == [(0, 'Zero'), (1, 'One')]
36+
assert form.base_fields["zero"].choices == [(0, 'Zero'), (1, 'One')]
3837
assert form.base_fields["int_enum"].choices == BLANK_CHOICE_DASH + [(0, 'foo'), (1, 'B')]
3938

4039

4140
def test_validation():
42-
form = get_form(data={"color": Color.GREEN, "zero2": ZeroEnum.ZERO})
41+
form = get_form(data={"color": Color.GREEN, "zero": ZeroEnum.ZERO})
4342
assert form.is_valid(), form.errors
4443

45-
instance = MyModel(color=Color.RED, zero2=ZeroEnum.ZERO)
46-
data = model_to_dict(instance, fields=("color", "zero2", "int_enum"))
44+
instance = MyModel(color=Color.RED, zero=ZeroEnum.ZERO)
45+
data = model_to_dict(instance, fields=("color", "zero", "int_enum"))
4746
form = get_form(data=data)
4847
assert form.is_valid(), form.errors

tests/test_serializers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ def test_serialize(int_names):
4848
def test_deserialize(lenient_data, lenient_serializer):
4949
secret_uuid = str(uuid.uuid4())
5050
data = {
51-
'color': Color.BLUE.value,
51+
'color': Color.BLUE,
5252
'taste': Taste.UMAMI.value,
5353
'int_enum': IntegerEnum.B.value,
5454
'random_code': secret_uuid,

tests/urls.py

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,13 @@
1-
import django
21
from django.conf import settings
3-
from django.conf.urls import include, url
2+
from django.urls import re_path
43
from django.contrib import admin
54
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
65

76
admin.autodiscover()
87

9-
if django.VERSION[0] < 2:
10-
urlpatterns = [
11-
url(r'^admin/', include(admin.site.urls)),
12-
]
13-
else:
14-
urlpatterns = [
15-
url(r'^admin/', admin.site.urls),
16-
]
8+
urlpatterns = [
9+
re_path(r'^admin/', admin.site.urls),
10+
]
1711

1812
if settings.DEBUG:
1913
urlpatterns = staticfiles_urlpatterns() + urlpatterns

tox.ini

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
11
[tox]
22
envlist =
3-
py{35,36}-django{111}, py{35,36,37}-django{20}, py{36,37}-django{21,22,30}
3+
py{36,37}-django{22,30,31}
44

55
[testenv]
66
setenv = PYTHONPATH = {toxinidir}
7-
commands = py.test -s tests
7+
commands = py.test -s tests --cov=enumfields --cov-report=term-missing
88
deps =
9-
django{111}: djangorestframework<3.7
10-
django{20,21,22,30}: djangorestframework>=3.7
11-
pytest-django==3.8.0
12-
django111: Django>=1.11,<2
13-
django20: Django>=2.0,<2.1
14-
django21: Django>=2.1,<2.2
9+
djangorestframework>=3.7
10+
pytest-django
11+
pytest-coverage
1512
django22: Django>=2.2,<2.3
1613
django30: Django>=3.0,<3.1
14+
django31: Django>=3.1,<3.2

0 commit comments

Comments
 (0)