From a81703084f9391f450308166388764d0ac10bcd7 Mon Sep 17 00:00:00 2001 From: zoedong Date: Mon, 9 Oct 2023 11:21:57 +0800 Subject: [PATCH] Downgrade version to 3.2 to meet openstack-dashboard-23.0.0 Requirement --- 15168.patch | 40 +++++ ...1d0c97832860fbd6597977e2aae17dd7e5b2.patch | 42 +++++ ...071d6ebd18a61c4d7f1b5c9d17106134bd44.patch | 151 ++++++++++++++++++ Add-Python-3.11-support-for-tests.patch | 108 +++++++++++++ Fix-url-validator.patch | 54 +++++++ ...44c339830ea53663415f00cbd17e2fd5aa43.patch | 29 ++++ python-django.spec | 22 ++- sources | 2 +- 8 files changed, 445 insertions(+), 3 deletions(-) create mode 100644 15168.patch create mode 100644 2d2c1d0c97832860fbd6597977e2aae17dd7e5b2.patch create mode 100644 36fa071d6ebd18a61c4d7f1b5c9d17106134bd44.patch create mode 100644 Add-Python-3.11-support-for-tests.patch create mode 100644 Fix-url-validator.patch create mode 100644 b61f44c339830ea53663415f00cbd17e2fd5aa43.patch diff --git a/15168.patch b/15168.patch new file mode 100644 index 0000000..a933c30 --- /dev/null +++ b/15168.patch @@ -0,0 +1,40 @@ +From 402ed030933ffa1af74db50f737872d48f0152bb Mon Sep 17 00:00:00 2001 +From: Mariusz Felisiak +Date: Thu, 9 Dec 2021 13:21:36 +0100 +Subject: [PATCH] Fixed inspectdb.tests.InspectDBTestCase.test_custom_fields() + on SQLite 3.37+. + +Use FlexibleFieldLookupDict which is case-insensitive mapping because +SQLite 3.37+ returns some data type names uppercased e.g. TEXT. +--- + tests/inspectdb/tests.py | 11 +++++------ + 1 file changed, 5 insertions(+), 6 deletions(-) + +diff --git a/tests/inspectdb/tests.py b/tests/inspectdb/tests.py +index 1deffff14047e..20da7ade4d512 100644 +--- a/tests/inspectdb/tests.py ++++ b/tests/inspectdb/tests.py +@@ -312,18 +312,17 @@ def test_custom_fields(self): + Introspection of columns with a custom field (#21090) + """ + out = StringIO() +- orig_data_types_reverse = connection.introspection.data_types_reverse +- try: +- connection.introspection.data_types_reverse = { ++ with mock.patch( ++ 'django.db.connection.introspection.data_types_reverse.base_data_types_reverse', ++ { + 'text': 'myfields.TextField', + 'bigint': 'BigIntegerField', +- } ++ }, ++ ): + call_command('inspectdb', 'inspectdb_columntypes', stdout=out) + output = out.getvalue() + self.assertIn("text_field = myfields.TextField()", output) + self.assertIn("big_int_field = models.BigIntegerField()", output) +- finally: +- connection.introspection.data_types_reverse = orig_data_types_reverse + + def test_introspection_errors(self): + """ diff --git a/2d2c1d0c97832860fbd6597977e2aae17dd7e5b2.patch b/2d2c1d0c97832860fbd6597977e2aae17dd7e5b2.patch new file mode 100644 index 0000000..a417e1c --- /dev/null +++ b/2d2c1d0c97832860fbd6597977e2aae17dd7e5b2.patch @@ -0,0 +1,42 @@ +diff --git a/django/core/validators.py b/django/core/validators.py +index bd2122f..832697c 100644 +--- a/django/core/validators.py ++++ b/django/core/validators.py +@@ -92,6 +92,7 @@ class URLValidator(RegexValidator): + r'\Z', re.IGNORECASE) + message = _('Enter a valid URL.') + schemes = ['http', 'https', 'ftp', 'ftps'] ++ unsafe_chars = frozenset('\t\r\n') + + def __init__(self, schemes=None, **kwargs): + super().__init__(**kwargs) +@@ -101,6 +102,8 @@ class URLValidator(RegexValidator): + def __call__(self, value): + if not isinstance(value, str): + raise ValidationError(self.message, code=self.code, params={'value': value}) ++ if self.unsafe_chars.intersection(value): ++ raise ValidationError(self.message, code=self.code, params={'value': value}) + # Check if the scheme is valid. + scheme = value.split('://')[0].lower() + if scheme not in self.schemes: +diff --git a/tests/validators/tests.py b/tests/validators/tests.py +index d6d013c..09d5c40 100644 +--- a/tests/validators/tests.py ++++ b/tests/validators/tests.py +@@ -226,9 +226,15 @@ TEST_DATA = [ + (URLValidator(), None, ValidationError), + (URLValidator(), 56, ValidationError), + (URLValidator(), 'no_scheme', ValidationError), +- # Trailing newlines not accepted ++ # Newlines and tabs are not accepted. + (URLValidator(), 'http://www.djangoproject.com/\n', ValidationError), + (URLValidator(), 'http://[::ffff:192.9.5.5]\n', ValidationError), ++ (URLValidator(), 'http://www.djangoproject.com/\r', ValidationError), ++ (URLValidator(), 'http://[::ffff:192.9.5.5]\r', ValidationError), ++ (URLValidator(), 'http://www.django\rproject.com/', ValidationError), ++ (URLValidator(), 'http://[::\rffff:192.9.5.5]', ValidationError), ++ (URLValidator(), 'http://\twww.djangoproject.com/', ValidationError), ++ (URLValidator(), 'http://\t[::ffff:192.9.5.5]', ValidationError), + # Trailing junk does not take forever to reject + (URLValidator(), 'http://www.asdasdasdasdsadfm.com.br ', ValidationError), + (URLValidator(), 'http://www.asdasdasdasdsadfm.com.br z', ValidationError), diff --git a/36fa071d6ebd18a61c4d7f1b5c9d17106134bd44.patch b/36fa071d6ebd18a61c4d7f1b5c9d17106134bd44.patch new file mode 100644 index 0000000..cc7623c --- /dev/null +++ b/36fa071d6ebd18a61c4d7f1b5c9d17106134bd44.patch @@ -0,0 +1,151 @@ +From 36fa071d6ebd18a61c4d7f1b5c9d17106134bd44 Mon Sep 17 00:00:00 2001 +From: Allan Feldman +Date: Wed, 30 Jun 2021 17:37:10 +0200 +Subject: [PATCH] Fixed #32889 -- Allowed per-request sync_to_async context in + ASGIHandler . + +By using a asgiref's ThreadSensitiveContext context manager, requests +will be able to execute independently of other requests when sync work +is involved. + +Prior to this commit, a single global thread was used to execute any +sync work independent of the request from which that work was scheduled. +This could result in contention for the global sync thread in the case +of a slow sync function. + +Requests are now isolated to their own sync thread. +--- + django/core/handlers/asgi.py | 10 ++++++++- + tests/asgi/tests.py | 41 ++++++++++++++++++++++++++++++------ + tests/asgi/urls.py | 15 +++++++++++++ + 3 files changed, 58 insertions(+), 8 deletions(-) + +diff --git a/django/core/handlers/asgi.py b/django/core/handlers/asgi.py +index 7fbabe45104d2..2b8cc8b76e9a7 100644 +--- a/django/core/handlers/asgi.py ++++ b/django/core/handlers/asgi.py +@@ -3,7 +3,7 @@ + import tempfile + import traceback + +-from asgiref.sync import sync_to_async ++from asgiref.sync import ThreadSensitiveContext, sync_to_async + + from django.conf import settings + from django.core import signals +@@ -144,6 +144,14 @@ async def __call__(self, scope, receive, send): + 'Django can only handle ASGI/HTTP connections, not %s.' + % scope['type'] + ) ++ ++ async with ThreadSensitiveContext(): ++ await self.handle(scope, receive, send) ++ ++ async def handle(self, scope, receive, send): ++ """ ++ Handles the ASGI request. Called via the __call__ method. ++ """ + # Receive the HTTP request body as a stream object. + try: + body_file = await self.read_body(receive) +diff --git a/tests/asgi/tests.py b/tests/asgi/tests.py +index 3509bb0aa7e88..7eb35724dfc5d 100644 +--- a/tests/asgi/tests.py ++++ b/tests/asgi/tests.py +@@ -4,7 +4,6 @@ + from pathlib import Path + from unittest import skipIf + +-from asgiref.sync import SyncToAsync + from asgiref.testing import ApplicationCommunicator + + from django.contrib.staticfiles.handlers import ASGIStaticFilesHandler +@@ -16,7 +15,7 @@ + ) + from django.utils.http import http_date + +-from .urls import test_filename ++from .urls import sync_waiter, test_filename + + TEST_STATIC_ROOT = Path(__file__).parent / 'project' / 'static' + +@@ -235,11 +234,39 @@ def __call__(self, **kwargs): + # Give response.close() time to finish. + await communicator.wait() + +- # At this point, AsyncToSync does not have a current executor. Thus +- # SyncToAsync falls-back to .single_thread_executor. +- target_thread = next(iter(SyncToAsync.single_thread_executor._threads)) ++ # AsyncToSync should have executed the signals in the same thread. + request_started_thread, request_finished_thread = signal_handler.threads +- self.assertEqual(request_started_thread, target_thread) +- self.assertEqual(request_finished_thread, target_thread) ++ self.assertEqual(request_started_thread, request_finished_thread) + request_started.disconnect(signal_handler) + request_finished.disconnect(signal_handler) ++ ++ async def test_concurrent_async_uses_multiple_thread_pools(self): ++ sync_waiter.active_threads.clear() ++ ++ # Send 2 requests concurrently ++ application = get_asgi_application() ++ scope = self.async_request_factory._base_scope(path='/wait/') ++ communicators = [] ++ for _ in range(2): ++ communicators.append(ApplicationCommunicator(application, scope)) ++ await communicators[-1].send_input({'type': 'http.request'}) ++ ++ # Each request must complete with a status code of 200 ++ # If requests aren't scheduled concurrently, the barrier in the ++ # sync_wait view will time out, resulting in a 500 status code. ++ for communicator in communicators: ++ response_start = await communicator.receive_output() ++ self.assertEqual(response_start['type'], 'http.response.start') ++ self.assertEqual(response_start['status'], 200) ++ response_body = await communicator.receive_output() ++ self.assertEqual(response_body['type'], 'http.response.body') ++ self.assertEqual(response_body['body'], b'Hello World!') ++ # Give response.close() time to finish. ++ await communicator.wait() ++ ++ # The requests should have scheduled on different threads. Note ++ # active_threads is a set (a thread can only appear once), therefore ++ # length is a sufficient check. ++ self.assertEqual(len(sync_waiter.active_threads), 2) ++ ++ sync_waiter.active_threads.clear() +diff --git a/tests/asgi/urls.py b/tests/asgi/urls.py +index ff8d21ea7cd0b..22d85604d14ca 100644 +--- a/tests/asgi/urls.py ++++ b/tests/asgi/urls.py +@@ -1,3 +1,5 @@ ++import threading ++ + from django.http import FileResponse, HttpResponse + from django.urls import path + +@@ -14,6 +16,18 @@ def hello_meta(request): + ) + + ++def sync_waiter(request): ++ with sync_waiter.lock: ++ sync_waiter.active_threads.add(threading.current_thread()) ++ sync_waiter.barrier.wait(timeout=0.5) ++ return hello(request) ++ ++ ++sync_waiter.active_threads = set() ++sync_waiter.lock = threading.Lock() ++sync_waiter.barrier = threading.Barrier(2) ++ ++ + test_filename = __file__ + + +@@ -21,4 +35,5 @@ def hello_meta(request): + path('', hello), + path('file/', lambda x: FileResponse(open(test_filename, 'rb'))), + path('meta/', hello_meta), ++ path('wait/', sync_waiter), + ] diff --git a/Add-Python-3.11-support-for-tests.patch b/Add-Python-3.11-support-for-tests.patch new file mode 100644 index 0000000..abdf698 --- /dev/null +++ b/Add-Python-3.11-support-for-tests.patch @@ -0,0 +1,108 @@ +diff -Naur django-3.2.orig/django/utils/version.py django-3.2/django/utils/version.py +--- django-3.2.orig/django/utils/version.py 2021-04-06 17:27:22.000000000 +0800 ++++ django-3.2/django/utils/version.py 2023-10-08 15:52:24.619595829 +0800 +@@ -13,6 +13,8 @@ + PY37 = sys.version_info >= (3, 7) + PY38 = sys.version_info >= (3, 8) + PY39 = sys.version_info >= (3, 9) ++PY310 = sys.version_info >= (3, 10) ++PY311 = sys.version_info >= (3, 11) + + + def get_version(version=None): +diff -Naur django-3.2.orig/tests/test_runner/test_debug_sql.py django-3.2/tests/test_runner/test_debug_sql.py +--- django-3.2.orig/tests/test_runner/test_debug_sql.py 2021-04-06 17:27:22.000000000 +0800 ++++ django-3.2/tests/test_runner/test_debug_sql.py 2023-10-08 15:52:24.718595822 +0800 +@@ -4,6 +4,7 @@ + from django.db import connection + from django.test import TestCase + from django.test.runner import DiscoverRunner ++from django.utils.version import PY311 + + from .models import Person + +@@ -100,14 +101,17 @@ + '''"test_runner_person"."first_name" = 'subtest-fail';'''), + ] + ++ # Python 3.11 uses fully qualified test name in the output. ++ method_name = ".runTest" if PY311 else "" ++ test_class_path = "test_runner.test_debug_sql.TestDebugSQL" + verbose_expected_outputs = [ +- 'runTest (test_runner.test_debug_sql.TestDebugSQL.FailingTest) ... FAIL', +- 'runTest (test_runner.test_debug_sql.TestDebugSQL.ErrorTest) ... ERROR', +- 'runTest (test_runner.test_debug_sql.TestDebugSQL.PassingTest) ... ok', ++ f"runTest ({test_class_path}.FailingTest{method_name}) ... FAIL", ++ f"runTest ({test_class_path}.ErrorTest{method_name}) ... ERROR", ++ f"runTest ({test_class_path}.PassingTest{method_name}) ... ok", + # If there are errors/failures in subtests but not in test itself, + # the status is not written. That behavior comes from Python. +- 'runTest (test_runner.test_debug_sql.TestDebugSQL.FailingSubTest) ...', +- 'runTest (test_runner.test_debug_sql.TestDebugSQL.ErrorSubTest) ...', ++ f"runTest ({test_class_path}.FailingSubTest{method_name}) ...", ++ f"runTest ({test_class_path}.ErrorSubTest{method_name}) ...", + ('''SELECT COUNT(*) AS "__count" ''' + '''FROM "test_runner_person" WHERE ''' + '''"test_runner_person"."first_name" = 'pass';'''), +diff -Naur django-3.2.orig/tests/test_runner/test_parallel.py django-3.2/tests/test_runner/test_parallel.py +--- django-3.2.orig/tests/test_runner/test_parallel.py 2021-04-06 17:27:22.000000000 +0800 ++++ django-3.2/tests/test_runner/test_parallel.py 2023-10-08 15:52:24.718595822 +0800 +@@ -2,7 +2,7 @@ + + from django.test import SimpleTestCase + from django.test.runner import RemoteTestResult +-from django.utils.version import PY37 ++from django.utils.version import PY37, PY311 + + try: + import tblib +@@ -79,7 +79,11 @@ + + event = events[1] + self.assertEqual(event[0], 'addSubTest') +- self.assertEqual(str(event[2]), 'dummy_test (test_runner.test_parallel.SampleFailingSubtest) (index=0)') ++ self.assertEqual(str(event[2]), ++ "dummy_test (test_runner.test_parallel.SampleFailingSubtest%s) (index=0)" ++ # Python 3.11 uses fully qualified test name in the output. ++ % (".dummy_test" if PY311 else ""), ++ ) + trailing_comma = '' if PY37 else ',' + self.assertEqual(repr(event[3][1]), "AssertionError('0 != 1'%s)" % trailing_comma) + +diff -Naur django-3.2.orig/tests/test_utils/tests.py django-3.2/tests/test_utils/tests.py +--- django-3.2.orig/tests/test_utils/tests.py 2021-04-06 17:27:22.000000000 +0800 ++++ django-3.2/tests/test_utils/tests.py 2023-10-08 15:52:24.719595822 +0800 +@@ -26,6 +26,7 @@ + ) + from django.urls import NoReverseMatch, path, reverse, reverse_lazy + from django.utils.deprecation import RemovedInDjango41Warning ++from django.utils.version import PY311 + + from .models import Car, Person, PossessedCar + from .views import empty_response +@@ -78,9 +79,11 @@ + SkipTestCase('test_foo').test_foo, + ValueError, + "skipUnlessDBFeature cannot be used on test_foo (test_utils.tests." +- "SkippingTestCase.test_skip_unless_db_feature..SkipTestCase) " ++ "SkippingTestCase.test_skip_unless_db_feature..SkipTestCase%s) " + "as SkippingTestCase.test_skip_unless_db_feature..SkipTestCase " + "doesn't allow queries against the 'default' database." ++ # Python 3.11 uses fully qualified test name in the output. ++ % (".test_foo" if PY311 else ""), + ) + + def test_skip_if_db_feature(self): +@@ -122,9 +125,11 @@ + SkipTestCase('test_foo').test_foo, + ValueError, + "skipIfDBFeature cannot be used on test_foo (test_utils.tests." +- "SkippingTestCase.test_skip_if_db_feature..SkipTestCase) " ++ "SkippingTestCase.test_skip_if_db_feature..SkipTestCase%s) " + "as SkippingTestCase.test_skip_if_db_feature..SkipTestCase " + "doesn't allow queries against the 'default' database." ++ # Python 3.11 uses fully qualified test name in the output. ++ % (".test_foo" if PY311 else ""), + ) + + diff --git a/Fix-url-validator.patch b/Fix-url-validator.patch new file mode 100644 index 0000000..d4b8892 --- /dev/null +++ b/Fix-url-validator.patch @@ -0,0 +1,54 @@ +From: Pedro Schlickmann Mendes +Date: Thu, 20 Jul 2023 12:59:59 +0100 +Subject: Fixed URLValidator crash in some edge cases + +Origin: upstream, https://github.com/django/django/commit/e8b4feddc34ffe5759ec21da8fa027e86e653f1c +Bug: https://code.djangoproject.com/ticket/33367 +Last-Update: 2021-12-15 +--- + django/core/validators.py | 13 +++++++------ + 1 file changed, 7 insertions(+), 6 deletions(-) + +diff --git a/django/core/validators.py b/django/core/validators.py +index 731ccf2d4690..6b28eef08dd2 100644 +--- a/django/core/validators.py ++++ b/django/core/validators.py +@@ -110,15 +110,16 @@ class URLValidator(RegexValidator): + raise ValidationError(self.message, code=self.code, params={'value': value}) + + # Then check full URL ++ try: ++ splitted_url = urlsplit(value) ++ except ValueError: ++ raise ValidationError(self.message, code=self.code, params={'value': value}) + try: + super().__call__(value) + except ValidationError as e: + # Trivial case failed. Try for possible IDN domain + if value: +- try: +- scheme, netloc, path, query, fragment = urlsplit(value) +- except ValueError: # for example, "Invalid IPv6 URL" +- raise ValidationError(self.message, code=self.code, params={'value': value}) ++ scheme, netloc, path, query, fragment = splitted_url + try: + netloc = punycode(netloc) # IDN -> ACE + except UnicodeError: # invalid domain part +@@ -129,7 +130,7 @@ class URLValidator(RegexValidator): + raise + else: + # Now verify IPv6 in the netloc part +- host_match = re.search(r'^\[(.+)\](?::\d{2,5})?$', urlsplit(value).netloc) ++ host_match = re.search(r'^\[(.+)\](?::\d{2,5})?$', splitted_url.netloc) + if host_match: + potential_ip = host_match[1] + try: +@@ -141,7 +142,7 @@ class URLValidator(RegexValidator): + # section 3.1. It's defined to be 255 bytes or less, but this includes + # one byte for the length of the name and one byte for the trailing dot + # that's used to indicate absolute names in DNS. +- if len(urlsplit(value).hostname) > 253: ++ if splitted_url.hostname is None or len(splitted_url.hostname) > 253: + raise ValidationError(self.message, code=self.code, params={'value': value}) + + diff --git a/b61f44c339830ea53663415f00cbd17e2fd5aa43.patch b/b61f44c339830ea53663415f00cbd17e2fd5aa43.patch new file mode 100644 index 0000000..e27b85f --- /dev/null +++ b/b61f44c339830ea53663415f00cbd17e2fd5aa43.patch @@ -0,0 +1,29 @@ +From b61f44c339830ea53663415f00cbd17e2fd5aa43 Mon Sep 17 00:00:00 2001 +From: Mariusz Felisiak +Date: Thu, 2 Sep 2021 10:56:56 +0200 +Subject: [PATCH] [3.2.x] Fixed #33082 -- Fixed + CommandTests.test_subparser_invalid_option on Python 3.9.7+. +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Thanks Michał Górny for the report. + +Backport of 50ed545e2fa02c51e0d1559b83624f256e4b499b from main. +--- + tests/user_commands/tests.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/tests/user_commands/tests.py b/tests/user_commands/tests.py +index 9262e2717acb8..9e242bc8d5ab9 100644 +--- a/tests/user_commands/tests.py ++++ b/tests/user_commands/tests.py +@@ -334,7 +334,7 @@ def test_subparser_dest_required_args(self): + self.assertIn('bar', out.getvalue()) + + def test_subparser_invalid_option(self): +- msg = "Error: invalid choice: 'test' (choose from 'foo')" ++ msg = "invalid choice: 'test' (choose from 'foo')" + with self.assertRaisesMessage(CommandError, msg): + management.call_command('subparser', 'test', 12) + if PY37: diff --git a/python-django.spec b/python-django.spec index 212d99c..3a9ae6f 100644 --- a/python-django.spec +++ b/python-django.spec @@ -1,13 +1,21 @@ %bcond_with python_bootstrap +%bcond_with doc Summary: A high-level Python Web framework Name: python-django -Version: 4.2.5 +Version: 3.2 Release: 1%{?dist} License: BSD URL: https://www.djangoproject.com/ Source0: https://github.com/django/django/archive/refs/tags/%{version}.tar.gz +Patch0001: Add-Python-3.11-support-for-tests.patch +Patch0002: Fix-url-validator.patch +Patch0003: 2d2c1d0c97832860fbd6597977e2aae17dd7e5b2.patch +Patch0004: 15168.patch +Patch0005: b61f44c339830ea53663415f00cbd17e2fd5aa43.patch +Patch0006: 36fa071d6ebd18a61c4d7f1b5c9d17106134bd44.patch + BuildArch: noarch @@ -28,6 +36,7 @@ This package contains the Bash completion files form Django high-level Python Web framework. +%if %{with doc} %package -n python3-django-doc Summary: Documentation for Django BuildRequires: make @@ -36,6 +45,7 @@ Suggests: python3-django = %{version}-%{release} %description -n python3-django-doc This package contains the documentation for the Django high-level Python Web framework. +%endif %package -n python3-django @@ -53,7 +63,7 @@ without needing to reinvent the wheel. It’s free and open source. %prep -%setup -q -n django-%{version} +%autosetup -n django-%{version} -p1 pathfix.py -pni "%{__python3} %{py3_shbang_opts}" . @@ -65,8 +75,10 @@ pathfix.py -pni "%{__python3} %{py3_shbang_opts}" . %py3_install pathfix.py -pni "%{__python3} %{py3_shbang_opts}" %{buildroot}/usr/lib/python%{python3_version}/site-packages/django/conf/project_template/manage.py-tpl +%if %{with doc} (cd docs && mkdir djangohtml && mkdir -p _build/{doctrees,html} && make html) cp -ar docs .. +%endif mkdir -p %{buildroot}%{_mandir}/man1/ cp -p docs/man/* %{buildroot}%{_mandir}/man1/ @@ -97,8 +109,10 @@ python3 runtests.py --settings=test_sqlite --verbosity=2 --parallel 1 %files bash-completion %{_datadir}/bash-completion +%if %{with doc} %files -n python3-django-doc %doc docs/_build/html/* +%endif %files -n python3-django %license LICENSE @@ -107,11 +121,15 @@ python3 runtests.py --settings=test_sqlite --verbosity=2 --parallel 1 %{_bindir}/django-admin-3 %{_bindir}/django-admin-%{python3_version} %{_bindir}/python3-django-admin +%{_bindir}/django-admin.py %{python3_sitelib}/* %{_mandir}/man1/django-admin.1* %changelog +* Sat Oct 07 2023 Miaojun Dong - 3.2-1 +- Downgrade version to 3.2 to meet openstack-dashboard-23.0.0 Requirement + * Thu Sep 28 2023 cunshunxia - 4.2.5-1 - Upgrade to version 4.2.5 diff --git a/sources b/sources index 8422546..1d04386 100644 --- a/sources +++ b/sources @@ -1 +1 @@ -SHA512 (4.2.5.tar.gz) = 5a0ee6928634d8b07bb35fd2064521b3b2f505930ff2cabb9709f914246cb141f87df7e45ee3f9357353302c9f7460a60a311d87a4ff727c603b865f81e2915a +SHA512 (3.2.tar.gz) = 9eb834ed6a79f888cc1339db55193d88ae29ed3f9af5fd3bb84d34f438a54a6992b8ed991dcdf91f7ad55700fd419b50beaa25e559e99dd79c5122e643263046 -- Gitee