Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-django-tastypie for 
openSUSE:Factory checked in at 2024-11-22 23:51:33
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-django-tastypie (Old)
 and      /work/SRC/openSUSE:Factory/.python-django-tastypie.new.28523 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-django-tastypie"

Fri Nov 22 23:51:33 2024 rev:26 rq:1225688 version:0.15.0

Changes:
--------
--- 
/work/SRC/openSUSE:Factory/python-django-tastypie/python-django-tastypie.changes
    2024-07-25 15:56:28.440816420 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-django-tastypie.new.28523/python-django-tastypie.changes
 2024-11-22 23:52:09.614986289 +0100
@@ -1,0 +2,10 @@
+Fri Nov 22 00:11:33 UTC 2024 - Steve Kowalik <steven.kowa...@suse.com>
+
+- Update to 0.15.0:
+  * Pin Sphinx to last known working version for now
+  * Fix race condition between POST / PATCH resources by using update_fields
+  * Use non-deprecated assertion methods
+  * Django 5.1 support
+- Drop patch correct-assertion-methods.patch, included upstream.
+
+-------------------------------------------------------------------

Old:
----
  correct-assertion-methods.patch
  v0.14.7.tar.gz

New:
----
  v0.15.0.tar.gz

BETA DEBUG BEGIN:
  Old:  * Django 5.1 support
- Drop patch correct-assertion-methods.patch, included upstream.
BETA DEBUG END:

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

Other differences:
------------------
++++++ python-django-tastypie.spec ++++++
--- /var/tmp/diff_new_pack.8Kb96e/_old  2024-11-22 23:52:11.831078611 +0100
+++ /var/tmp/diff_new_pack.8Kb96e/_new  2024-11-22 23:52:11.847079278 +0100
@@ -18,16 +18,15 @@
 
 %{?sle15_python_module_pythons}
 Name:           python-django-tastypie
-Version:        0.14.7
+Version:        0.15.0
 Release:        0
 Summary:        A webservice API framework layer for Django
 License:        BSD-3-Clause
 URL:            https://github.com/django-tastypie/django-tastypie
 Source:         
https://github.com/django-tastypie/django-tastypie/archive/v%{version}.tar.gz
-# PATCH-FIX-UPSTREAM gh#django-tastypie/django-tastypie#1667
-Patch0:         correct-assertion-methods.patch
-BuildRequires:  %{python_module Django >= 1.11.0}
+BuildRequires:  %{python_module Django >= 4.0}
 BuildRequires:  %{python_module PyYAML}
+BuildRequires:  %{python_module base >= 3.8}
 BuildRequires:  %{python_module biplist}
 BuildRequires:  %{python_module defusedxml}
 BuildRequires:  %{python_module lxml}
@@ -39,7 +38,7 @@
 BuildRequires:  %{python_module wheel}
 BuildRequires:  fdupes
 BuildRequires:  python-rpm-macros
-Requires:       python-Django >= 1.11.0
+Requires:       python-Django >= 4.0
 Requires:       python-python-dateutil >= 2.1
 Requires:       python-python-mimeparse >= 0.1.4
 Recommends:     python-PyYAML

++++++ v0.14.7.tar.gz -> v0.15.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-tastypie-0.14.7/.github/dependabot.yml 
new/django-tastypie-0.15.0/.github/dependabot.yml
--- old/django-tastypie-0.14.7/.github/dependabot.yml   1970-01-01 
01:00:00.000000000 +0100
+++ new/django-tastypie-0.15.0/.github/dependabot.yml   2024-11-20 
22:43:26.000000000 +0100
@@ -0,0 +1,13 @@
+# Keep GitHub Actions up to date with GitHub's Dependabot...
+# 
https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot
+# 
https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#package-ecosystem
+version: 2
+updates:
+  - package-ecosystem: github-actions
+    directory: /
+    groups:
+      github-actions:
+        patterns:
+          - "*"  # Group all Actions updates into a single larger pull request
+    schedule:
+      interval: weekly
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/django-tastypie-0.14.7/.github/workflows/python-package.yml 
new/django-tastypie-0.15.0/.github/workflows/python-package.yml
--- old/django-tastypie-0.14.7/.github/workflows/python-package.yml     
2024-04-23 22:25:21.000000000 +0200
+++ new/django-tastypie-0.15.0/.github/workflows/python-package.yml     
2024-11-20 22:43:26.000000000 +0100
@@ -15,42 +15,30 @@
     strategy:
       fail-fast: false
       matrix:
-        python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11"]
-        django-version: ["3.2", "4.0", "4.1", "4.2", "5.0"]  # Todo: add "dev" 
back
+        python-version: ["3.8", "3.9", "3.10", "3.11"]
+        django-version: ["4.0", "4.1", "4.2", "5.0", "5.1"]  # Todo: add "dev" 
back
         exclude:
-           - python-version: "3.6"
-             django-version: "4.0"
-           - python-version: "3.7"
-             django-version: "4.0"
-           - python-version: "3.6"
-             django-version: "4.1"
-           - python-version: "3.7"
-             django-version: "4.1"
-           - python-version: "3.6"
-             django-version: "4.2"
-           - python-version: "3.7"
-             django-version: "4.2"
-           - python-version: "3.6"
-             django-version: "5.0"
-           - python-version: "3.7"
-             django-version: "5.0"
            - python-version: "3.8"
              django-version: "5.0"
+           - python-version: "3.8"
+             django-version: "5.1"
            - python-version: "3.9"
              django-version: "5.0"
+           - python-version: "3.9"
+             django-version: "5.1"
 #           - python-version: "3.6"
 #             django-version: "dev"
 #           - python-version: "3.7"
 #             django-version: "dev"
 
     steps:
-    - uses: actions/checkout@v2
+    - uses: actions/checkout@v4
     - name: Install OS dependencies
       run: |
         sudo apt install -y binutils libproj-dev gdal-bin 
libsqlite3-mod-spatialite
 
     - name: Set up Python ${{ matrix.python-version }}
-      uses: actions/setup-python@v4
+      uses: actions/setup-python@v5
       with:
         python-version: ${{ matrix.python-version }}
     - name: Install dependencies
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-tastypie-0.14.7/BACKWARDS-INCOMPATIBLE.txt 
new/django-tastypie-0.15.0/BACKWARDS-INCOMPATIBLE.txt
--- old/django-tastypie-0.14.7/BACKWARDS-INCOMPATIBLE.txt       2024-04-23 
22:25:21.000000000 +0200
+++ new/django-tastypie-0.15.0/BACKWARDS-INCOMPATIBLE.txt       2024-11-20 
22:43:26.000000000 +0100
@@ -1,5 +1,11 @@
-Master (v0.9.16)
-================
+v0.15.0
+=======
+
+[2024-10-15] 2444fce - PATCH requests no longer save entire objects, instead 
only updating fields specified in the payload. This is correct behavior and an 
unusual edge case, but has been broken for more than a decade so existing 
workarounds may break as a result.
+
+
+v0.9.16
+======
 
 [2012-12-11] abc0bef - Changed response code of PUT with 
always_return_data=True from 202 to 200
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-tastypie-0.14.7/README.rst 
new/django-tastypie-0.15.0/README.rst
--- old/django-tastypie-0.14.7/README.rst       2024-04-23 22:25:21.000000000 
+0200
+++ new/django-tastypie-0.15.0/README.rst       2024-11-20 22:43:26.000000000 
+0100
@@ -34,8 +34,8 @@
 Core
 ----
 
-* Python 3.6+, preferably 3.8+ (Whatever is supported by your version of 
Django)
-* Django 4.2, 3.2 (LTS releases), or Django 4.0, 4.1, and 5.0 (intermediate 
releases)
+* Python 3.8+ (Whatever is supported by your version of Django)
+* Django 4.2 (LTS releases) or Django 4.0+ (intermediate releases, including 
5.0 and 5.1)
 * dateutil (http://labix.org/python-dateutil) >= 2.1
 
 Format Support
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-tastypie-0.14.7/SECURITY.rst 
new/django-tastypie-0.15.0/SECURITY.rst
--- old/django-tastypie-0.14.7/SECURITY.rst     1970-01-01 01:00:00.000000000 
+0100
+++ new/django-tastypie-0.15.0/SECURITY.rst     2024-11-20 22:43:26.000000000 
+0100
@@ -0,0 +1,18 @@
+===============
+django-tastypie
+===============
+
+Security Policy
+===============
+
+Tastypie is committed to providing a flexible and secure API, and was designed
+with many security features and options in mind. Due to the complex nature of
+APIs and the constant discovery of new attack vectors and vulnerabilities,
+no software is immune to security holes. We rely on our community to report
+and help us investigate security issues.
+
+If you come across a security hole **please do not open a Github issue**.
+Instead, **drop us an email** at ``tastypie-secur...@googlegroups.com``
+
+We'll then work together to investigate and resolve the problem so we can
+announce a solution along with the vulnerability.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-tastypie-0.14.7/docs/conf.py 
new/django-tastypie-0.15.0/docs/conf.py
--- old/django-tastypie-0.14.7/docs/conf.py     2024-04-23 22:25:21.000000000 
+0200
+++ new/django-tastypie-0.15.0/docs/conf.py     2024-11-20 22:43:26.000000000 
+0100
@@ -108,12 +108,11 @@
 html_theme = 'default'
 
 try:
-    import sphinx_rtd_theme
+    import sphinx_rtd_theme  # noqa
 except ImportError:
     pass
 else:
     html_theme = 'sphinx_rtd_theme'
-    html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
 
 # Theme options are theme-specific and customize the look and feel of a theme
 # further.  For a list of options available for each theme, see the
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-tastypie-0.14.7/docs/cookbook.rst 
new/django-tastypie-0.15.0/docs/cookbook.rst
--- old/django-tastypie-0.14.7/docs/cookbook.rst        2024-04-23 
22:25:21.000000000 +0200
+++ new/django-tastypie-0.15.0/docs/cookbook.rst        2024-11-20 
22:43:26.000000000 +0100
@@ -8,7 +8,7 @@
 -----------------------------
 
 It is common to use django to provision OAuth 2.0 tokens for users and then
-have Tasty Pie use these tokens to authenticate users to the API. `Follow this 
tutorial 
<http://ianalexandr.com/blog/building-a-true-oauth-20-api-with-django-and-tasty-pie.html>`_
 and `use this custom authentication class 
<https://github.com/ianalexander/django-oauth2-tastypie>`_ to enable
+have Tasty Pie use these tokens to authenticate users to the API. `Follow this 
tutorial 
<https://web.archive.org/web/20160308015637/http://ianalexandr.com/blog/building-a-true-oauth-20-api-with-django-and-tasty-pie.html>`_
 and `use this custom authentication class 
<https://github.com/ianalexander/django-oauth2-tastypie>`_ to enable
 OAuth 2.0 authentication with Tasty Pie.
 
 .. testsetup::
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-tastypie-0.14.7/docs/index.rst 
new/django-tastypie-0.15.0/docs/index.rst
--- old/django-tastypie-0.14.7/docs/index.rst   2024-04-23 22:25:21.000000000 
+0200
+++ new/django-tastypie-0.15.0/docs/index.rst   2024-11-20 22:43:26.000000000 
+0100
@@ -83,8 +83,8 @@
 Core
 ----
 
-* Python 3.6+, preferably 3.8+ (Whatever is supported by your version of 
Django)
-* Django 2.2, 3.2 (LTS releases) or Django 4.0 (latest release)
+* Python 3.8+ (Whatever is supported by your version of Django)
+* Django 4.2+ (may work with other versions, but the most recent LTS is our 
priority)
 * dateutil (http://labix.org/python-dateutil) >= 2.1
 
 Format Support
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-tastypie-0.14.7/docs/release_notes/index.rst 
new/django-tastypie-0.15.0/docs/release_notes/index.rst
--- old/django-tastypie-0.14.7/docs/release_notes/index.rst     2024-04-23 
22:25:21.000000000 +0200
+++ new/django-tastypie-0.15.0/docs/release_notes/index.rst     2024-11-20 
22:43:26.000000000 +0100
@@ -5,6 +5,7 @@
    :maxdepth: 1
 
    dev
+   v0.15.0
    v0.14.7
    v0.14.6
    v0.14.5
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/django-tastypie-0.14.7/docs/release_notes/v0.15.0.rst 
new/django-tastypie-0.15.0/docs/release_notes/v0.15.0.rst
--- old/django-tastypie-0.14.7/docs/release_notes/v0.15.0.rst   1970-01-01 
01:00:00.000000000 +0100
+++ new/django-tastypie-0.15.0/docs/release_notes/v0.15.0.rst   2024-11-20 
22:43:26.000000000 +0100
@@ -0,0 +1,8 @@
+v0.15.0
+=======
+
+:date: 2024-10-15
+
+- Officially supports Django 5.1 (though no changes were necessary)
+- Dropped official support for Django 3.2.
+- #1617 Fixed a longstanding race condition in PATCH requests that saved 
entire objects rather than only applying changes from the sent payload
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-tastypie-0.14.7/docs/requirements.txt 
new/django-tastypie-0.15.0/docs/requirements.txt
--- old/django-tastypie-0.14.7/docs/requirements.txt    2024-04-23 
22:25:21.000000000 +0200
+++ new/django-tastypie-0.15.0/docs/requirements.txt    2024-11-20 
22:43:26.000000000 +0100
@@ -1,2 +1,3 @@
-sphinx~=7.1.2
-sphinx-rtd-theme==1.3.0rc1
+Sphinx~=7.1.2
+sphinx-rtd-theme~=3.0.1
+mock~=5.1.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-tastypie-0.14.7/tastypie/__init__.py 
new/django-tastypie-0.15.0/tastypie/__init__.py
--- old/django-tastypie-0.14.7/tastypie/__init__.py     2024-04-23 
22:25:21.000000000 +0200
+++ new/django-tastypie-0.15.0/tastypie/__init__.py     2024-11-20 
22:43:26.000000000 +0100
@@ -1,6 +1,6 @@
 __author__ = 'Daniel Lindsley & the Tastypie core team'
 
-VERSION = (0, 14, 7)
+VERSION = (0, 15, 0)
 
 __short_version__ = '.'.join(map(str, VERSION[0:2]))
 __version__ = ''.join(['.'.join(map(str, VERSION[0:3])), ''.join(VERSION[3:])])
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-tastypie-0.14.7/tastypie/resources.py 
new/django-tastypie-0.15.0/tastypie/resources.py
--- old/django-tastypie-0.14.7/tastypie/resources.py    2024-04-23 
22:25:21.000000000 +0200
+++ new/django-tastypie-0.15.0/tastypie/resources.py    2024-11-20 
22:43:26.000000000 +0100
@@ -1690,6 +1690,24 @@
 
         # Now update the bundle in-place.
         deserialized = self.deserialize(request, request.body, 
format=request.META.get('CONTENT_TYPE', 'application/json'))
+        # Create a place to store the names of those fields we want to update
+        bundle.update_fields = []
+        bundle.m2m_update_fields = []
+        # When we get to the obj.save() stage, we need to know which fields 
have changed
+        # Otherwise we can't do a proper update.  Thus,
+        # For every key in deserialized (e.g. the fields submitted in the 
PATCH)
+        for key in deserialized:
+            # If the key is a property of the object, lets add it to the list, 
except:
+            if hasattr(bundle.obj, key):
+                # Can't update_fields an m2m field, so instead add it to 
patch_m2m_fields
+                if getattr(self.fields[key], 'is_m2m', False):
+                    bundle.m2m_update_fields.append(key)
+                    continue
+                # Don't add if it is the id/pk field, can't patch that.
+                if key == 'id' or key == 'pk':
+                    continue
+                # No more checks.  Add it.
+                bundle.update_fields.append(key)
         self.update_in_place(request, bundle, deserialized)
 
         if not self._meta.always_return_data:
@@ -2393,7 +2411,10 @@
         obj_id = self.create_identifier(bundle.obj)
 
         if obj_id not in bundle.objects_saved or bundle.obj._state.adding:
-            bundle.obj.save()
+            if hasattr(bundle, 'update_fields'):
+                bundle.obj.save(update_fields=bundle.update_fields)
+            else:
+                bundle.obj.save()
             obj_id = self.create_identifier(bundle.obj)
             bundle.objects_saved.add(obj_id)
 
@@ -2507,6 +2528,19 @@
             if field_object.readonly:
                 continue
 
+            # If this is a PATCH, make sure that this field name is one of the
+            # patched fields (recorded in the update_fields property of the 
bundle).
+            # Otherwise, we do not want to save / recreate this field.
+            if hasattr(bundle, 'update_fields'):
+                # This bundle is from a PATCH, we should not save an M2M field
+                # unless it was present in the PATCH
+                if field_name not in bundle.m2m_update_fields:
+                    continue  # Skip this field_name
+                else:  # this field name WAS in the patch, lets save it.
+                    pass
+            else:  # Not a PATCH operation, carry on normally.
+                pass
+
             # Get the manager.
             related_mngr = None
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-tastypie-0.14.7/tests/core/models.py 
new/django-tastypie-0.15.0/tests/core/models.py
--- old/django-tastypie-0.14.7/tests/core/models.py     2024-04-23 
22:25:21.000000000 +0200
+++ new/django-tastypie-0.15.0/tests/core/models.py     2024-11-20 
22:43:26.000000000 +0100
@@ -1,3 +1,4 @@
+import time
 from itertools import count
 import uuid
 
@@ -47,6 +48,16 @@
         app_label = 'core'
 
 
+class SlowNote(Note):
+    class Meta:
+        proxy = True
+        app_label = 'core'
+
+    def save(self, *args, **kwargs):
+        time.sleep(1)
+        return super(SlowNote, self).save(*args, **kwargs)
+
+
 class NoteWithEditor(Note):
     editor = models.ForeignKey(AUTH_USER_MODEL, related_name='notes_edited',
                                on_delete=models.CASCADE)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-tastypie-0.14.7/tests/core/tests/__init__.py 
new/django-tastypie-0.15.0/tests/core/tests/__init__.py
--- old/django-tastypie-0.14.7/tests/core/tests/__init__.py     2024-04-23 
22:25:21.000000000 +0200
+++ new/django-tastypie-0.15.0/tests/core/tests/__init__.py     2024-11-20 
22:43:26.000000000 +0100
@@ -15,6 +15,7 @@
 from core.tests.throttle import *  # noqa
 from core.tests.utils import *  # noqa
 from core.tests.validation import *  # noqa
+from core.tests.race_condition import *  # noqa
 
 
 # Explicitly add doctests to suite; Django's test runner stopped
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-tastypie-0.14.7/tests/core/tests/api.py 
new/django-tastypie-0.15.0/tests/core/tests/api.py
--- old/django-tastypie-0.14.7/tests/core/tests/api.py  2024-04-23 
22:25:21.000000000 +0200
+++ new/django-tastypie-0.15.0/tests/core/tests/api.py  2024-11-20 
22:43:26.000000000 +0100
@@ -6,11 +6,12 @@
 from django.test.utils import override_settings
 
 from tastypie.api import Api
+from tastypie.authorization import Authorization
 from tastypie.exceptions import NotRegistered, BadRequest
 from tastypie.resources import ModelResource
 from tastypie.serializers import Serializer
 
-from core.models import Note
+from core.models import Note, SlowNote
 from core.utils import adjust_schema
 User = get_user_model()
 
@@ -19,6 +20,14 @@
     class Meta:
         resource_name = 'notes'
         queryset = Note.objects.filter(is_active=True)
+        authorization = Authorization()
+
+
+class SlowNoteResource(ModelResource):
+    class Meta:
+        resource_name = 'slownotes'
+        queryset = SlowNote.objects.filter(is_active=True)
+        authorization = Authorization()
 
 
 class UserResource(ModelResource):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-tastypie-0.14.7/tests/core/tests/api_urls.py 
new/django-tastypie-0.15.0/tests/core/tests/api_urls.py
--- old/django-tastypie-0.14.7/tests/core/tests/api_urls.py     2024-04-23 
22:25:21.000000000 +0200
+++ new/django-tastypie-0.15.0/tests/core/tests/api_urls.py     2024-11-20 
22:43:26.000000000 +0100
@@ -2,10 +2,12 @@
 
 from core.tests.api import Api, NoteResource, UserResource
 
+from tests.core.tests.api import SlowNoteResource
 
 api = Api()
 api.register(NoteResource())
 api.register(UserResource())
+api.register(SlowNoteResource())
 
 urlpatterns = [
     re_path(r'^api/', include(api.urls)),
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/django-tastypie-0.14.7/tests/core/tests/race_condition.py 
new/django-tastypie-0.15.0/tests/core/tests/race_condition.py
--- old/django-tastypie-0.14.7/tests/core/tests/race_condition.py       
1970-01-01 01:00:00.000000000 +0100
+++ new/django-tastypie-0.15.0/tests/core/tests/race_condition.py       
2024-11-20 22:43:26.000000000 +0100
@@ -0,0 +1,75 @@
+# -*- coding: utf-8 -*-
+import threading
+
+from django.contrib.auth import get_user_model
+
+try:
+    from django.urls import reverse
+except ImportError:
+    from django.core.urlresolvers import reverse
+from django.test import LiveServerTestCase, Client
+
+from core.models import (
+    Note
+)
+
+User = get_user_model()
+
+
+class ApiConcurrentPatchTestCase(LiveServerTestCase):
+
+    fixtures = ['note_testdata.json']
+
+    def setUp(self):
+        super(ApiConcurrentPatchTestCase, self).setUp()
+        self.user = User.objects.get(username='johndoe')
+        self.note = Note.objects.get(pk=1)
+        self.client = Client()
+
+    def test_concurrent_patch_requests(self):
+        mapping_url = f"{self.live_server_url}{reverse('api_dispatch_detail', 
kwargs={'api_name': 'v1', 'resource_name': 'slownotes', 'pk': self.note.pk})}"
+
+        response1 = self.client.patch(
+            mapping_url, data={
+                'title': 'original_title', 'slug': 'original_slug', 'content': 
'original_content'
+            }, content_type='application/json'
+        )
+        print(f"Concurrent PATCH request with HTTP status code: 
{response1.status_code}")
+        self.assertEqual(response1.status_code, 202)  # 202 Accepted
+
+        def patch_request(data):
+            local_client = Client()
+            response = local_client.patch(
+                mapping_url,
+                data=data,
+                content_type='application/json'
+            )
+            print(f"Concurrent PATCH request with data {data} HTTP status 
code: {response.status_code}")
+
+        # Define PATCH requests data
+        data1 = {'title': 'new_title'}
+        data2 = {'slug': 'new_slug'}
+        data3 = {'content': 'new_content'}
+
+        # Create threads to simulate concurrent requests
+        thread1 = threading.Thread(target=patch_request, args=(data1,))
+        thread2 = threading.Thread(target=patch_request, args=(data2,))
+        thread3 = threading.Thread(target=patch_request, args=(data3,))
+
+        # Start threads
+        thread1.start()
+        thread2.start()
+        thread3.start()
+
+        # Wait for threads to finish
+        thread1.join()
+        thread2.join()
+        thread3.join()
+
+        # Refresh the object from the database
+        self.note.refresh_from_db()
+
+        # Check final values (exact value check since race conditions should 
be handled)
+        self.assertEqual(self.note.title, "new_title")
+        self.assertEqual(self.note.slug, "new_slug")
+        self.assertEqual(self.note.content, "new_content")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-tastypie-0.14.7/tests/core/tests/resources.py 
new/django-tastypie-0.15.0/tests/core/tests/resources.py
--- old/django-tastypie-0.14.7/tests/core/tests/resources.py    2024-04-23 
22:25:21.000000000 +0200
+++ new/django-tastypie-0.15.0/tests/core/tests/resources.py    2024-11-20 
22:43:26.000000000 +0100
@@ -302,19 +302,19 @@
     def test_fields(self):
         basic = BasicResource()
         self.assertEqual(len(basic.fields), 4)
-        self.assert_('name' in basic.fields)
+        self.assertIn('name', basic.fields)
         self.assertEqual(isinstance(basic.fields['name'], fields.CharField), 
True)
         self.assertEqual(basic.fields['name']._resource, basic.__class__)
         self.assertEqual(basic.fields['name'].instance_name, 'name')
-        self.assert_('view_count' in basic.fields)
+        self.assertIn('view_count', basic.fields)
         self.assertEqual(isinstance(basic.fields['view_count'], 
fields.IntegerField), True)
         self.assertEqual(basic.fields['view_count']._resource, basic.__class__)
         self.assertEqual(basic.fields['view_count'].instance_name, 
'view_count')
-        self.assert_('date_joined' in basic.fields)
+        self.assertIn('date_joined', basic.fields)
         self.assertEqual(isinstance(basic.fields['date_joined'], 
fields.DateTimeField), True)
         self.assertEqual(basic.fields['date_joined']._resource, 
basic.__class__)
         self.assertEqual(basic.fields['date_joined'].instance_name, 
'date_joined')
-        self.assert_('resource_uri' in basic.fields)
+        self.assertIn('resource_uri', basic.fields)
         self.assertEqual(isinstance(basic.fields['resource_uri'], 
fields.CharField), True)
         self.assertEqual(basic.fields['resource_uri']._resource, 
basic.__class__)
         self.assertEqual(basic.fields['resource_uri'].instance_name, 
'resource_uri')
@@ -322,35 +322,35 @@
 
         another = AnotherBasicResource()
         self.assertEqual(len(another.fields), 8)
-        self.assert_('name' in another.fields)
+        self.assertIn('name', another.fields)
         self.assertEqual(isinstance(another.name, fields.CharField), True)
         self.assertEqual(another.fields['name']._resource, another.__class__)
         self.assertEqual(another.fields['name'].instance_name, 'name')
-        self.assert_('view_count' in another.fields)
+        self.assertIn('view_count', another.fields)
         self.assertEqual(isinstance(another.view_count, fields.IntegerField), 
True)
         self.assertEqual(another.fields['view_count']._resource, 
another.__class__)
         self.assertEqual(another.fields['view_count'].instance_name, 
'view_count')
-        self.assert_('date_joined' in another.fields)
+        self.assertIn('date_joined', another.fields)
         self.assertEqual(isinstance(another.date_joined, fields.DateField), 
True)
         self.assertEqual(another.fields['date_joined']._resource, 
another.__class__)
         self.assertEqual(another.fields['date_joined'].instance_name, 
'date_joined')
-        self.assert_('is_active' in another.fields)
+        self.assertIn('is_active', another.fields)
         self.assertEqual(isinstance(another.is_active, fields.BooleanField), 
True)
         self.assertEqual(another.fields['is_active']._resource, 
another.__class__)
         self.assertEqual(another.fields['is_active'].instance_name, 
'is_active')
-        self.assert_('aliases' in another.fields)
+        self.assertIn('aliases', another.fields)
         self.assertEqual(isinstance(another.aliases, fields.ListField), True)
         self.assertEqual(another.fields['aliases']._resource, 
another.__class__)
         self.assertEqual(another.fields['aliases'].instance_name, 'aliases')
-        self.assert_('meta' in another.fields)
+        self.assertIn('meta', another.fields)
         self.assertEqual(isinstance(another.meta, fields.DictField), True)
         self.assertEqual(another.fields['meta']._resource, another.__class__)
         self.assertEqual(another.fields['meta'].instance_name, 'meta')
-        self.assert_('owed' in another.fields)
+        self.assertIn('owed', another.fields)
         self.assertEqual(isinstance(another.owed, fields.DecimalField), True)
         self.assertEqual(another.fields['owed']._resource, another.__class__)
         self.assertEqual(another.fields['owed'].instance_name, 'owed')
-        self.assert_('resource_uri' in another.fields)
+        self.assertIn('resource_uri', another.fields)
         self.assertEqual(isinstance(another.resource_uri, fields.CharField), 
True)
         self.assertEqual(another.fields['resource_uri']._resource, 
another.__class__)
         self.assertEqual(another.fields['resource_uri'].instance_name, 
'resource_uri')
@@ -358,15 +358,15 @@
 
         nouri = NoUriBasicResource()
         self.assertEqual(len(nouri.fields), 3)
-        self.assert_('name' in nouri.fields)
+        self.assertIn('name', nouri.fields)
         self.assertEqual(isinstance(nouri.name, fields.CharField), True)
         self.assertEqual(nouri.fields['name']._resource, nouri.__class__)
         self.assertEqual(nouri.fields['name'].instance_name, 'name')
-        self.assert_('view_count' in nouri.fields)
+        self.assertIn('view_count', nouri.fields)
         self.assertEqual(isinstance(nouri.view_count, fields.IntegerField), 
True)
         self.assertEqual(nouri.fields['view_count']._resource, nouri.__class__)
         self.assertEqual(nouri.fields['view_count'].instance_name, 
'view_count')
-        self.assert_('date_joined' in nouri.fields)
+        self.assertIn('date_joined', nouri.fields)
         self.assertEqual(isinstance(nouri.date_joined, fields.DateTimeField), 
True)
         self.assertEqual(nouri.fields['date_joined']._resource, 
nouri.__class__)
         self.assertEqual(nouri.fields['date_joined'].instance_name, 
'date_joined')
@@ -553,7 +553,7 @@
         empty_null_bundle = Bundle(obj=obj, data={})
         hydrated = nullable.full_hydrate(empty_null_bundle)
 
-        self.assertEquals(hydrated.obj.name, "Daniel")
+        self.assertEqual(hydrated.obj.name, "Daniel")
 
     def test_full_hydrate__can_put_null_to_clear_related_value(self):
         class RelatedBasicResource(BasicResource):
@@ -890,8 +890,8 @@
         request.method = 'GET'
 
         basic_resource_list = 
json.loads(force_str(basic.get_list(request).content))['objects']
-        self.assertEquals(basic_resource_list[0]['name'], 'Daniel')
-        self.assertEquals(basic_resource_list[0]['date_joined'], 
u'2010-03-30T09:00:00')
+        self.assertEqual(basic_resource_list[0]['name'], 'Daniel')
+        self.assertEqual(basic_resource_list[0]['date_joined'], 
u'2010-03-30T09:00:00')
 
         self.assertNotIn('view_count', basic_resource_list[0])
 
@@ -1622,7 +1622,7 @@
         # some related bits here & self-referential bits later on.
         resource_1 = RelatedNoteResource()
         self.assertEqual(len(resource_1.fields), 8)
-        self.assert_('author' in resource_1.fields)
+        self.assertIn('author', resource_1.fields)
         self.assertTrue(isinstance(resource_1.fields['author'], 
fields.ToOneField))
         self.assertEqual(resource_1.fields['author']._resource, 
resource_1.__class__)
         self.assertEqual(resource_1.fields['author'].instance_name, 'author')
@@ -4522,9 +4522,9 @@
         }, obj=Counter())
         cr.obj_create(counter_bundle)
 
-        self.assertEquals(counter_bundle._create_auth_call_count, 1)
-        self.assertEquals(counter_bundle.obj.name, "About")
-        self.assertEquals(counter_bundle.obj.slug, "about")
+        self.assertEqual(counter_bundle._create_auth_call_count, 1)
+        self.assertEqual(counter_bundle.obj.name, "About")
+        self.assertEqual(counter_bundle.obj.slug, "about")
 
     def test_obj_update(self):
         self.assertEqual(Note.objects.all().count(), 6)
@@ -4688,9 +4688,9 @@
         cr.obj_update(counter_bundle, pk=1)
 
         counter = Counter.objects.get(pk=1)
-        self.assertEquals(counter_bundle._update_auth_call_count, 1)
-        self.assertEquals(counter_bundle.obj.name, "Signups")
-        self.assertEquals(counter_bundle.obj.slug, "signups")
+        self.assertEqual(counter_bundle._update_auth_call_count, 1)
+        self.assertEqual(counter_bundle.obj.name, "Signups")
+        self.assertEqual(counter_bundle.obj.slug, "signups")
 
     def test_lookup_kwargs_with_identifiers__field_without_attr(self):
         """
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-tastypie-0.14.7/tests/namespaced/tests.py 
new/django-tastypie-0.15.0/tests/namespaced/tests.py
--- old/django-tastypie-0.14.7/tests/namespaced/tests.py        2024-04-23 
22:25:21.000000000 +0200
+++ new/django-tastypie-0.15.0/tests/namespaced/tests.py        2024-11-20 
22:43:26.000000000 +0100
@@ -18,5 +18,5 @@
 
         self.assertRaises(NoReverseMatch, reverse, 'api_v1_top_level')
         self.assertRaises(NoReverseMatch, reverse, 'special:api_v1_top_level')
-        self.assertEquals(reverse('special:api_v1_top_level', 
kwargs={'api_name': 'v1'}), '/api/v1/')
-        self.assertEquals(reverse('special:api_dispatch_list', 
kwargs={'api_name': 'v1', 'resource_name': 'notes'}), '/api/v1/notes/')
+        self.assertEqual(reverse('special:api_v1_top_level', 
kwargs={'api_name': 'v1'}), '/api/v1/')
+        self.assertEqual(reverse('special:api_dispatch_list', 
kwargs={'api_name': 'v1', 'resource_name': 'notes'}), '/api/v1/notes/')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-tastypie-0.14.7/tests/settings.py 
new/django-tastypie-0.15.0/tests/settings.py
--- old/django-tastypie-0.14.7/tests/settings.py        2024-04-23 
22:25:21.000000000 +0200
+++ new/django-tastypie-0.15.0/tests/settings.py        2024-11-20 
22:43:26.000000000 +0100
@@ -59,8 +59,8 @@
     'django.contrib.auth.hashers.CryptPasswordHasher',
     'django.contrib.auth.hashers.PBKDF2PasswordHasher',
     'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
+    'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
     'django.contrib.auth.hashers.BCryptPasswordHasher',
-    'django.contrib.auth.hashers.SHA1PasswordHasher',
     'django.contrib.auth.hashers.MD5PasswordHasher',
 ]
 # Django 5.0 removed this hasher
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-tastypie-0.14.7/tox.ini 
new/django-tastypie-0.15.0/tox.ini
--- old/django-tastypie-0.14.7/tox.ini  2024-04-23 22:25:21.000000000 +0200
+++ new/django-tastypie-0.15.0/tox.ini  2024-11-20 22:43:26.000000000 +0100
@@ -1,10 +1,9 @@
 [tox]
 envlist =
-    py{3.6,3.7,3.8,3.9,3.10}-dj{3.2,}
     py{3.8,3.9,3.10,3.11}-dj{4.0,4.1,4.2,dev}
-    py{3.9,3.10,3.11}-dj{5.0}
-    py{3.6,3.7,3.8,3.9,3.10,3.11}-docs,
-    py{3.6,3.7,3.8,3.9,3.10,3.11}-flake8,
+    py{3.8,3.9,3.10,3.11}-docs,
+    py{3.9,3.10,3.11}-dj{5.0,5.1}
+    py{3.8,3.9,3.10,3.11}-flake8,
     py{3.8,3.9,3.10,3.11}-flake8-strict
 
 skipsdist=True
@@ -15,20 +14,20 @@
     PYTHONPATH = {toxinidir}:{toxinidir}/tests
     PYTHONWARNINGS = always
        TESTEXE = {envbindir}/coverage run --append --source=tastypie,tests 
{envbindir}/django-admin.py
-       dj{4.0,4.1,4.2,5.0,dev}: TESTEXE = {envbindir}/coverage run --append 
--source=tastypie,tests {envbindir}/django-admin
+       dj{4.0,4.1,4.2,5.0,5.1,dev}: TESTEXE = {envbindir}/coverage run 
--append --source=tastypie,tests {envbindir}/django-admin
 
 commands =
-    dj{3.2,4.0,4.1,4.2,5.0,dev}: {env:TESTEXE} test -p '*' core.tests 
--settings=settings_core
-    dj{3.2,4.0,4.1,4.2,5.0,dev}: {env:TESTEXE} test basic.tests 
--settings=settings_basic
-    dj{3.2,4.0,4.1,4.2,5.0,dev}: {env:TESTEXE} test related_resource.tests 
--settings=settings_related
-    dj{3.2,4.0,4.1,4.2,5.0,dev}: {env:TESTEXE} test alphanumeric.tests 
--settings=settings_alphanumeric
-    dj{3.2,4.0,4.1,4.2,5.0,dev}: {env:TESTEXE} test authorization.tests 
--settings=settings_authorization
-    dj{3.2,4.0,4.1,4.2,5.0,dev}: {env:TESTEXE} test content_gfk.tests 
--settings=settings_content_gfk
-    dj{3.2,4.0,4.1,4.2,5.0,dev}: {env:TESTEXE} test customuser.tests 
--settings=settings_customuser
-    dj{3.2,4.0,4.1,4.2,5.0,dev}: {env:TESTEXE} test namespaced.tests 
--settings=settings_namespaced
-    dj{3.2,4.0,4.1,4.2,5.0,dev}: {env:TESTEXE} test slashless.tests 
--settings=settings_slashless
-    dj{3.2,4.0,4.1,4.2,5.0,dev}: {env:TESTEXE} test validation.tests 
--settings=settings_validation
-    dj{3.2,4.0,4.1,4.2,5.0,dev}: {env:TESTEXE} test gis.tests 
--settings=settings_gis_spatialite
+    dj{4.0,4.1,4.2,5.0,5.1,dev}: {env:TESTEXE} test -p '*' core.tests 
--settings=settings_core
+    dj{4.0,4.1,4.2,5.0,5.1,dev}: {env:TESTEXE} test basic.tests 
--settings=settings_basic
+    dj{4.0,4.1,4.2,5.0,5.1,dev}: {env:TESTEXE} test related_resource.tests 
--settings=settings_related
+    dj{4.0,4.1,4.2,5.0,5.1,dev}: {env:TESTEXE} test alphanumeric.tests 
--settings=settings_alphanumeric
+    dj{4.0,4.1,4.2,5.0,5.1,dev}: {env:TESTEXE} test authorization.tests 
--settings=settings_authorization
+    dj{4.0,4.1,4.2,5.0,5.1,dev}: {env:TESTEXE} test content_gfk.tests 
--settings=settings_content_gfk
+    dj{4.0,4.1,4.2,5.0,5.1,dev}: {env:TESTEXE} test customuser.tests 
--settings=settings_customuser
+    dj{4.0,4.1,4.2,5.0,5.1,dev}: {env:TESTEXE} test namespaced.tests 
--settings=settings_namespaced
+    dj{4.0,4.1,4.2,5.0,5.1,dev}: {env:TESTEXE} test slashless.tests 
--settings=settings_slashless
+    dj{4.0,4.1,4.2,5.0,5.1,dev}: {env:TESTEXE} test validation.tests 
--settings=settings_validation
+    dj{4.0,4.1,4.2,5.0,5.1,dev}: {env:TESTEXE} test gis.tests 
--settings=settings_gis_spatialite
 
     docs: sphinx-build -W -b html -d {envtmpdir}/doctrees . {envtmpdir}/html
     docs: sphinx-build -W -b doctest -d {envtmpdir}/doctrees . {envtmpdir}/html
@@ -37,31 +36,26 @@
 
     flake8-strict: {envbindir}/flake8 --ignore=E128 --max-complexity 10 .
 basepython =
-    py3.6: python3.6
-    py3.7: python3.7
     py3.8: python3.8
     py3.9: python3.9
     py3.10: python3.10
     py3.11: python3.11
 deps =
-    dj3.2: Django>=3.2,<3.3
     dj4.0: Django>=4.0,<4.1
     dj4.1: Django>=4.1,<4.2
     dj4.2: Django>=4.2,<4.3
     dj5.0: Django>=5.0,<5.1
+    dj5.1: Django>=5.1,<5.2
     djdev: https://github.com/django/django/archive/refs/heads/main.zip
 
-    dj{3.2,4.0,4.1,4.2,dev}: python3-digest>=1.8b4
-    dj{3.2,4.0,4.1,4.2,5.0,dev}: -r{toxinidir}/tests/requirements.txt
-    dj{3.2,4.0,4.1,4.2,5.0,dev}: -r{toxinidir}/requirements.txt
+    dj{4.0,4.1,4.2,dev}: python3-digest>=1.8b4
+    dj{4.0,4.1,4.2,5.0,5.1,dev}: -r{toxinidir}/tests/requirements.txt
+    dj{4.0,4.1,4.2,5.0,5.1,dev}: -r{toxinidir}/requirements.txt
 
-    py{3.6,3.7}-docs: Django~=3.2
     py{3.8,3.9}-docs: Django<4.3
-    py{3.10,3.11}-docs: Django~=5.0
-    docs: Sphinx
-    docs: mock
-    docs: sphinx_rtd_theme
+    py{3.10,3.11}-docs: Django~=5.1
     docs: -r{toxinidir}/requirements.txt
+    docs: -r{toxinidir}/docs/requirements.txt
 
     {flake8,flake8-strict}: flake8
 changedir =

Reply via email to