diff --git a/CHANGELOG.md b/CHANGELOG.md index b596fc52..ba9307d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,13 +8,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased](https://github.com/model-bakers/model_bakery/tree/main) ### Added - - Add Django 5.0 support ### Changed ### Removed -- Drop Django 4.1 support (reached end of life) +- Drop Django 3.2 and 4.1 support (reached end of life) ## [1.17.0](https://pypi.org/project/model-bakery/1.17.0/) diff --git a/docs/conf.py b/docs/conf.py index 9150181b..7b185bc8 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,9 +1,9 @@ import os import sys -sys.path.insert(0, os.path.abspath("..")) +from model_bakery import __about__ -from model_bakery import __about__ # noqa +sys.path.insert(0, os.path.abspath("..")) project = "Model Bakery" copyright = "2023, Rust Saiargaliev" diff --git a/docs/how_bakery_behaves.md b/docs/how_bakery_behaves.md index fc7fc5aa..3bfbd355 100644 --- a/docs/how_bakery_behaves.md +++ b/docs/how_bakery_behaves.md @@ -37,7 +37,7 @@ Model Bakery should handle fields that: ## Currently supported fields -- `BooleanField`, `NullBooleanField`, `IntegerField`, `BigIntegerField`, `SmallIntegerField`, `PositiveIntegerField`, `PositiveSmallIntegerField`, `FloatField`, `DecimalField` +- `BooleanField`, `IntegerField`, `BigIntegerField`, `SmallIntegerField`, `PositiveIntegerField`, `PositiveBigIntegerField`, `PositiveSmallIntegerField`, `FloatField`, `DecimalField` - `CharField`, `TextField`, `BinaryField`, `SlugField`, `URLField`, `EmailField`, `IPAddressField`, `GenericIPAddressField`, `ContentType` - `ForeignKey`, `OneToOneField`, `ManyToManyField` (even with through model) - `DateField`, `DateTimeField`, `TimeField`, `DurationField` @@ -93,7 +93,7 @@ class CustomBaker(baker.Baker): return [ field for field in super(CustomBaker, self).get_fields() - if not field isinstance CustomField + if not isinstance(field, CustomField) ] # in your settings.py file: @@ -111,7 +111,7 @@ movie = baker.make(Movie, title='Old Boys', _from_manager='availables') # This If you have overwritten the `save` method for a model, you can pass custom parameters to it using Model Bakery. Example: ```python -class ProjectWithCustomSave(models.Model) +class ProjectWithCustomSave(models.Model): # some model fields created_by = models.ForeignKey(settings.AUTH_USER_MODEL) diff --git a/docs/index.md b/docs/index.md index 3312422c..9ef66e90 100644 --- a/docs/index.md +++ b/docs/index.md @@ -8,7 +8,7 @@ Model Bakery is a rename of the legacy [model_mommy\'s project](https://pypi.org # Compatibility -Model Bakery supports Django \>= 3.2. +Model Bakery supports Django \>= 4.2. # Install diff --git a/model_bakery/baker.py b/model_bakery/baker.py index 4388c496..7172ad96 100644 --- a/model_bakery/baker.py +++ b/model_bakery/baker.py @@ -15,7 +15,6 @@ overload, ) -from django import VERSION as DJANGO_VERSION from django.apps import apps from django.conf import settings from django.contrib import contenttypes @@ -78,8 +77,7 @@ def make( _using: str = "", _bulk_create: bool = False, **attrs: Any, -) -> M: - ... +) -> M: ... @overload @@ -94,8 +92,7 @@ def make( _bulk_create: bool = False, _fill_optional: Union[List[str], bool] = False, **attrs: Any, -) -> List[M]: - ... +) -> List[M]: ... def make( @@ -147,8 +144,7 @@ def prepare( _save_related: bool = False, _using: str = "", **attrs, -) -> M: - ... +) -> M: ... @overload @@ -159,8 +155,7 @@ def prepare( _using: str = "", _fill_optional: Union[List[str], bool] = False, **attrs, -) -> List[M]: - ... +) -> List[M]: ... def prepare( @@ -818,12 +813,7 @@ def bulk_create(baker: Baker[M], quantity: int, **kwargs) -> List[M]: else: manager = baker.model._base_manager - existing_entries = list(manager.values_list("pk", flat=True)) created_entries = manager.bulk_create(entries) - # bulk_create in Django < 4.0 does not return ids of created objects. - # drop this after 01 Apr 2024 (Django 3.2 LTS end of life) - if DJANGO_VERSION < (4, 0): - created_entries = manager.exclude(pk__in=existing_entries) # set many-to-many relations from kwargs for entry in created_entries: diff --git a/model_bakery/generators.py b/model_bakery/generators.py index 43ea5238..4e89177e 100644 --- a/model_bakery/generators.py +++ b/model_bakery/generators.py @@ -3,6 +3,8 @@ from django.db.backends.base.operations import BaseDatabaseOperations from django.db.models import ( + AutoField, + BigAutoField, BigIntegerField, BinaryField, BooleanField, @@ -19,11 +21,14 @@ ImageField, IntegerField, IPAddressField, + JSONField, ManyToManyField, OneToOneField, + PositiveBigIntegerField, PositiveIntegerField, PositiveSmallIntegerField, SlugField, + SmallAutoField, SmallIntegerField, TextField, TimeField, @@ -34,40 +39,12 @@ from . import random_gen from .utils import import_from_str -try: - # Proper support starts with Django 3.0 - # (as it uses `django/db/backends/base/operations.py` for matching ranges) - from django.db.models import AutoField, BigAutoField, SmallAutoField -except ImportError: - AutoField = None - BigAutoField = None - SmallAutoField = None - -try: - # added in Django 3.1 - from django.db.models import PositiveBigIntegerField -except ImportError: - PositiveBigIntegerField = None - -try: - # Replaced `django.contrib.postgres.fields.JSONField` in Django 3.1 - from django.db.models import JSONField -except ImportError: - JSONField = None - try: # PostgreSQL-specific field (only available when psycopg is installed) from django.contrib.postgres.fields import ArrayField except ImportError: ArrayField = None -try: - # Deprecated since Django 3.1, removed in Django 4.0 - # PostgreSQL-specific field (only available when psycopg is installed) - from django.contrib.postgres.fields import JSONField as PostgresJSONField -except ImportError: - PostgresJSONField = None - try: # PostgreSQL-specific field (only available when psycopg is installed) from django.contrib.postgres.fields import HStoreField @@ -86,12 +63,6 @@ CIEmailField = None CITextField = None -try: - # Deprecated since Django 3.1, removed in Django 4.0 - from django.db.models import NullBooleanField -except ImportError: - NullBooleanField = None - try: # PostgreSQL-specific fields (only available when psycopg is installed) @@ -124,9 +95,13 @@ def gen_integer(): OneToOneField: random_gen.gen_related, ManyToManyField: random_gen.gen_m2m, BooleanField: random_gen.gen_boolean, + AutoField: _make_integer_gen_by_range(AutoField), + BigAutoField: _make_integer_gen_by_range(BigAutoField), IntegerField: _make_integer_gen_by_range(IntegerField), + SmallAutoField: _make_integer_gen_by_range(SmallAutoField), BigIntegerField: _make_integer_gen_by_range(BigIntegerField), SmallIntegerField: _make_integer_gen_by_range(SmallIntegerField), + PositiveBigIntegerField: _make_integer_gen_by_range(PositiveBigIntegerField), PositiveIntegerField: _make_integer_gen_by_range(PositiveIntegerField), PositiveSmallIntegerField: _make_integer_gen_by_range(PositiveSmallIntegerField), FloatField: random_gen.gen_float, @@ -146,14 +121,11 @@ def gen_integer(): FileField: random_gen.gen_file_field, ImageField: random_gen.gen_image_field, DurationField: random_gen.gen_interval, + JSONField: random_gen.gen_json, } # type: Dict[Type, Callable] if ArrayField: default_mapping[ArrayField] = random_gen.gen_array -if JSONField: - default_mapping[JSONField] = random_gen.gen_json -if PostgresJSONField: - default_mapping[PostgresJSONField] = random_gen.gen_json if HStoreField: default_mapping[HStoreField] = random_gen.gen_hstore if CICharField: @@ -162,16 +134,6 @@ def gen_integer(): default_mapping[CIEmailField] = random_gen.gen_email if CITextField: default_mapping[CITextField] = random_gen.gen_text -if AutoField: - default_mapping[AutoField] = _make_integer_gen_by_range(AutoField) -if BigAutoField: - default_mapping[BigAutoField] = _make_integer_gen_by_range(BigAutoField) -if SmallAutoField: - default_mapping[SmallAutoField] = _make_integer_gen_by_range(SmallAutoField) -if PositiveBigIntegerField: - default_mapping[PositiveBigIntegerField] = _make_integer_gen_by_range( - PositiveBigIntegerField - ) if DecimalRangeField: default_mapping[DecimalRangeField] = random_gen.gen_pg_numbers_range(Decimal) if IntegerRangeField: @@ -182,8 +144,6 @@ def gen_integer(): default_mapping[DateRangeField] = random_gen.gen_date_range if DateTimeRangeField: default_mapping[DateTimeRangeField] = random_gen.gen_datetime_range -if NullBooleanField: - default_mapping[NullBooleanField] = random_gen.gen_boolean # Add GIS fields diff --git a/model_bakery/random_gen.py b/model_bakery/random_gen.py index ce740623..a54516a6 100644 --- a/model_bakery/random_gen.py +++ b/model_bakery/random_gen.py @@ -27,7 +27,7 @@ # Postgres database. MAX_INT = 100000000000 -baker_random = Random() +baker_random = Random() # noqa: S311 def get_content_file(content: bytes, name: str) -> ContentFile: diff --git a/model_bakery/recipe.py b/model_bakery/recipe.py index 13058b7c..b302f96c 100644 --- a/model_bakery/recipe.py +++ b/model_bakery/recipe.py @@ -94,8 +94,7 @@ def make( _bulk_create: bool = False, _save_kwargs: Optional[Dict[str, Any]] = None, **attrs: Any, - ) -> M: - ... + ) -> M: ... @overload def make( @@ -108,8 +107,7 @@ def make( _bulk_create: bool = False, _save_kwargs: Optional[Dict[str, Any]] = None, **attrs: Any, - ) -> List[M]: - ... + ) -> List[M]: ... def make( self, @@ -146,8 +144,7 @@ def prepare( _save_related: bool = False, _using: str = "", **attrs: Any, - ) -> M: - ... + ) -> M: ... @overload def prepare( @@ -156,8 +153,7 @@ def prepare( _save_related: bool = False, _using: str = "", **attrs: Any, - ) -> List[M]: - ... + ) -> List[M]: ... def prepare( self, diff --git a/pyproject.toml b/pyproject.toml index 22dac1fc..359591c3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,7 +22,6 @@ keywords = [ classifiers = [ "Development Status :: 5 - Production/Stable", "Framework :: Django", - "Framework :: Django :: 3.2", "Framework :: Django :: 4.2", "Framework :: Django :: 5.0", "Intended Audience :: Developers", @@ -38,7 +37,7 @@ classifiers = [ "Topic :: Software Development", ] dependencies = [ - "django>=3.2", + "django>=4.2", ] [project.optional-dependencies] diff --git a/tests/generic/models.py b/tests/generic/models.py index 94438be8..6bd6ba59 100755 --- a/tests/generic/models.py +++ b/tests/generic/models.py @@ -87,24 +87,17 @@ class Person(models.Model): occupation = models.CharField(max_length=10, choices=OCCUPATION_CHOICES) uuid = models.UUIDField(primary_key=False) name_hash = models.BinaryField(max_length=16) - days_since_last_login = models.BigIntegerField() + days_since_last_login = models.SmallIntegerField() + days_since_account_creation = models.BigIntegerField() duration_of_sleep = models.DurationField() email = models.EmailField() id_document = models.CharField(unique=True, max_length=10) - - try: - from django.db.models import JSONField - - data = JSONField() - except ImportError: - # Skip JSONField-related fields - pass + data = models.JSONField() try: from django.contrib.postgres.fields import ( ArrayField, HStoreField, - JSONField as PostgresJSONField, ) from django.contrib.postgres.fields.citext import ( CICharField, @@ -123,7 +116,6 @@ class Person(models.Model): if django.VERSION >= (4, 2): long_name = models.CharField() acquaintances = ArrayField(models.IntegerField()) - postgres_data = PostgresJSONField() hstore_data = HStoreField() ci_char = CICharField(max_length=30) ci_email = CIEmailField() @@ -258,6 +250,7 @@ class DummyIntModel(models.Model): class DummyPositiveIntModel(models.Model): positive_small_int_field = models.PositiveSmallIntegerField() positive_int_field = models.PositiveIntegerField() + positive_big_int_field = models.PositiveBigIntegerField() class DummyNumbersModel(models.Model): diff --git a/tests/test_baker.py b/tests/test_baker.py index adc441b0..4f03d411 100644 --- a/tests/test_baker.py +++ b/tests/test_baker.py @@ -3,7 +3,6 @@ from decimal import Decimal from unittest.mock import patch -from django import VERSION as DJANGO_VERSION from django.conf import settings from django.contrib.contenttypes.models import ContentType from django.db.models import Manager @@ -153,7 +152,7 @@ def test_make_should_create_objects_respecting_quantity_parameter(self): assert all(p.name == "George Washington" for p in people) def test_make_quantity_respecting_bulk_create_parameter(self): - query_count = 2 if DJANGO_VERSION >= (4, 0) else 3 + query_count = 1 with self.assertNumQueries(query_count): baker.make(models.Person, _quantity=5, _bulk_create=True) assert models.Person.objects.count() == 5 @@ -365,7 +364,7 @@ def test_create_multiple_one_to_one(self): assert models.Person.objects.all().count() == 5 def test_bulk_create_multiple_one_to_one(self): - query_count = 7 if DJANGO_VERSION >= (4, 0) else 8 + query_count = 6 with self.assertNumQueries(query_count): baker.make(models.LonelyPerson, _quantity=5, _bulk_create=True) @@ -373,7 +372,7 @@ def test_bulk_create_multiple_one_to_one(self): assert models.Person.objects.all().count() == 5 def test_chaining_bulk_create_reduces_query_count(self): - query_count = 5 if DJANGO_VERSION >= (4, 0) else 7 + query_count = 3 with self.assertNumQueries(query_count): baker.make(models.Person, _quantity=5, _bulk_create=True) person_iter = models.Person.objects.all().iterator() @@ -389,7 +388,7 @@ def test_chaining_bulk_create_reduces_query_count(self): assert models.Person.objects.all().count() == 5 def test_bulk_create_multiple_fk(self): - query_count = 7 if DJANGO_VERSION >= (4, 0) else 8 + query_count = 6 with self.assertNumQueries(query_count): baker.make(models.PaymentBill, _quantity=5, _bulk_create=True) @@ -1042,7 +1041,7 @@ def test_annotation_within_manager_get_queryset_are_run_on_make(self): class TestCreateM2MWhenBulkCreate(TestCase): @pytest.mark.django_db def test_create(self): - query_count = 13 if DJANGO_VERSION >= (4, 0) else 14 + query_count = 12 with self.assertNumQueries(query_count): person = baker.make(models.Person) baker.make( diff --git a/tests/test_filling_fields.py b/tests/test_filling_fields.py index 68b5e12f..c52f43b4 100644 --- a/tests/test_filling_fields.py +++ b/tests/test_filling_fields.py @@ -214,6 +214,15 @@ def test_fill_PositiveIntegerField_with_a_random_number(self): assert isinstance(dummy_positive_int_model.positive_int_field, int) assert dummy_positive_int_model.positive_int_field > 0 + def test_fill_PositiveBigIntegerField_with_a_random_number(self): + dummy_positive_int_model = baker.make(models.DummyPositiveIntModel) + positive_big_int_field = models.DummyPositiveIntModel._meta.get_field( + "positive_big_int_field" + ) + assert isinstance(positive_big_int_field, fields.PositiveBigIntegerField) + assert isinstance(dummy_positive_int_model.positive_big_int_field, int) + assert dummy_positive_int_model.positive_big_int_field > 0 + @pytest.mark.django_db class TestFillingOthersNumericFields: @@ -560,9 +569,6 @@ class TestPostgreSQLFieldsFilling: def test_fill_arrayfield_with_empty_array(self, person): assert person.acquaintances == [] - def test_fill_jsonfield_with_empty_dict(self, person): - assert person.postgres_data == {} - def test_fill_hstorefield_with_empty_dict(self, person): assert person.hstore_data == {} diff --git a/tox.ini b/tox.ini index ed28b1af..c77ea10c 100644 --- a/tox.ini +++ b/tox.ini @@ -1,11 +1,8 @@ [tox] env_list = - py38-django{32,42}-{postgresql,sqlite} - py39-django{32,42}-{postgresql,sqlite} - py310-django{32,42,50}-{postgresql,sqlite} - py311-django{32,42,50}-{postgresql,sqlite} - py311-django{42,50}-{postgresql-psycopg3} - py312-django{42,50}-{postgresql-psycopg3} + py{38,39}-django{42}-{postgresql,sqlite} + py{310,311}-django{42,50}-{postgresql,sqlite} + py{311,312}-django{42,50}-{postgresql-psycopg3} [testenv] package = wheel @@ -24,7 +21,6 @@ deps = pillow pytest pytest-django - django32: Django==3.2 django42: Django>=4.2,<5 django50: Django>=5.0,<5.1 postgresql: psycopg2-binary