Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-django-money for 
openSUSE:Factory checked in at 2023-03-14 18:16:24
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-django-money (Old)
 and      /work/SRC/openSUSE:Factory/.python-django-money.new.31432 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-django-money"

Tue Mar 14 18:16:24 2023 rev:7 rq:1071154 version:3.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-django-money/python-django-money.changes  
2022-06-07 11:45:22.391250278 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-django-money.new.31432/python-django-money.changes
       2023-03-14 18:16:29.879684758 +0100
@@ -1,0 +2,29 @@
+Mon Mar 13 13:31:38 UTC 2023 - Matej Cepl <[email protected]>
+
+- Update to 3.0:
+  - Changed
+    - Update py-moneyed to 2.0.
+    - Remove the deprecated Money.decimal_places_display property
+      and argument.
+    - Remove the deprecated CURRENCY_DECIMAL_PLACES_DISPLAY
+      setting.
+    - Null constraint on an implicit CurrencyField is now
+      declared from null=... argument to MoneyField.
+  - Fixed
+    - Improve the internal check for whether a currency is
+      provided
+    - Fix test suite for django main branch
+    - MoneyField raises TypeError when default contains a valid
+      amount but no currence, i.e. Money(123, None).
+    - MoneyField supports default of type bytes
+  - Added
+    - Add support for Django 4.0 and 4.1.
+    - Add support for Python 3.10.
+  - Removed
+    - Drop support for Django 3.1.
+    - Drop support for Python 3.6.
+- Remove upstreamed patches:
+  - merged_pr_657.patch
+  -  pr_638.patch
+
+-------------------------------------------------------------------

Old:
----
  django-money-2.1.1.tar.gz
  merged_pr_657.patch
  pr_638.patch

New:
----
  django-money-3.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-django-money.spec ++++++
--- /var/tmp/diff_new_pack.YE32u8/_old  2023-03-14 18:16:30.451687816 +0100
+++ /var/tmp/diff_new_pack.YE32u8/_new  2023-03-14 18:16:30.455687836 +0100
@@ -1,7 +1,7 @@
 #
 # spec file for package python-django-money
 #
-# Copyright (c) 2022 SUSE LLC
+# Copyright (c) 2023 SUSE LLC
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -16,18 +16,16 @@
 #
 
 
-%{?!python_module:%define python_module() python-%{**} python3-%{**}}
 %define skip_python2 1
+%define skip_python36 1
 Name:           python-django-money
-Version:        2.1.1
+Version:        3.0
 Release:        0
 Summary:        Django support for using money and currency fields
 License:        BSD-3-Clause
 Group:          Development/Languages/Python
 URL:            https://github.com/django-money/django-money
 Source:         
https://github.com/django-money/django-money/archive/%{version}.tar.gz#/django-money-%{version}.tar.gz
-Patch0:         merged_pr_657.patch
-Patch1:         pr_638.patch
 BuildRequires:  %{python_module setuptools}
 BuildRequires:  fdupes
 BuildRequires:  python-rpm-macros
@@ -52,9 +50,9 @@
 Django money and currency fields in models and forms.
 
 %prep
-%setup -q -n django-money-%{version}
-%patch0 -p1
-%patch1 -p1
+%autosetup -p1 -n django-money-%{version}
+
+sed -i -e '/^addopts/d' pytest.ini
 
 %build
 %python_build
@@ -75,7 +73,7 @@
 %files %{python_files}
 %doc README.rst
 %license LICENSE.txt
-%{python_sitelib}/djmoney/
-%{python_sitelib}/django_money*/
+%{python_sitelib}/djmoney
+%{python_sitelib}/django_money-%{version}*-info
 
 %changelog

++++++ django-money-2.1.1.tar.gz -> django-money-3.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-money-2.1.1/.github/workflows/main.yml 
new/django-money-3.0/.github/workflows/main.yml
--- old/django-money-2.1.1/.github/workflows/main.yml   2022-01-02 
18:39:41.000000000 +0100
+++ new/django-money-3.0/.github/workflows/main.yml     2022-06-20 
16:16:22.000000000 +0200
@@ -7,13 +7,13 @@
     name: Generic pre-commit checks
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v3
         with:
           fetch-depth: 1
 
       - uses: actions/setup-python@v2
         with:
-          python-version: 3.7
+          python-version: '3.9'
 
       - run: pip install pre-commit
       - run: pre-commit run --all-files
@@ -22,13 +22,13 @@
     name: docs
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v3
         with:
           fetch-depth: 1
 
       - uses: actions/setup-python@v2
         with:
-          python-version: 3.9
+          python-version: '3.9'
 
       - run: pip install tox
 
@@ -39,40 +39,52 @@
     strategy:
       matrix:
         include:
-          - python: pypy3
+          - python: pypy-3.8
             tox_env: django22-pypy3
-          - python: pypy3
-            tox_env: django31-pypy3
-          - python: 3.6
-            tox_env: django22-py36
-          - python: 3.6
-            tox_env: django31-py36
-          - python: 3.6
-            tox_env: no_rest_framework
-          - python: 3.7
+          - python: pypy-3.8
+            tox_env: django32-pypy3
+          - python: pypy-3.8
+            tox_env: django40-pypy3
+          - python: '3.7'
             tox_env: django22-py37
-          - python: 3.7
-            tox_env: django31-py37
-          - python: 3.8
+          - python: '3.7'
+            tox_env: django32-py37
+          - python: '3.8'
             tox_env: django22-py38
-          - python: 3.8
-            tox_env: django31-py38
-          - python: 3.8
+          - python: '3.8'
             tox_env: django32-py38
-          - python: 3.8
+          - python: '3.8'
+            tox_env: django40-py38
+          - python: '3.8'
+            tox_env: django41-py38
+          - python: '3.8'
             tox_env: django_main-py38
-          - python: 3.9
+          - python: '3.9'
             tox_env: django22-py39
-          - python: 3.9
-            tox_env: django31-py39
-          - python: 3.9
+          - python: '3.9'
             tox_env: django32-py39
-          - python: 3.9
+          - python: '3.9'
+            tox_env: django40-py39
+          - python: '3.9'
+            tox_env: django41-py39
+          - python: '3.9'
             tox_env: django_main-py39
+          - python: '3.10'
+            tox_env: django22-py310
+          - python: '3.10'
+            tox_env: django32-py310
+          - python: '3.10'
+            tox_env: django40-py310
+          - python: '3.10'
+            tox_env: django41-py310
+          - python: '3.10'
+            tox_env: django_main-py310
+          - python: '3.10'
+            tox_env: no_rest_framework
     name: ${{ matrix.tox_env }}
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v3
         with:
           fetch-depth: 1
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-money-2.1.1/.gitignore 
new/django-money-3.0/.gitignore
--- old/django-money-2.1.1/.gitignore   2022-01-02 18:39:41.000000000 +0100
+++ new/django-money-3.0/.gitignore     2022-06-20 16:16:22.000000000 +0200
@@ -29,5 +29,6 @@
 
 # Coverage reports
 .coverage
+.coverage.*
 coverage.xml
 htmlcov
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-money-2.1.1/.pre-commit-config.yaml 
new/django-money-3.0/.pre-commit-config.yaml
--- old/django-money-2.1.1/.pre-commit-config.yaml      2022-01-02 
18:39:41.000000000 +0100
+++ new/django-money-3.0/.pre-commit-config.yaml        2022-06-20 
16:16:22.000000000 +0200
@@ -1,6 +1,8 @@
+default_language_version:
+  python: python3
 repos:
--   repo: git://github.com/pre-commit/pre-commit-hooks
-    rev: v3.4.0
+-   repo: https://github.com/pre-commit/pre-commit-hooks
+    rev: v4.2.0
     hooks:
     -   id: trailing-whitespace
     -   id: check-yaml
@@ -12,7 +14,7 @@
         - --remove
 
 -   repo: https://github.com/asottile/pyupgrade
-    rev: v2.12.0
+    rev: v2.32.0
     hooks:
     -   id: pyupgrade
         args:
@@ -24,12 +26,12 @@
       - id: seed-isort-config
 
 -   repo: https://github.com/pre-commit/mirrors-isort
-    rev: v5.8.0
+    rev: v5.10.1
     hooks:
       -   id: isort
 
 -   repo: https://gitlab.com/pycqa/flake8
-    rev: 3.9.1
+    rev: 3.9.2
     hooks:
     -   id: flake8
         exclude: ^docs
@@ -39,11 +41,11 @@
     hooks:
       - id: rstcheck
         additional_dependencies:
-          - sphinx==3.1.2
+          - sphinx
         args: [--ignore-directives=code]
 
 -   repo: https://github.com/psf/black
-    rev: 20.8b1
+    rev: 22.3.0
     hooks:
       - id: black
         exclude: ^docs
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-money-2.1.1/README.rst 
new/django-money-3.0/README.rst
--- old/django-money-2.1.1/README.rst   2022-01-02 18:39:41.000000000 +0100
+++ new/django-money-3.0/README.rst     2022-06-20 16:16:22.000000000 +0200
@@ -20,8 +20,8 @@
 A little Django app that uses `py-moneyed 
<https://github.com/py-moneyed/py-moneyed>`__ to add support for Money
 fields in your models and forms.
 
-* Django versions supported: 2.2, 3.1, 3.2
-* Python versions supported: 3.6, 3.7, 3.8, 3.9
+* Django versions supported: 2.2, 3.2, 4.0, 4.1
+* Python versions supported: 3.7, 3.8, 3.9, 3.10
 * PyPy versions supported: PyPy3
 
 If you need support for older versions of Django and Python, please refer to 
older releases mentioned in `the release notes 
<https://django-money.readthedocs.io/en/latest/changes.html>`__.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-money-2.1.1/djmoney/__init__.py 
new/django-money-3.0/djmoney/__init__.py
--- old/django-money-2.1.1/djmoney/__init__.py  2022-01-02 18:39:41.000000000 
+0100
+++ new/django-money-3.0/djmoney/__init__.py    2022-06-20 16:16:22.000000000 
+0200
@@ -1,2 +1,10 @@
-__version__ = "2.1.1"
-default_app_config = "djmoney.apps.MoneyConfig"
+__version__ = "3.0.0"
+
+try:
+    import django
+
+    if django.VERSION < (3, 2):
+        default_app_config = "djmoney.apps.MoneyConfig"
+except ModuleNotFoundError:
+    # this part is useful for allow setup.py to be used for version checks
+    pass
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-money-2.1.1/djmoney/models/fields.py 
new/django-money-3.0/djmoney/models/fields.py
--- old/django-money-2.1.1/djmoney/models/fields.py     2022-01-02 
18:39:41.000000000 +0100
+++ new/django-money-3.0/djmoney/models/fields.py       2022-06-20 
16:16:22.000000000 +0200
@@ -91,6 +91,8 @@
         currency = obj.__dict__[self.currency_field_name]
         if amount is None:
             return None
+        elif currency is None:
+            raise TypeError("Currency code can't be None")
         return Money(amount=amount, currency=currency, 
decimal_places=self.field.decimal_places)
 
     def __get__(self, obj, type=None):
@@ -104,7 +106,12 @@
         return data[self.field.name]
 
     def __set__(self, obj, value):  # noqa
-        if value is not None and self.field._currency_field.null and not 
isinstance(value, MONEY_CLASSES + (Decimal,)):
+        if (
+            value is not None
+            and self.field._currency_field.null
+            and not isinstance(value, MONEY_CLASSES)
+            and not obj.__dict__[self.currency_field_name]
+        ):
             # For nullable fields we need either both NULL amount and currency 
or both NOT NULL
             raise ValueError("Missing currency value")
         if isinstance(value, BaseExpression):
@@ -177,7 +184,7 @@
     ):
         nullable = kwargs.get("null", False)
         default = self.setup_default(default, default_currency, nullable)
-        if not default_currency and default is not NOT_PROVIDED:
+        if not default_currency and default not in (None, NOT_PROVIDED):
             default_currency = default.currency
 
         self.currency_max_length = currency_max_length
@@ -191,25 +198,34 @@
         Field.creation_counter += 1
 
     def setup_default(self, default, default_currency, nullable):
-        if isinstance(default, (str, bytes)):
+        if default in (None, NOT_PROVIDED) or isinstance(default, Money):
+            return default
+        elif isinstance(default, (str, bytes)):
             try:
                 # handle scenario where default is formatted like:
                 # 'amount currency-code'
-                amount, currency = default.split(" ")
+                amount, currency = (default.decode() if isinstance(default, 
bytes) else default).split(" ", 1)
             except ValueError:
                 # value error would be risen if the default is
                 # without the currency part, i.e
                 # 'amount'
-                amount = default
+                amount = default.decode() if isinstance(default, bytes) else 
default
                 currency = default_currency
-            default = Money(Decimal(amount), Currency(code=currency))
+
+            amount = Decimal(amount)
         elif isinstance(default, (float, Decimal, int)):
-            default = Money(default, default_currency)
+            amount, currency = default, default_currency
         elif isinstance(default, OldMoney):
-            default = Money(default.amount, default.currency)
-        if default is not None and default is not NOT_PROVIDED and not 
isinstance(default, Money):
-            raise ValueError("default value must be an instance of Money, is: 
%s" % default)
-        return default
+            amount, currency = default.amount, default.currency
+        else:
+            raise ValueError(f"default value must be an instance of Money, is: 
{default}")
+
+        assert currency is not None, (
+            "Default currency can not be `None` when default is of a value 
that doesn't include a currency. Either"
+            " provide a default containing a currency value or configure a 
currency default via setting"
+            " `default_currency=` or `DEFAULT_CURRENCY`(global)"
+        )
+        return Money(amount, currency)
 
     def to_python(self, value):
         if isinstance(value, MONEY_CLASSES):
@@ -256,7 +272,7 @@
             default=self.default_currency,
             editable=False,
             choices=self.currency_choices,
-            null=self.default_currency is None,
+            null=self.null,
         )
         currency_field.creation_counter = self.creation_counter - 1
         currency_field_name = get_currency_field_name(name, self)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-money-2.1.1/djmoney/money.py 
new/django-money-3.0/djmoney/money.py
--- old/django-money-2.1.1/djmoney/money.py     2022-01-02 18:39:41.000000000 
+0100
+++ new/django-money-3.0/djmoney/money.py       2022-06-20 16:16:22.000000000 
+0200
@@ -1,5 +1,4 @@
 import warnings
-from functools import partial
 from types import MappingProxyType
 
 from django.conf import settings
@@ -10,20 +9,13 @@
 from django.utils.safestring import mark_safe
 
 import moneyed.l10n
-import moneyed.localization
 from moneyed import Currency, Money as DefaultMoney
 
-from .settings import DECIMAL_PLACES, DECIMAL_PLACES_DISPLAY, 
IS_DECIMAL_PLACES_DISPLAY_SET, MONEY_FORMAT
+from .settings import DECIMAL_PLACES, MONEY_FORMAT
 
 
 __all__ = ["Money", "Currency"]
 
-_warn_decimal_places_display_deprecated = partial(
-    warnings.warn,
-    "`Money.decimal_places_display` is deprecated and will be removed in 
django-money 3.0.",
-    DeprecationWarning,
-)
-
 
 @deconstructible
 class Money(DefaultMoney):
@@ -33,27 +25,11 @@
 
     use_l10n = None
 
-    def __init__(self, *args, decimal_places_display=None, 
format_options=None, **kwargs):
+    def __init__(self, *args, format_options=None, **kwargs):
         self.decimal_places = kwargs.pop("decimal_places", DECIMAL_PLACES)
-        self._decimal_places_display = decimal_places_display
-        if decimal_places_display is not None:
-            _warn_decimal_places_display_deprecated()
         self.format_options = MappingProxyType(format_options) if 
format_options is not None else None
         super().__init__(*args, **kwargs)
 
-    @property
-    def decimal_places_display(self):
-        _warn_decimal_places_display_deprecated()
-        if self._decimal_places_display is None:
-            return DECIMAL_PLACES_DISPLAY.get(self.currency.code, 
self.decimal_places)
-        return self._decimal_places_display
-
-    @decimal_places_display.setter
-    def decimal_places_display(self, value):
-        """ Set number of digits being displayed - `None` resets to 
`DECIMAL_PLACES_DISPLAY` setting """
-        _warn_decimal_places_display_deprecated()
-        self._decimal_places_display = value
-
     def _copy_attributes(self, source, target):
         """Copy attributes to the new `Money` instance.
 
@@ -65,15 +41,13 @@
 
         When it comes to what number of decimal places to choose, we take the 
maximum number.
         """
-        for attribute_name in ("decimal_places", "_decimal_places_display"):
-            selection = [
-                getattr(candidate, attribute_name, None)
-                for candidate in (self, source)
-                if getattr(candidate, attribute_name, None) is not None
-            ]
-            if selection:
-                value = max(selection)
-                setattr(target, attribute_name, value)
+        selection = [
+            getattr(candidate, "decimal_places", None)
+            for candidate in (self, source)
+            if getattr(candidate, "decimal_places", None) is not None
+        ]
+        if selection:
+            target.decimal_places = max(selection)
 
     def __add__(self, other):
         if isinstance(other, F):
@@ -114,17 +88,16 @@
     @property
     def is_localized(self):
         if self.use_l10n is None:
-            return settings.USE_L10N
+            # This definitely raises a warning in Django 4 - we want to ignore 
RemovedInDjango50Warning
+            # However, we cannot ignore this specific warning class as it 
doesn't exist in older
+            # Django versions
+            with warnings.catch_warnings():
+                warnings.filterwarnings("ignore")
+                setting = getattr(settings, "USE_L10N", True)
+            return setting
         return self.use_l10n
 
     def __str__(self):
-        if self._decimal_places_display is not None or 
IS_DECIMAL_PLACES_DISPLAY_SET:
-            kwargs = {"money": self, "decimal_places": 
self.decimal_places_display}
-            if self.is_localized:
-                locale = get_current_locale(for_babel=False)
-                if locale:
-                    kwargs["locale"] = locale
-            return moneyed.localization.format_money(**kwargs)
         format_options = {
             **MONEY_FORMAT,
             **(self.format_options or {}),
@@ -175,22 +148,12 @@
     __rmul__ = __mul__
 
 
-def get_current_locale(for_babel=True):
-    # get_language can return None starting from Django 1.8
-    language = translation.get_language() or settings.LANGUAGE_CODE
-    locale = translation.to_locale(language)
-
-    if for_babel:
-        return locale
-
-    if locale.upper() in 
moneyed.localization._FORMATTER.formatting_definitions:
-        return locale
-
-    locale = f"{locale}_{locale}".upper()
-    if locale in moneyed.localization._FORMATTER.formatting_definitions:
-        return locale
-
-    return ""
+def get_current_locale():
+    return translation.to_locale(
+        translation.get_language()
+        # get_language can return None starting from Django 1.8
+        or settings.LANGUAGE_CODE
+    )
 
 
 def maybe_convert(value, currency):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-money-2.1.1/djmoney/settings.py 
new/django-money-3.0/djmoney/settings.py
--- old/django-money-2.1.1/djmoney/settings.py  2022-01-02 18:39:41.000000000 
+0100
+++ new/django-money-3.0/djmoney/settings.py    2022-06-20 16:16:22.000000000 
+0200
@@ -1,15 +1,14 @@
 import operator
-import warnings
 from types import MappingProxyType
 
 from django.conf import settings
 
-from moneyed import CURRENCIES, DEFAULT_CURRENCY, DEFAULT_CURRENCY_CODE
+from moneyed import CURRENCIES, Currency
 
 
 # The default currency, you can define this in your project's settings module
 # This has to be a currency object imported from moneyed
-DEFAULT_CURRENCY = getattr(settings, "DEFAULT_CURRENCY", DEFAULT_CURRENCY)
+DEFAULT_CURRENCY: Currency = getattr(settings, "DEFAULT_CURRENCY", None)
 
 
 # The default currency choices, you can define this in your project's
@@ -21,18 +20,10 @@
     if PROJECT_CURRENCIES:
         CURRENCY_CHOICES = [(code, CURRENCIES[code].name) for code in 
PROJECT_CURRENCIES]
     else:
-        CURRENCY_CHOICES = [(c.code, c.name) for i, c in CURRENCIES.items() if 
c.code != DEFAULT_CURRENCY_CODE]
+        CURRENCY_CHOICES = [(c.code, c.name) for i, c in CURRENCIES.items() if 
c != DEFAULT_CURRENCY]
 
 CURRENCY_CHOICES.sort(key=operator.itemgetter(1, 0))
 DECIMAL_PLACES = getattr(settings, "CURRENCY_DECIMAL_PLACES", 2)
-_decimal_display_value = getattr(settings, "CURRENCY_DECIMAL_PLACES_DISPLAY", 
None)
-if _decimal_display_value is not None:
-    warnings.warn(
-        "`CURRENCY_DECIMAL_PLACES_DISPLAY` is deprecated and will be removed 
in django-money 3.0.",
-        DeprecationWarning,
-    )
-DECIMAL_PLACES_DISPLAY = _decimal_display_value or {currency[0]: 
DECIMAL_PLACES for currency in CURRENCY_CHOICES}
-IS_DECIMAL_PLACES_DISPLAY_SET = _decimal_display_value is not None
 
 OPEN_EXCHANGE_RATES_URL = getattr(settings, "OPEN_EXCHANGE_RATES_URL", 
"https://openexchangerates.org/api/latest.json";)
 OPEN_EXCHANGE_RATES_APP_ID = getattr(settings, "OPEN_EXCHANGE_RATES_APP_ID", 
None)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-money-2.1.1/docs/changes.rst 
new/django-money-3.0/docs/changes.rst
--- old/django-money-2.1.1/docs/changes.rst     2022-01-02 18:39:41.000000000 
+0100
+++ new/django-money-3.0/docs/changes.rst       2022-06-20 16:16:22.000000000 
+0200
@@ -1,6 +1,34 @@
 Changelog
 =========
 
+
+`3.0`_ - 2022-06-20
+--------------------
+
+**Changed**
+- Update py-moneyed to 2.0. `#638`_ (`antonagestam`_, `flaeppe`_, `paoloxnet`_)
+- Remove the deprecated ``Money.decimal_places_display`` property and 
argument. `#638`_ (`antonagestam`_, `flaeppe`_, `paoloxnet`_)
+- Remove the deprecated ``CURRENCY_DECIMAL_PLACES_DISPLAY`` setting. `#638`_ 
(`antonagestam`_, `flaeppe`_, `paoloxnet`_)
+- Null constraint on an implicit ``CurrencyField`` is now declared from 
``null=...`` argument to ``MoneyField``. `#638`_ (`antonagestam`_, `flaeppe`_, 
`paoloxnet`_)
+
+**Fixed**
+
+- Improve the internal check for whether a currency is provided `#657`_ 
(`davidszotten`_)
+- Fix test suite for django main branch `#657`_ (`davidszotten`_)
+- ``MoneyField`` raises ``TypeError`` when default contains a valid amount but 
no currence, i.e. ``Money(123, None)``. `#661`_ (`flaeppe`_)
+- ``MoneyField`` supports default of type ``bytes`` `#661`_ (`flaeppe`_)
+
+**Added**
+
+- Add support for Django 4.0 and 4.1.
+- Add support for Python 3.10.
+
+**Removed**
+
+- Drop support for Django 3.1.
+- Drop support for Python 3.6.
+
+
 `2.1.1`_ - 2022-01-02
 ---------------------
 
@@ -709,8 +737,7 @@
 
 - Initial public release
 
-# .. _Unreleased: 
https:///github.com/django-money/django-money/compare/2.1.1...HEAD
-
+.. _3.0: https:///github.com/django-money/django-money/compare/2.1.1...3.0
 .. _2.1.1: https:///github.com/django-money/django-money/compare/2.1...2.1.1
 .. _2.1: https:///github.com/django-money/django-money/compare/2.0.3...2.1
 .. _2.0.3: https://github.com/django-money/django-money/compare/2.0.2...2.0.3
@@ -773,8 +800,11 @@
 .. _0.3: https://github.com/django-money/django-money/compare/0.2...0.3
 .. _0.2: 
https://github.com/django-money/django-money/compare/0.2...a6d90348085332a393abb40b86b5dd9505489b04
 
+.. _#661: https://github.com/django-money/django-money/issues/657
+.. _#657: https://github.com/django-money/django-money/issues/657
 .. _#648: https://github.com/django-money/django-money/issues/648
 .. _#646: https://github.com/django-money/django-money/issues/646
+.. _#638: https://github.com/django-money/django-money/issues/638
 .. _#637: https://github.com/django-money/django-money/issues/637
 .. _#630: https://github.com/django-money/django-money/pull/630
 .. _#629: https://github.com/django-money/django-money/pull/629
@@ -943,6 +973,7 @@
 .. _mstarostik: https://github.com/mstarostik
 .. _niklasb: https://github.com/niklasb
 .. _nerdoc: https://github.com/nerdoc
+.. _paoloxnet: https://github.com/paoloxnet
 .. _pjdelport: https://github.com/pjdelport
 .. _plumdog: https://github.com/plumdog
 .. _rach: https://github.com/rach
@@ -966,3 +997,4 @@
 .. _washeck: https://github.com/washeck
 .. _fara: https://github.com/fara
 .. _wearebasti: https://github.com/wearebasti
+.. _davidszotten: https://github.com/davidszotten
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-money-2.1.1/pytest.ini 
new/django-money-3.0/pytest.ini
--- old/django-money-2.1.1/pytest.ini   2022-01-02 18:39:41.000000000 +0100
+++ new/django-money-3.0/pytest.ini     2022-06-20 16:16:22.000000000 +0200
@@ -2,6 +2,4 @@
 DJANGO_SETTINGS_MODULE=tests.settings
 filterwarnings =
     error::DeprecationWarning
-    ignore:This module and all its contents is deprecated in favour of new 
moneyed.l10n.format_money\.:DeprecationWarning
-    ignore:`Money\.decimal_places_display` is deprecated and will be removed 
in django-money 3\.0\.:DeprecationWarning
-    ignore:`CURRENCY_DECIMAL_PLACES_DISPLAY` is deprecated and will be removed 
in django-money 3\.0\.:DeprecationWarning
+addopts = "--no-cov-on-fail"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-money-2.1.1/setup.py 
new/django-money-3.0/setup.py
--- old/django-money-2.1.1/setup.py     2022-01-02 18:39:41.000000000 +0100
+++ new/django-money-3.0/setup.py       2022-06-20 16:16:22.000000000 +0200
@@ -66,8 +66,8 @@
     maintainer_email="[email protected]",
     license="BSD",
     packages=find_packages(include=["djmoney", "djmoney.*"]),
-    install_requires=["setuptools", "Django>=2.2", "py-moneyed>=1.2,<2.0"],
-    python_requires=">=3.6",
+    install_requires=["setuptools", "Django>=2.2", "py-moneyed>=2.0,<3.0"],
+    python_requires=">=3.7",
     platforms=["Any"],
     keywords=["django", "py-money", "money"],
     classifiers=[
@@ -77,14 +77,15 @@
         "Operating System :: OS Independent",
         "Framework :: Django",
         "Framework :: Django :: 2.2",
-        "Framework :: Django :: 3.1",
         "Framework :: Django :: 3.2",
+        "Framework :: Django :: 4.0",
+        "Framework :: Django :: 4.1",
         "Programming Language :: Python",
         "Programming Language :: Python :: 3",
-        "Programming Language :: Python :: 3.6",
         "Programming Language :: Python :: 3.7",
         "Programming Language :: Python :: 3.8",
         "Programming Language :: Python :: 3.9",
+        "Programming Language :: Python :: 3.10",
         "Programming Language :: Python :: Implementation :: CPython",
         "Programming Language :: Python :: Implementation :: PyPy",
     ],
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-money-2.1.1/tests/conftest.py 
new/django-money-3.0/tests/conftest.py
--- old/django-money-2.1.1/tests/conftest.py    2022-01-02 18:39:41.000000000 
+0100
+++ new/django-money-3.0/tests/conftest.py      2022-06-20 16:16:22.000000000 
+0200
@@ -1,5 +1,3 @@
-from unittest import mock
-
 import pytest
 
 from djmoney.contrib.exchange.models import ExchangeBackend, Rate, 
get_default_backend_name
@@ -31,9 +29,3 @@
 
 
 pytest_plugins = "pytester"
-
-
[email protected]_fixture
-def legacy_formatting():
-    with mock.patch("djmoney.money.IS_DECIMAL_PLACES_DISPLAY_SET", True):
-        yield
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/django-money-2.1.1/tests/contrib/test_django_rest_framework.py 
new/django-money-3.0/tests/contrib/test_django_rest_framework.py
--- old/django-money-2.1.1/tests/contrib/test_django_rest_framework.py  
2022-01-02 18:39:41.000000000 +0100
+++ new/django-money-3.0/tests/contrib/test_django_rest_framework.py    
2022-06-20 16:16:22.000000000 +0200
@@ -64,10 +64,9 @@
             (NullMoneyFieldModel, "field", {"default_currency": "EUR", 
"allow_null": True}, None, None),
             (NullMoneyFieldModel, "field", None, Money(10, "USD"), Money(10, 
"USD")),
             (NullMoneyFieldModel, "field", {"default_currency": "EUR"}, 
Money(10, "USD"), Money(10, "USD")),
-            (NullMoneyFieldModel, "field", {"default_currency": "EUR"}, 10, 
Money(10, "EUR")),
+            (ModelWithVanillaMoneyField, "money", {"default_currency": "EUR"}, 
10, Money(10, "EUR")),
             (ModelWithVanillaMoneyField, "money", None, Money(10, "USD"), 
Money(10, "USD")),
             (ModelWithVanillaMoneyField, "money", {"default_currency": "EUR"}, 
Money(10, "USD"), Money(10, "USD")),
-            (ModelWithVanillaMoneyField, "money", None, 10, Money(10, "XYZ")),
             (ModelWithVanillaMoneyField, "money", {"default_currency": "EUR"}, 
10, Money(10, "EUR")),
         ),
     )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-money-2.1.1/tests/migrations/helpers.py 
new/django-money-3.0/tests/migrations/helpers.py
--- old/django-money-2.1.1/tests/migrations/helpers.py  2022-01-02 
18:39:41.000000000 +0100
+++ new/django-money-3.0/tests/migrations/helpers.py    2022-06-20 
16:16:22.000000000 +0200
@@ -12,7 +12,7 @@
     from django.db.migrations import questioner
 
     # We should answer yes for all migrations questioner questions
-    questioner.input = lambda x: "y"
+    questioner.input = lambda prompt=None: "y"
 
     os.system("find . -name \\*.pyc -delete")
     call_command("makemigrations", "money_app", name=MIGRATION_NAME)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-money-2.1.1/tests/settings.py 
new/django-money-3.0/tests/settings.py
--- old/django-money-2.1.1/tests/settings.py    2022-01-02 18:39:41.000000000 
+0100
+++ new/django-money-3.0/tests/settings.py      2022-06-20 16:16:22.000000000 
+0200
@@ -1,8 +1,8 @@
 import warnings
-from decimal import ROUND_HALF_EVEN
+
+import django
 
 import moneyed
-from moneyed.localization import _FORMATTER, DEFAULT
 
 
 DATABASES = {"default": {"ENGINE": "django.db.backends.sqlite3", "NAME": 
":memory:"}}
@@ -33,22 +33,11 @@
 
 SECRET_KEY = "foobar"
 
-USE_L10N = True
 
+# This now defaults to True and raises RemovedInDjango50Warning
+if django.VERSION < (4, 0):
+    USE_L10N = True
 
-_FORMATTER.add_sign_definition("pl_PL", moneyed.PLN, suffix=" zł")
-_FORMATTER.add_sign_definition(DEFAULT, moneyed.PLN, suffix=" zł")
-_FORMATTER.add_formatting_definition(
-    "pl_PL",
-    group_size=3,
-    group_separator=" ",
-    decimal_point=",",
-    positive_sign="",
-    trailing_positive_sign="",
-    negative_sign="-",
-    trailing_negative_sign="",
-    rounding_method=ROUND_HALF_EVEN,
-)
 
 moneyed.add_currency("USDT", "000", "Tether", None)
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-money-2.1.1/tests/test_admin.py 
new/django-money-3.0/tests/test_admin.py
--- old/django-money-2.1.1/tests/test_admin.py  2022-01-02 18:39:41.000000000 
+0100
+++ new/django-money-3.0/tests/test_admin.py    2022-06-20 16:16:22.000000000 
+0200
@@ -1,3 +1,4 @@
+import django
 import django.contrib.admin.utils as admin_utils
 
 import pytest
@@ -14,33 +15,16 @@
 @pytest.mark.parametrize(
     "value, expected",
     (
-        (Money(10, "RUB"), "10.00 руб."),  # Issue 232
-        (Money(1234), "1,234.00 XYZ"),  # Issue 220
-        (Money(1000, "SAR"), "ر.س1,000.00"),  # Issue 196
-        (Money(1000, "PLN"), "1,000.00 zł"),  # Issue 102
-        (Money("3.33", "EUR"), "3.33 €"),  # Issue 90
-    ),
-)
-def test_display_for_field_with_legacy_formatting(legacy_formatting, settings, 
value, expected):
-    settings.USE_L10N = True
-    # This locale has no definitions in py-moneyed, so it will work for 
localized money representation.
-    settings.LANGUAGE_CODE = "cs"
-    settings.DECIMAL_PLACES_DISPLAY = {}
-    assert admin_utils.display_for_field(value, MONEY_FIELD, "") == expected
-
-
[email protected](
-    "value, expected",
-    (
         (Money(10, "RUB"), "10,00\xa0RUB"),  # Issue 232
-        (Money(1234), "1\xa0234,00\xa0XYZ"),  # Issue 220
         (Money(1000, "SAR"), "1\xa0000,00\xa0SAR"),  # Issue 196
         (Money(1000, "PLN"), "1\xa0000,00\xa0PLN"),  # Issue 102
         (Money("3.33", "EUR"), "3,33\xa0€"),  # Issue 90
     ),
 )
 def test_display_for_field(settings, value, expected):
-    settings.USE_L10N = True
+    # This now defaults to True and raises RemovedInDjango50Warning
+    if django.VERSION < (4, 0):
+        settings.USE_L10N = True
     # This locale has no definitions in py-moneyed, so it will work for 
localized money representation.
     settings.LANGUAGE_CODE = "cs"
     assert admin_utils.display_for_field(value, MONEY_FIELD, "") == expected
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-money-2.1.1/tests/test_form.py 
new/django-money-3.0/tests/test_form.py
--- old/django-money-2.1.1/tests/test_form.py   2022-01-02 18:39:41.000000000 
+0100
+++ new/django-money-3.0/tests/test_form.py     2022-06-20 16:16:22.000000000 
+0200
@@ -190,7 +190,7 @@
 def test_decimal_places_model_form(model_class):
     """Forms should use DECIMAL_PLACES setting value when none specified."""
 
-    expected = str(10 ** -settings.DECIMAL_PLACES)
+    expected = str(10**-settings.DECIMAL_PLACES)
     assert model_class().fields["money"].widget.widgets[0].attrs["step"] == 
expected
 
 
@@ -198,5 +198,5 @@
     """Forms should use decimal_places in field value when specified."""
 
     decimal_places = PreciseModelForm.Meta.model._meta.fields[2].decimal_places
-    expected = str(10 ** -decimal_places)
+    expected = str(10**-decimal_places)
     assert PreciseModelForm().fields["money"].widget.widgets[0].attrs["step"] 
== expected
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-money-2.1.1/tests/test_models.py 
new/django-money-3.0/tests/test_models.py
--- old/django-money-2.1.1/tests/test_models.py 2022-01-02 18:39:41.000000000 
+0100
+++ new/django-money-3.0/tests/test_models.py   2022-06-20 16:16:22.000000000 
+0200
@@ -5,6 +5,7 @@
 """
 import datetime
 from copy import copy
+from decimal import Decimal, InvalidOperation
 
 from django import VERSION
 from django.core.exceptions import ValidationError
@@ -18,6 +19,7 @@
 from djmoney.models.fields import MoneyField
 from djmoney.money import Money
 from moneyed import Money as OldMoney
+from moneyed.classes import CurrencyDoesNotExist
 
 from .testapp.models import (
     AbstractModel,
@@ -40,6 +42,7 @@
     ModelWithDefaultAsStringWithCurrency,
     ModelWithNonMoneyField,
     ModelWithNullableCurrency,
+    ModelWithNullDefaultOnNonNullableField,
     ModelWithSharedCurrency,
     ModelWithTwoMoneyFields,
     ModelWithUniqueIdAndCurrency,
@@ -59,8 +62,8 @@
     @pytest.mark.parametrize(
         "model_class, kwargs, expected",
         (
-            (ModelWithVanillaMoneyField, {"money": Money("100.0")}, 
Money("100.0")),
-            (ModelWithVanillaMoneyField, {"money": OldMoney("100.0")}, 
Money("100.0")),
+            (ModelWithVanillaMoneyField, {"money": Money("100.0", "USD")}, 
Money("100.0", "USD")),
+            (ModelWithVanillaMoneyField, {"money": OldMoney("100.0", "USD")}, 
Money("100.0", "USD")),
             (BaseModel, {}, Money(0, "USD")),
             (BaseModel, {"money": "111.2"}, Money("111.2", "USD")),
             (BaseModel, {"money": Money("123", "PLN")}, Money("123", "PLN")),
@@ -115,7 +118,6 @@
     @pytest.mark.parametrize(
         "model_class, other_value",
         (
-            (ModelWithVanillaMoneyField, Money("100.0")),
             (BaseModel, Money(0, "USD")),
             (ModelWithDefaultAsMoney, Money("0.01", "RUB")),
             (ModelWithDefaultAsFloat, OldMoney("12.05", "PLN")),
@@ -148,27 +150,35 @@
             BaseModel.objects.create(money=value)
 
     @pytest.mark.parametrize("money_class", (Money, OldMoney))
-    @pytest.mark.parametrize("field_name", ("money", "second_money"))
-    def test_save_new_value(self, field_name, money_class):
-        ModelWithVanillaMoneyField.objects.create(**{field_name: 
money_class("100.0")})
+    def test_save_new_value_on_field_without_default(self, money_class):
+        ModelWithVanillaMoneyField.objects.create(money=money_class("100.0", 
"DKK"))
 
         # Try setting the value directly
         retrieved = ModelWithVanillaMoneyField.objects.get()
-        setattr(retrieved, field_name, Money(1, "DKK"))
+        retrieved.money = Money(1, "DKK")
         retrieved.save()
         retrieved = ModelWithVanillaMoneyField.objects.get()
+        assert retrieved.money == Money(1, "DKK")
 
-        assert getattr(retrieved, field_name) == Money(1, "DKK")
+    def test_save_new_value_on_field_with_default(self):
+        ModelWithDefaultAsMoney.objects.create()
+
+        # Try setting the value directly
+        retrieved = ModelWithDefaultAsMoney.objects.get()
+        retrieved.money = Money(1, "DKK")
+        retrieved.save()
+        retrieved = ModelWithDefaultAsMoney.objects.get()
+        assert retrieved.money == Money(1, "DKK")
 
     def test_rounding(self):
-        money = Money("100.0623456781123219")
+        money = Money("100.0623456781123219", "USD")
 
         instance = ModelWithVanillaMoneyField.objects.create(money=money)
         # TODO. Should instance.money be rounded too?
 
         retrieved = ModelWithVanillaMoneyField.objects.get(pk=instance.pk)
 
-        assert retrieved.money == Money("100.06")
+        assert retrieved.money == Money("100.06", "USD")
 
     @pytest.fixture(params=[Money, OldMoney])
     def objects_setup(self, request):
@@ -237,7 +247,7 @@
         assert ModelWithTwoMoneyFields.objects.filter(**kwargs).count() == 
expected
 
     def test_exact_match(self):
-        money = Money("100.0")
+        money = Money("100.0", "USD")
 
         instance = ModelWithVanillaMoneyField.objects.create(money=money)
         retrieved = ModelWithVanillaMoneyField.objects.get(money=money)
@@ -250,9 +260,9 @@
         ModelIssue300.objects.filter(money__created__gt=date)
 
     def test_range_search(self):
-        money = Money("3")
+        money = Money("3", "EUR")
 
-        instance = 
ModelWithVanillaMoneyField.objects.create(money=Money("100.0"))
+        instance = 
ModelWithVanillaMoneyField.objects.create(money=Money("100.0", "EUR"))
         retrieved = ModelWithVanillaMoneyField.objects.get(money__gt=money)
 
         assert instance.pk == retrieved.pk
@@ -301,12 +311,101 @@
         instance = NullMoneyFieldModel.objects.create()
         assert instance.field is None
 
+    def 
test_implicit_currency_field_not_nullable_when_money_field_not_nullable(self):
+        instance = ModelWithVanillaMoneyField(money=10, money_currency=None)
+        with pytest.raises(TypeError, match=r"Currency code can't be None"):
+            instance.save()
+
+    def 
test_raises_type_error_setting_currency_to_none_on_nullable_currency_field_while_having_amount(self):
+        with pytest.raises(ValueError, match=r"Missing currency value"):
+            NullMoneyFieldModel(field=10, field_currency=None)
+
+    def 
test_currency_field_null_switch_not_triggered_from_default_currency(self):
+        # We want a sane default behaviour and simply declaring a 
`MoneyField(...)`
+        # without any default value args should create non nullable amount and 
currency
+        # fields
+        assert ModelWithVanillaMoneyField._meta.get_field("money").null is 
False
+        assert 
ModelWithVanillaMoneyField._meta.get_field("money_currency").null is False
+
+    def test_currency_field_nullable_when_money_field_is_nullable(self):
+        assert NullMoneyFieldModel._meta.get_field("field").null is True
+        assert NullMoneyFieldModel._meta.get_field("field_currency").null is 
True
+
+
[email protected](
+    ("default", "error", "error_match"),
+    [
+        pytest.param("10", AssertionError, r"Default currency can not be 
`None`", id="string without currency"),
+        pytest.param(b"10", AssertionError, r"Default currency can not be 
`None`", id="bytes without currency"),
+        pytest.param(13.37, AssertionError, r"Default currency can not be 
`None`", id="float"),
+        pytest.param(Decimal(10), AssertionError, r"Default currency can not 
be `None`", id="decimal"),
+        pytest.param(10, AssertionError, r"Default currency can not be 
`None`", id="int"),
+        pytest.param("100 ", CurrencyDoesNotExist, r"code  ", id="string with 
trailing space and without currency"),
+        pytest.param(b"100 ", CurrencyDoesNotExist, r"code  ", id="bytes with 
trailing space and without currency"),
+        pytest.param("100 ABC123", CurrencyDoesNotExist, r"code ABC123", 
id="string with unknown currency code"),
+        pytest.param(b"100 ABC123", CurrencyDoesNotExist, r"code ABC123", 
id="bytes with unknown currency code"),
+        # TODO: Better error reporting on > 1 white spaces between amount and 
currency as that could be
+        # quite difficult to spot?
+        pytest.param(
+            "100  SEK", CurrencyDoesNotExist, r"code  SEK", id="string with 
too much value and currency separation"
+        ),
+        pytest.param(
+            b"100  SEK", CurrencyDoesNotExist, r"code  SEK", id="bytes with 
too much value and currency separation"
+        ),
+        pytest.param(
+            "  10 SEK  ",
+            InvalidOperation,
+            r"(ConversionSyntax|Invalid literal)",
+            id="string with leading and trailing spaces",
+        ),
+        pytest.param(
+            b"  10 SEK  ",
+            InvalidOperation,
+            r"(ConversionSyntax|Invalid literal)",
+            id="bytes with leading and trailing spaces",
+        ),
+        pytest.param(
+            "  10 SEK", InvalidOperation, r"(ConversionSyntax|Invalid 
literal)", id="string with leading spaces"
+        ),
+        pytest.param(
+            b"  10 SEK", InvalidOperation, r"(ConversionSyntax|Invalid 
literal)", id="bytes with leading spaces"
+        ),
+        pytest.param("10 SEK  ", CurrencyDoesNotExist, r"code SEK   ", 
id="string with trailing spaces"),
+        pytest.param(b"10 SEK  ", CurrencyDoesNotExist, r"code SEK   ", 
id="bytes with trailing spaces"),
+    ],
+)
+def 
test_errors_instantiating_money_field_with_no_default_currency_and_default_as(default,
 error, error_match):
+    with pytest.raises(error, match=error_match):
+        MoneyField(max_digits=10, decimal_places=2, default=default, 
default_currency=None)
+
+
[email protected](
+    ("default", "default_currency", "expected"),
+    [
+        pytest.param("10 SEK", None, Money(10, "SEK"), id="string with 
currency"),
+        pytest.param(b"10 SEK", None, Money(10, "SEK"), id="bytes with 
currency"),
+        pytest.param("10", "USD", Money(10, "USD"), id="string without 
currency and currency default"),
+        pytest.param(b"10", "USD", Money(10, "USD"), id="bytes without 
currency and currency default"),
+    ],
+)
+def test_can_instantiate_money_field_default_as(default, default_currency, 
expected):
+    field = MoneyField(max_digits=10, decimal_places=2, default=default, 
default_currency=default_currency)
+    assert field.default == expected
+
+
+def 
test_errors_on_default_values_being_none_when_fields_have_not_null_constraint():
+    instance = ModelWithNullDefaultOnNonNullableField()
+    assert instance.money is None
+    assert instance.money_currency is None
+    with pytest.raises(IntegrityError, 
match=r"testapp_modelwithnulldefaultonnonnullablefield.money"):
+        instance.save()
+
 
 class TestGetOrCreate:
     @pytest.mark.parametrize(
         "model, field_name, kwargs, currency",
         (
-            (ModelWithVanillaMoneyField, "money", {"money_currency": "PLN"}, 
"PLN"),
+            (ModelWithDefaultAsInt, "money", {"money_currency": "PLN"}, "PLN"),
             (ModelWithVanillaMoneyField, "money", {"money": Money(0, "EUR")}, 
"EUR"),
             (ModelWithVanillaMoneyField, "money", {"money": OldMoney(0, 
"EUR")}, "EUR"),
             (ModelWithSharedCurrency, "first", {"first": 10, "second": 15, 
"currency": "CZK"}, "CZK"),
@@ -373,10 +472,16 @@
         assert str(exc.value) == "Missing currency value"
         assert not ModelWithNullableCurrency.objects.exists()
 
+    def test_fails_with_null_currency_decimal(self):
+        with pytest.raises(ValueError) as exc:
+            ModelWithNullableCurrency.objects.create(money=Decimal(10))
+        assert str(exc.value) == "Missing currency value"
+        assert not ModelWithNullableCurrency.objects.exists()
+
     def test_fails_with_nullable_but_no_default(self):
-        with pytest.raises(IntegrityError) as exc:
+        match = r"NOT NULL constraint failed: 
testapp_modelwithtwomoneyfields.amount1"
+        with pytest.raises(IntegrityError, match=match):
             ModelWithTwoMoneyFields.objects.create()
-        assert str(exc.value) == "NOT NULL constraint failed: 
testapp_modelwithtwomoneyfields.amount1"
 
     def test_query_not_null(self):
         money = Money(100, "EUR")
@@ -520,9 +625,11 @@
         assert ModelWithVanillaMoneyField.objects.get(integer=1).money == 
Money(0, "USD")
 
     def test_create_func(self):
-        instance = 
ModelWithVanillaMoneyField.objects.create(money=Func(Value(-10), 
function="ABS"))
+        instance = ModelWithVanillaMoneyField.objects.create(
+            money=Func(Value(-10), function="ABS"), money_currency="USD"
+        )
         instance.refresh_from_db()
-        assert instance.money.amount == 10
+        assert instance.money == Money(10, "USD")
 
     @pytest.mark.parametrize(
         "value, expected", ((None, None), (10, Money(10, "USD")), (Money(10, 
"EUR"), Money(10, "EUR")))
@@ -534,7 +641,7 @@
 
     def test_value_create_invalid(self):
         with pytest.raises(ValidationError):
-            ModelWithVanillaMoneyField.objects.create(money=Value("string"))
+            ModelWithVanillaMoneyField.objects.create(money=Value("string"), 
money_currency="DKK")
 
     def test_expressions_for_non_money_fields(self):
         instance = ModelWithVanillaMoneyField.objects.create(money=Money(1, 
"USD"), integer=0)
@@ -553,7 +660,7 @@
 def test_allow_expression_nodes_without_money():
     """Allow querying on expression nodes that are not Money"""
     desc = "hundred"
-    ModelWithNonMoneyField.objects.create(money=Money(100.0), desc=desc)
+    ModelWithNonMoneyField.objects.create(money=Money(100.0, "USD"), desc=desc)
     instance = ModelWithNonMoneyField.objects.filter(desc=F("desc")).get()
     assert instance.desc == desc
 
@@ -712,10 +819,12 @@
 def test_properties_access():
     with pytest.raises(TypeError) as exc:
         ModelWithVanillaMoneyField(money=Money(1, "USD"), bla=1)
-    if VERSION[:2] > (2, 1):
+    if VERSION[:2] > (4, 0):
+        assert str(exc.value) == "ModelWithVanillaMoneyField() got unexpected 
keyword arguments: 'bla'"
+    elif VERSION[:2] > (2, 1):
         assert str(exc.value) == "ModelWithVanillaMoneyField() got an 
unexpected keyword argument 'bla'"
     else:
-        assert str(exc.value) == "'bla' is an invalid keyword argument for 
this function"
+        assert str(exc.value) == "ModelWithVanillaMoneyField() got an 
unexpected keyword argument 'bla'"
 
 
 def parametrize_with_q(**kwargs):
@@ -784,7 +893,7 @@
     except AttributeError:
         pass  # mixer doesn't work with pypy
     else:
-        instance = mixer.blend(ModelWithTwoMoneyFields)
+        instance = mixer.blend(ModelWithTwoMoneyFields, 
amount1_currency="EUR", amount2_currency="USD")
         assert isinstance(instance.amount1, Money)
         assert isinstance(instance.amount2, Money)
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-money-2.1.1/tests/test_money.py 
new/django-money-3.0/tests/test_money.py
--- old/django-money-2.1.1/tests/test_money.py  2022-01-02 18:39:41.000000000 
+0100
+++ new/django-money-3.0/tests/test_money.py    2022-06-20 16:16:22.000000000 
+0200
@@ -10,23 +10,19 @@
 
 
 def test_legacy_repr():
-    assert repr(Money("10.5", "USD", decimal_places_display=2)) == 
"Money('10.5', 'USD')"
+    assert repr(Money("10.5", "USD")) == "Money('10.5', 'USD')"
 
 
 def test_html_safe():
     assert Money("10.5", "EUR").__html__() == "€10.50"
 
 
-def test_legacy_html_safe():
-    assert Money("10.5", "EUR", decimal_places_display=2).__html__() == 
"10.50\xa0€"
-
-
 def test_html_unsafe():
     class UnsafeMoney(Money):
         def __str__(self):
             return "<script>"
 
-    assert UnsafeMoney().__html__() == "&lt;script&gt;"
+    assert UnsafeMoney(100, "USD").__html__() == "&lt;script&gt;"
 
 
 def test_default_mul():
@@ -46,18 +42,6 @@
 @pytest.mark.parametrize(
     "locale, expected",
     (
-        ("pl", "PL_PL"),
-        ("pl_PL", "pl_PL"),
-    ),
-)
-def test_legacy_get_current_locale(locale, expected):
-    with override(locale):
-        assert get_current_locale(for_babel=False) == expected
-
-
[email protected](
-    "locale, expected",
-    (
         ("pl", "pl"),
         ("pl-pl", "pl_PL"),
         ("sv", "sv"),
@@ -82,17 +66,8 @@
     assert mny.decimal_places == 3
 
 
-def test_localize_decimal_places_default():
-    # use default decimal display places from settings
-    assert str(Money("10.543125", "USD")) == "$10.54"
-
-
-def test_localize_decimal_places_overwrite():
-    assert str(Money("10.543125", "USD", decimal_places_display=4)) == 
"$10.5431"
-
-
-def test_localize_decimal_places_both():
-    assert str(Money("10.543125", "USD", decimal_places=5, 
decimal_places_display=1)) == "$10.5"
+def test_localize_defaults_to_currency_decimal_places():
+    assert str(Money("10.543125", "USD", decimal_places=5)) == "$10.54"
 
 
 def test_add_decimal_places():
@@ -117,10 +92,10 @@
         lambda a, d: a * 2,
         lambda a, d: 2 * a,
         lambda a, d: a / 5,
-        lambda a, d: a - Money("2", "USD", decimal_places=d, 
decimal_places_display=d),
-        lambda a, d: Money("2", "USD", decimal_places=d, 
decimal_places_display=d) - a,
-        lambda a, d: a + Money("2", "USD", decimal_places=d, 
decimal_places_display=d),
-        lambda a, d: Money("2", "USD", decimal_places=d, 
decimal_places_display=d) + a,
+        lambda a, d: a - Money("2", "USD", decimal_places=d),
+        lambda a, d: Money("2", "USD", decimal_places=d) - a,
+        lambda a, d: a + Money("2", "USD", decimal_places=d),
+        lambda a, d: Money("2", "USD", decimal_places=d) + a,
         lambda a, d: -a,
         lambda a, d: +a,
         lambda a, d: abs(a),
@@ -131,19 +106,9 @@
 )
 def test_keep_decimal_places(operation, decimal_places):
     # Arithmetic operations should keep the `decimal_places` value
-    amount = Money("1.0000", "USD", decimal_places=decimal_places, 
decimal_places_display=decimal_places)
+    amount = Money("1.0000", "USD", decimal_places=decimal_places)
     new = operation(amount, decimal_places)
     assert new.decimal_places == decimal_places
-    assert new.decimal_places_display == decimal_places
-
-
-def test_decimal_places_display_overwrite():
-    number = Money("1.23456789", "USD")
-    assert str(number) == "$1.23"
-    number.decimal_places_display = 5
-    assert str(number) == "$1.23457"
-    number.decimal_places_display = None
-    assert str(number) == "$1.23"
 
 
 def test_sub_negative():
@@ -155,57 +120,30 @@
     assert total == Money(-33, "EUR")
 
 
[email protected](
-    "decimal_places_display, decimal_places",
-    [
-        [None, None],
-        [0, 0],
-        [1, 0],
-        [4, 0],
-        [0, 1],
-        [1, 1],
-        [4, 1],
-        [0, 4],
-        [1, 4],
-        [4, 4],
-        [None, 4],
-        [None, 1],
-        [None, 0],
-        [4, None],
-        [1, None],
-        [0, None],
-    ],
-)
-def test_proper_copy_of_attributes(decimal_places_display, decimal_places):
-    one = Money(1, "EUR", decimal_places_display=decimal_places_display)
[email protected]("decimal_places", [None, 0, 1, 4])
+def test_proper_copy_of_attributes(decimal_places):
+    one = Money(1, "EUR")
 
-    assert one._decimal_places_display is decimal_places_display
     assert one.decimal_places == 2, "default value"
 
     two = Money(2, "EUR", decimal_places=decimal_places)
 
-    assert two._decimal_places_display is None, "default value"
     assert two.decimal_places == decimal_places
 
     result = Money(3, "EUR")
     one._copy_attributes(two, result)
 
-    assert result._decimal_places_display == decimal_places_display
     assert result.decimal_places == max(2, decimal_places) if decimal_places 
is not None else 2
 
     result = Money(0, "EUR")
-    one._copy_attributes(Money(1, "EUR", decimal_places_display=3), result)
-
-    assert result._decimal_places_display == max(3, decimal_places_display) if 
decimal_places_display is not None else 3
+    one._copy_attributes(Money(1, "EUR"), result)
 
     result = Money(0, "EUR")
     one._copy_attributes(1, result)
 
-    assert result._decimal_places_display == decimal_places_display
     assert result.decimal_places == 2
 
     result = Money(0, "EUR")
     two._copy_attributes(1, result)
 
-    assert result._decimal_places_display is None
     assert result.decimal_places == decimal_places if decimal_places else 2
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-money-2.1.1/tests/test_tags.py 
new/django-money-3.0/tests/test_tags.py
--- old/django-money-2.1.1/tests/test_tags.py   2022-01-02 18:39:41.000000000 
+0100
+++ new/django-money-3.0/tests/test_tags.py     2022-06-20 16:16:22.000000000 
+0200
@@ -1,3 +1,4 @@
+import django
 from django.template import Context, Template, TemplateSyntaxError
 from django.utils.translation import override
 
@@ -56,41 +57,6 @@
 @pytest.mark.parametrize(
     "string, result, context",
     (
-        ('{% load djmoney %}{% money_localize "2.5" "PLN" as NEW_M 
%}{{NEW_M}}', "2,50 zł", {}),
-        ('{% load djmoney %}{% money_localize "2.5" "PLN" %}', "2,50 zł", {}),
-        ("{% load djmoney %}{% money_localize amount currency %}", "2,60 zł", 
{"amount": 2.6, "currency": "PLN"}),
-        ("{% load djmoney %}{% money_localize money as NEW_M %}{{NEW_M}}", 
"2,30 zł", {"money": Money(2.3, "PLN")}),
-        ("{% load djmoney %}{% money_localize money off as NEW_M %}{{NEW_M}}", 
"2.30 zł", {"money": Money(2.3, "PLN")}),
-        ("{% load djmoney %}{% money_localize money off as NEW_M %}{{NEW_M}}", 
"0.00 zł", {"money": Money(0, "PLN")}),
-        (
-            # with a tag template "money_localize"
-            "{% load djmoney %}{% money_localize money %}",
-            "2,30 zł",
-            {"money": Money(2.3, "PLN")},
-        ),
-        (
-            # without a tag template "money_localize"
-            "{{ money }}",
-            "2,30 zł",
-            {"money": Money(2.3, "PLN")},
-        ),
-        ("{% load djmoney %}{% money_localize money off %}", "2.30 zł", 
{"money": Money(2.3, "PLN")}),
-        ("{% load djmoney %}{% money_localize money on %}", "2,30 zł", 
{"money": Money(2.3, "PLN")}),
-        (
-            # in django 2.0 we fail inside the for loop
-            '{% load djmoney %}{% for i in "xxx" %}{% money_localize money %} 
{% endfor %}',
-            "2,30 zł 2,30 zł 2,30 zł ",
-            {"money": Money(2.3, "PLN"), "test": "test"},
-        ),
-    ),
-)
-def test_tag_with_legacy_formatting(legacy_formatting, string, result, 
context):
-    assert_template(string, result, context)
-
-
[email protected](
-    "string, result, context",
-    (
         ('{% load djmoney %}{% money_localize "2.5" "PLN" as NEW_M 
%}{{NEW_M}}', "2,50\xa0zł", {}),
         ('{% load djmoney %}{% money_localize "2.5" "PLN" %}', "2,50\xa0zł", 
{}),
         ("{% load djmoney %}{% money_localize amount currency %}", 
"2,60\xa0zł", {"amount": 2.6, "currency": "PLN"}),
@@ -150,8 +116,12 @@
     ),
 )
 def test_l10n_off(settings, string, result, context):
-    settings.USE_L10N = False
-    assert_template(string, result, context)
+    # This test is only nice to run in older version of Django that do not 
either
+    # complain that the setting is deprecated or as of Django 5.0 entirely 
ignores
+    # this setting
+    if django.VERSION < (4, 0):
+        settings.USE_L10N = False
+        assert_template(string, result, context)
 
 
 def test_forced_l10n():
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-money-2.1.1/tests/testapp/models.py 
new/django-money-3.0/tests/testapp/models.py
--- old/django-money-2.1.1/tests/testapp/models.py      2022-01-02 
18:39:41.000000000 +0100
+++ new/django-money-3.0/tests/testapp/models.py        2022-06-20 
16:16:22.000000000 +0200
@@ -24,7 +24,7 @@
 
 
 class ModelWithVanillaMoneyField(models.Model):
-    money = MoneyField(max_digits=10, decimal_places=2, default=0.0)
+    money = MoneyField(max_digits=10, decimal_places=2)
     second_money = MoneyField(max_digits=10, decimal_places=2, default=0.0, 
default_currency="EUR")
     integer = models.IntegerField(default=0)
 
@@ -215,3 +215,7 @@
 
 class ModelWithDefaultPrecision(models.Model):
     money = MoneyField(max_digits=10)
+
+
+class ModelWithNullDefaultOnNonNullableField(models.Model):
+    money = MoneyField(max_digits=10, decimal_places=2, default=None, 
default_currency=None)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-money-2.1.1/tox.ini 
new/django-money-3.0/tox.ini
--- old/django-money-2.1.1/tox.ini      2022-01-02 18:39:41.000000000 +0100
+++ new/django-money-3.0/tox.ini        2022-06-20 16:16:22.000000000 +0200
@@ -1,9 +1,10 @@
 [tox]
 envlist =
-    django_main-py{39,38,37,36,py3}
-    django32-py{39,38,37,36,py3}
-    django31-py{39,38,37,36,py3}
-    django22-py{39,38,37,36,py3}
+    django_main-py{310,39,38,py3}
+    django41-py{310,39,38,py3}
+    django40-py{310,39,38,py3}
+    django32-py{310,39,38,37,py3}
+    django22-py{310,39,38,37,py3}
     lint
     docs
 skipsdist = true
@@ -16,8 +17,9 @@
 deps =
     .[test,exchange]
     django22: {[django]2.2.x}
-    django31: {[django]3.1.x}
     django32: {[django]3.2.x}
+    django40: {[django]4.0.x}
+    django41: {[django]4.1.x}
     django_main: {[django]main}
 commands = py.test --ds=tests.settings_reversion --cov=./djmoney {posargs}
 usedevelop = false
@@ -36,23 +38,28 @@
        Django>=2.2,<2.3
        django-reversion>=2.0.8
        djangorestframework>=3.7.3
-3.1.x  =
-       Django>=3.1,<3.2
-       django-reversion>=3.0.8
-       djangorestframework>=3.12.0
 3.2.x  =
         Django>=3.2,<3.3
         django-reversion>=3.0.8
         djangorestframework>=3.12.0
+4.0.x  =
+       Django>=4.0,<4.1
+       django-reversion>=4.0.0
+       djangorestframework>=3.13.0
+4.1.x  =
+       Django>=4.1a1,<4.2
+       django-reversion>=4.0.0
+       djangorestframework>=3.13.0
 main =
        https://github.com/django/django/tarball/main
-       djangorestframework>=3.12.0
+       django-reversion>=4.0.0
+       djangorestframework>=3.13.0
 
 [testenv:no_rest_framework]
 deps =
     .[test,exchange]
-    Django>=2.2,<3.2
-    django-reversion>=3.0.8
+    Django>=2.2,<4.2
+    django-reversion>=4.0.0
 
 [testenv:docs]
 allowlist_externals = make

Reply via email to