Skip to content

Commit d4016d8

Browse files
authored
Add Django 5.0 support (#9233)
* Update tox.ini * Update tests for Django 5.0 * Update documentation * Update setup.py
1 parent a45432b commit d4016d8

File tree

7 files changed

+97
-85
lines changed

7 files changed

+97
-85
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ There is a live example API for testing purposes, [available here][sandbox].
5656
# Requirements
5757

5858
* Python 3.6+
59-
* Django 4.2, 4.1, 4.0, 3.2, 3.1, 3.0
59+
* Django 5.0, 4.2, 4.1, 4.0, 3.2, 3.1, 3.0
6060

6161
We **highly recommend** and only officially support the latest patch release of
6262
each Python and Django series.

docs/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ continued development by **[signing up for a paid plan][funding]**.
8787
REST framework requires the following:
8888

8989
* Python (3.6, 3.7, 3.8, 3.9, 3.10, 3.11)
90-
* Django (3.0, 3.1, 3.2, 4.0, 4.1, 4.2)
90+
* Django (3.0, 3.1, 3.2, 4.0, 4.1, 4.2, 5.0)
9191

9292
We **highly recommend** and only officially support the latest patch release of
9393
each Python and Django series.

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ def get_version(package):
9696
'Framework :: Django :: 4.0',
9797
'Framework :: Django :: 4.1',
9898
'Framework :: Django :: 4.2',
99+
'Framework :: Django :: 5.0',
99100
'Intended Audience :: Developers',
100101
'License :: OSI Approved :: BSD License',
101102
'Operating System :: OS Independent',

tests/test_fields.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1538,7 +1538,8 @@ class TestNoOutputFormatDateTimeField(FieldValues):
15381538
field = serializers.DateTimeField(format=None)
15391539

15401540

1541-
class TestNaiveDateTimeField(FieldValues):
1541+
@override_settings(TIME_ZONE='UTC', USE_TZ=False)
1542+
class TestNaiveDateTimeField(FieldValues, TestCase):
15421543
"""
15431544
Valid and invalid values for `DateTimeField` with naive datetimes.
15441545
"""

tests/test_model_serializer.py

Lines changed: 42 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import datetime
99
import decimal
1010
import json # noqa
11+
import re
1112
import sys
1213
import tempfile
1314

@@ -169,53 +170,52 @@ class Meta:
169170
model = RegularFieldsModel
170171
fields = '__all__'
171172

172-
expected = dedent("""
173-
TestSerializer():
174-
auto_field = IntegerField(read_only=True)
175-
big_integer_field = IntegerField()
176-
boolean_field = BooleanField(default=False, required=False)
177-
char_field = CharField(max_length=100)
178-
comma_separated_integer_field = CharField(max_length=100, validators=[<django.core.validators.RegexValidator object>])
179-
date_field = DateField()
180-
datetime_field = DateTimeField()
181-
decimal_field = DecimalField(decimal_places=1, max_digits=3)
182-
email_field = EmailField(max_length=100)
183-
float_field = FloatField()
184-
integer_field = IntegerField()
185-
null_boolean_field = BooleanField(allow_null=True, default=False, required=False)
186-
positive_integer_field = IntegerField()
187-
positive_small_integer_field = IntegerField()
188-
slug_field = SlugField(allow_unicode=False, max_length=100)
189-
small_integer_field = IntegerField()
190-
text_field = CharField(max_length=100, style={'base_template': 'textarea.html'})
191-
file_field = FileField(max_length=100)
192-
time_field = TimeField()
193-
url_field = URLField(max_length=100)
194-
custom_field = ModelField(model_field=<tests.test_model_serializer.CustomField: custom_field>)
195-
file_path_field = FilePathField(path=%r)
173+
expected = dedent(r"""
174+
TestSerializer\(\):
175+
auto_field = IntegerField\(read_only=True\)
176+
big_integer_field = IntegerField\(.*\)
177+
boolean_field = BooleanField\(default=False, required=False\)
178+
char_field = CharField\(max_length=100\)
179+
comma_separated_integer_field = CharField\(max_length=100, validators=\[<django.core.validators.RegexValidator object>\]\)
180+
date_field = DateField\(\)
181+
datetime_field = DateTimeField\(\)
182+
decimal_field = DecimalField\(decimal_places=1, max_digits=3\)
183+
email_field = EmailField\(max_length=100\)
184+
float_field = FloatField\(\)
185+
integer_field = IntegerField\(.*\)
186+
null_boolean_field = BooleanField\(allow_null=True, default=False, required=False\)
187+
positive_integer_field = IntegerField\(.*\)
188+
positive_small_integer_field = IntegerField\(.*\)
189+
slug_field = SlugField\(allow_unicode=False, max_length=100\)
190+
small_integer_field = IntegerField\(.*\)
191+
text_field = CharField\(max_length=100, style={'base_template': 'textarea.html'}\)
192+
file_field = FileField\(max_length=100\)
193+
time_field = TimeField\(\)
194+
url_field = URLField\(max_length=100\)
195+
custom_field = ModelField\(model_field=<tests.test_model_serializer.CustomField: custom_field>\)
196+
file_path_field = FilePathField\(path=%r\)
196197
""" % tempfile.gettempdir())
197-
198-
self.assertEqual(repr(TestSerializer()), expected)
198+
assert re.search(expected, repr(TestSerializer())) is not None
199199

200200
def test_field_options(self):
201201
class TestSerializer(serializers.ModelSerializer):
202202
class Meta:
203203
model = FieldOptionsModel
204204
fields = '__all__'
205205

206-
expected = dedent("""
207-
TestSerializer():
208-
id = IntegerField(label='ID', read_only=True)
209-
value_limit_field = IntegerField(max_value=10, min_value=1)
210-
length_limit_field = CharField(max_length=12, min_length=3)
211-
blank_field = CharField(allow_blank=True, max_length=10, required=False)
212-
null_field = IntegerField(allow_null=True, required=False)
213-
default_field = IntegerField(default=0, required=False)
214-
descriptive_field = IntegerField(help_text='Some help text', label='A label')
215-
choices_field = ChoiceField(choices=(('red', 'Red'), ('blue', 'Blue'), ('green', 'Green')))
216-
text_choices_field = ChoiceField(choices=(('red', 'Red'), ('blue', 'Blue'), ('green', 'Green')))
206+
expected = dedent(r"""
207+
TestSerializer\(\):
208+
id = IntegerField\(label='ID', read_only=True\)
209+
value_limit_field = IntegerField\(max_value=10, min_value=1\)
210+
length_limit_field = CharField\(max_length=12, min_length=3\)
211+
blank_field = CharField\(allow_blank=True, max_length=10, required=False\)
212+
null_field = IntegerField\(allow_null=True,.*required=False\)
213+
default_field = IntegerField\(default=0,.*required=False\)
214+
descriptive_field = IntegerField\(help_text='Some help text', label='A label'.*\)
215+
choices_field = ChoiceField\(choices=(?:\[|\()\('red', 'Red'\), \('blue', 'Blue'\), \('green', 'Green'\)(?:\]|\))\)
216+
text_choices_field = ChoiceField\(choices=(?:\[|\()\('red', 'Red'\), \('blue', 'Blue'\), \('green', 'Green'\)(?:\]|\))\)
217217
""")
218-
self.assertEqual(repr(TestSerializer()), expected)
218+
assert re.search(expected, repr(TestSerializer())) is not None
219219

220220
def test_nullable_boolean_field_choices(self):
221221
class NullableBooleanChoicesModel(models.Model):
@@ -1334,12 +1334,12 @@ class Meta:
13341334
}
13351335
}
13361336

1337-
expected = dedent("""
1338-
TestSerializer():
1339-
number_field = IntegerField(source='integer_field')
1337+
expected = dedent(r"""
1338+
TestSerializer\(\):
1339+
number_field = IntegerField\(.*source='integer_field'\)
13401340
""")
13411341
self.maxDiff = None
1342-
self.assertEqual(repr(TestSerializer()), expected)
1342+
assert re.search(expected, repr(TestSerializer())) is not None
13431343

13441344

13451345
class Issue6110TestModel(models.Model):

tests/test_validators.py

Lines changed: 47 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import datetime
2+
import re
23
from unittest.mock import MagicMock, patch
34

45
import pytest
6+
from django import VERSION as django_version
57
from django.db import DataError, models
68
from django.test import TestCase
79

@@ -112,11 +114,15 @@ def test_updated_instance_excluded(self):
112114
def test_doesnt_pollute_model(self):
113115
instance = AnotherUniquenessModel.objects.create(code='100')
114116
serializer = AnotherUniquenessSerializer(instance)
115-
assert AnotherUniquenessModel._meta.get_field('code').validators == []
117+
assert all(
118+
["Unique" not in repr(v) for v in AnotherUniquenessModel._meta.get_field('code').validators]
119+
)
116120

117121
# Accessing data shouldn't effect validators on the model
118122
serializer.data
119-
assert AnotherUniquenessModel._meta.get_field('code').validators == []
123+
assert all(
124+
["Unique" not in repr(v) for v in AnotherUniquenessModel._meta.get_field('code').validators]
125+
)
120126

121127
def test_related_model_is_unique(self):
122128
data = {'username': 'Existing', 'email': '[email protected]'}
@@ -193,15 +199,15 @@ def setUp(self):
193199

194200
def test_repr(self):
195201
serializer = UniquenessTogetherSerializer()
196-
expected = dedent("""
197-
UniquenessTogetherSerializer():
198-
id = IntegerField(label='ID', read_only=True)
199-
race_name = CharField(max_length=100, required=True)
200-
position = IntegerField(required=True)
202+
expected = dedent(r"""
203+
UniquenessTogetherSerializer\(\):
204+
id = IntegerField\(label='ID', read_only=True\)
205+
race_name = CharField\(max_length=100, required=True\)
206+
position = IntegerField\(.*required=True\)
201207
class Meta:
202-
validators = [<UniqueTogetherValidator(queryset=UniquenessTogetherModel.objects.all(), fields=('race_name', 'position'))>]
208+
validators = \[<UniqueTogetherValidator\(queryset=UniquenessTogetherModel.objects.all\(\), fields=\('race_name', 'position'\)\)>\]
203209
""")
204-
assert repr(serializer) == expected
210+
assert re.search(expected, repr(serializer)) is not None
205211

206212
def test_is_not_unique_together(self):
207213
"""
@@ -282,13 +288,13 @@ class Meta:
282288
read_only_fields = ('race_name',)
283289

284290
serializer = ReadOnlyFieldSerializer()
285-
expected = dedent("""
286-
ReadOnlyFieldSerializer():
287-
id = IntegerField(label='ID', read_only=True)
288-
race_name = CharField(read_only=True)
289-
position = IntegerField(required=True)
291+
expected = dedent(r"""
292+
ReadOnlyFieldSerializer\(\):
293+
id = IntegerField\(label='ID', read_only=True\)
294+
race_name = CharField\(read_only=True\)
295+
position = IntegerField\(.*required=True\)
290296
""")
291-
assert repr(serializer) == expected
297+
assert re.search(expected, repr(serializer)) is not None
292298

293299
def test_read_only_fields_with_default(self):
294300
"""
@@ -366,14 +372,14 @@ class Meta:
366372
fields = ['name', 'position']
367373

368374
serializer = TestSerializer()
369-
expected = dedent("""
370-
TestSerializer():
371-
name = CharField(source='race_name')
372-
position = IntegerField()
375+
expected = dedent(r"""
376+
TestSerializer\(\):
377+
name = CharField\(source='race_name'\)
378+
position = IntegerField\(.*\)
373379
class Meta:
374-
validators = [<UniqueTogetherValidator(queryset=UniquenessTogetherModel.objects.all(), fields=('name', 'position'))>]
380+
validators = \[<UniqueTogetherValidator\(queryset=UniquenessTogetherModel.objects.all\(\), fields=\('name', 'position'\)\)>\]
375381
""")
376-
assert repr(serializer) == expected
382+
assert re.search(expected, repr(serializer)) is not None
377383

378384
def test_default_validator_with_multiple_fields_with_same_source(self):
379385
class TestSerializer(serializers.ModelSerializer):
@@ -411,13 +417,13 @@ class Meta:
411417
validators = []
412418

413419
serializer = NoValidatorsSerializer()
414-
expected = dedent("""
415-
NoValidatorsSerializer():
416-
id = IntegerField(label='ID', read_only=True)
417-
race_name = CharField(max_length=100)
418-
position = IntegerField()
420+
expected = dedent(r"""
421+
NoValidatorsSerializer\(\):
422+
id = IntegerField\(label='ID', read_only=True.*\)
423+
race_name = CharField\(max_length=100\)
424+
position = IntegerField\(.*\)
419425
""")
420-
assert repr(serializer) == expected
426+
assert re.search(expected, repr(serializer)) is not None
421427

422428
def test_ignore_validation_for_null_fields(self):
423429
# None values that are on fields which are part of the uniqueness
@@ -540,16 +546,16 @@ def test_repr(self):
540546
# the order of validators isn't deterministic so delete
541547
# fancy_conditions field that has two of them
542548
del serializer.fields['fancy_conditions']
543-
expected = dedent("""
544-
UniqueConstraintSerializer():
545-
id = IntegerField(label='ID', read_only=True)
546-
race_name = CharField(max_length=100, required=True)
547-
position = IntegerField(required=True)
548-
global_id = IntegerField(validators=[<UniqueValidator(queryset=UniqueConstraintModel.objects.all())>])
549+
expected = dedent(r"""
550+
UniqueConstraintSerializer\(\):
551+
id = IntegerField\(label='ID', read_only=True\)
552+
race_name = CharField\(max_length=100, required=True\)
553+
position = IntegerField\(.*required=True\)
554+
global_id = IntegerField\(.*validators=\[<UniqueValidator\(queryset=UniqueConstraintModel.objects.all\(\)\)>\]\)
549555
class Meta:
550-
validators = [<UniqueTogetherValidator(queryset=<QuerySet [<UniqueConstraintModel: UniqueConstraintModel object (1)>, <UniqueConstraintModel: UniqueConstraintModel object (2)>]>, fields=('race_name', 'position'))>]
556+
validators = \[<UniqueTogetherValidator\(queryset=<QuerySet \[<UniqueConstraintModel: UniqueConstraintModel object \(1\)>, <UniqueConstraintModel: UniqueConstraintModel object \(2\)>\]>, fields=\('race_name', 'position'\)\)>\]
551557
""")
552-
assert repr(serializer) == expected
558+
assert re.search(expected, repr(serializer)) is not None
553559

554560
def test_unique_together_field(self):
555561
"""
@@ -569,15 +575,18 @@ def test_single_field_uniq_validators(self):
569575
UniqueConstraint with single field must be transformed into
570576
field's UniqueValidator
571577
"""
578+
# Django 5 includes Max and Min values validators for IntergerField
579+
extra_validators_qty = 2 if django_version[0] >= 5 else 0
580+
#
572581
serializer = UniqueConstraintSerializer()
573582
assert len(serializer.validators) == 1
574583
validators = serializer.fields['global_id'].validators
575-
assert len(validators) == 1
584+
assert len(validators) == 1 + extra_validators_qty
576585
assert validators[0].queryset == UniqueConstraintModel.objects
577586

578587
validators = serializer.fields['fancy_conditions'].validators
579-
assert len(validators) == 2
580-
ids_in_qs = {frozenset(v.queryset.values_list(flat=True)) for v in validators}
588+
assert len(validators) == 2 + extra_validators_qty
589+
ids_in_qs = {frozenset(v.queryset.values_list(flat=True)) for v in validators if hasattr(v, "queryset")}
581590
assert ids_in_qs == {frozenset([1]), frozenset([3])}
582591

583592

tox.ini

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ envlist =
44
{py36,py37,py38,py39}-django31
55
{py36,py37,py38,py39,py310}-django32
66
{py38,py39,py310}-{django40,django41,django42,djangomain}
7-
{py311}-{django41,django42,djangomain}
8-
{py312}-{django42,djangomain}
7+
{py311}-{django41,django42,django50,djangomain}
8+
{py312}-{django42,djanggo50,djangomain}
99
base
1010
dist
1111
docs
@@ -23,6 +23,7 @@ deps =
2323
django40: Django>=4.0,<4.1
2424
django41: Django>=4.1,<4.2
2525
django42: Django>=4.2,<5.0
26+
django50: Django>=5.0,<5.1
2627
djangomain: https://github.com/django/django/archive/main.tar.gz
2728
-rrequirements/requirements-testing.txt
2829
-rrequirements/requirements-optionals.txt

0 commit comments

Comments
 (0)