diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 44dfb755..688b8650 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -14,8 +14,8 @@ jobs: strategy: matrix: python: - - "3.9" - - "3.13" + - "3.11" # Debian Bookworm + - "3.13" # Debian Trixie services: db: diff --git a/Pipfile b/Pipfile index 68a0f634..019b48de 100644 --- a/Pipfile +++ b/Pipfile @@ -10,7 +10,7 @@ name = "pypi" # Like ipaddress but for MACs netaddr = ">=0.8.0,<1.4.0" # Debian stretch version -Django = "~=4.2" +Django = "~=5.2" # postgres lib used by django psycopg2-binary = "~=2.9" # Django support for network address fields on PostgreSQL @@ -21,7 +21,7 @@ Pillow = "~=10.0" dateparser = "~=1.1" # Use Twelve-factor methodology to configure environment variables django-environ = "<1.0.0" -django_compressor = "~=4.4" +django_compressor = "~=4.6" # Used for API access - adminapi paramiko = ">=2.7,<4" typing-extensions = "*" @@ -31,7 +31,7 @@ typing-extensions = "*" sphinx = "~=7.0" sphinx-rtd-theme = "~=2.0" # Provides runserver_plus and other gadgets -django-extensions = "<4.0.0" +django-extensions = "<5.0.0" # Required for runserver_plus to work Werkzeug = "<4.0.0" # Used to package a single executable according to PEP 441 @@ -44,7 +44,7 @@ faker = "<14.0.0" tblib = "*" [requires] -python_version = "3.13" +python_version = ">=3.11,<3.14" [production] sentry-sdk = {version = "*", extras = ["django"]} diff --git a/Pipfile.lock b/Pipfile.lock index ba58b64f..ee49e641 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,11 +1,11 @@ { "_meta": { "hash": { - "sha256": "297b68e4c14970acaa4c8a70e0a3ad3ec307d8c5d2db917815832722b620987f" + "sha256": "ad0363f39987ab4b6b8bfd6f33db4bdb691007f4b8178d5151c174e7c49f2f86" }, "pipfile-spec": 6, "requires": { - "python_version": "3.13" + "python_version": ">=3.11,<3.14" }, "sources": [ { @@ -246,12 +246,12 @@ }, "django": { "hashes": [ - "sha256:4d07aaf1c62f9984842b67c2874ebbf7056a17be253860299b93ae1881faad65", - "sha256:4ebc7a434e3819db6cf4b399fb5b3f536310a30e8486f08b66886840be84b37c" + "sha256:0eb4a9bb1853a35b0286dbc6d916bd352c8c2687195a7f2d6f80cefd840e4970", + "sha256:5154a9bf84ac01dde011e367f355c07dbb329532e06810dcf3ef2af269e236e7" ], "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==4.2.30" + "markers": "python_version >= '3.10'", + "version": "==5.2.15" }, "django-appconf": { "hashes": [ @@ -820,11 +820,11 @@ }, "tzlocal": { "hashes": [ - "sha256:024d11221ff83453eae1f608f09b145b9779e1345d08c15404ce8ff7917cf629", - "sha256:41e1293f80d4b5ff38dff222601a8fbd06b4fdcaf25e224704047ad26a39af54" + "sha256:24ce97bb58e2a973f7640ec2553ab4e6f6d5a0d0d1aa9dc43bca21d89e1feb82", + "sha256:3a8c9bc18cf47e1dcde252ea0e6a72a6cde320a400b6ac6db1f1f8cccd553c00" ], "markers": "python_version >= '3.10'", - "version": "==5.4" + "version": "==5.4.3" } }, "develop": { @@ -854,11 +854,11 @@ }, "certifi": { "hashes": [ - "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897", - "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d" + "sha256:024c88eeec92ca068db80f02b8b07c9cef7b9fe261d1d535abfd5abd6f6af432", + "sha256:2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db" ], "markers": "python_version >= '3.7'", - "version": "==2026.5.20" + "version": "==2026.6.17" }, "charset-normalizer": { "hashes": [ @@ -997,21 +997,21 @@ }, "django": { "hashes": [ - "sha256:4d07aaf1c62f9984842b67c2874ebbf7056a17be253860299b93ae1881faad65", - "sha256:4ebc7a434e3819db6cf4b399fb5b3f536310a30e8486f08b66886840be84b37c" + "sha256:0eb4a9bb1853a35b0286dbc6d916bd352c8c2687195a7f2d6f80cefd840e4970", + "sha256:5154a9bf84ac01dde011e367f355c07dbb329532e06810dcf3ef2af269e236e7" ], "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==4.2.30" + "markers": "python_version >= '3.10'", + "version": "==5.2.15" }, "django-extensions": { "hashes": [ - "sha256:44d27919d04e23b3f40231c4ab7af4e61ce832ef46d610cc650d53e68328410a", - "sha256:9600b7562f79a92cbf1fde6403c04fee314608fefbb595502e34383ae8203401" + "sha256:0699a7af28f2523bf8db309a80278519362cd4b6e1fd0a8cd4bf063e1e023336", + "sha256:7b70a4d28e9b840f44694e3f7feb54f55d495f8b3fa6c5c0e5e12bcb2aa3cdeb" ], "index": "pypi", - "markers": "python_version >= '3.6'", - "version": "==3.2.3" + "markers": "python_version >= '3.9'", + "version": "==4.1" }, "docutils": { "hashes": [ @@ -1337,31 +1337,31 @@ }, "certifi": { "hashes": [ - "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897", - "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d" + "sha256:024c88eeec92ca068db80f02b8b07c9cef7b9fe261d1d535abfd5abd6f6af432", + "sha256:2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db" ], "markers": "python_version >= '3.7'", - "version": "==2026.5.20" + "version": "==2026.6.17" }, "django": { "hashes": [ - "sha256:4d07aaf1c62f9984842b67c2874ebbf7056a17be253860299b93ae1881faad65", - "sha256:4ebc7a434e3819db6cf4b399fb5b3f536310a30e8486f08b66886840be84b37c" + "sha256:0eb4a9bb1853a35b0286dbc6d916bd352c8c2687195a7f2d6f80cefd840e4970", + "sha256:5154a9bf84ac01dde011e367f355c07dbb329532e06810dcf3ef2af269e236e7" ], "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==4.2.30" + "markers": "python_version >= '3.10'", + "version": "==5.2.15" }, "sentry-sdk": { "extras": [ "django" ], "hashes": [ - "sha256:27f61d13a86c3c1648dec666dd5a64f79772dd6a84b446f11866601ecab24f6f", - "sha256:3c870b9f50d9fd15b58c817dbde1c7cfaa9fe3f05df0a4c6edd5571cb82f5491" + "sha256:2a1502bf864769275dbc8c2c9fc7a0f7f5e18358180b615d262d13a31ffba216", + "sha256:3a9b5ddd403f79eb73bd670f75f04485819db53d28f76ced7bc09041cb0dfd6a" ], "markers": "python_version >= '3.6'", - "version": "==2.62.0" + "version": "==2.63.0" }, "sqlparse": { "hashes": [ diff --git a/serveradmin/api/decorators.py b/serveradmin/api/decorators.py index f2f637cd..dc29f290 100644 --- a/serveradmin/api/decorators.py +++ b/serveradmin/api/decorators.py @@ -3,7 +3,7 @@ Copyright (c) 2019 InnoGames GmbH """ -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone as dt_timezone from functools import update_wrapper from logging import getLogger from base64 import b64decode @@ -55,7 +55,7 @@ def _wrapper(request): token = request.META.get('HTTP_X_SECURITYTOKEN') then = datetime.utcfromtimestamp( int(request.META['HTTP_X_TIMESTAMP']) - ).replace(tzinfo=timezone.utc) + ).replace(tzinfo=dt_timezone.utc) body_json = json.loads(body) if body else None status_code = 200 diff --git a/serveradmin/serverdb/migrations/0024_alter_serverbooleanattribute_attribute.py b/serveradmin/serverdb/migrations/0024_alter_serverbooleanattribute_attribute.py new file mode 100644 index 00000000..52c198d9 --- /dev/null +++ b/serveradmin/serverdb/migrations/0024_alter_serverbooleanattribute_attribute.py @@ -0,0 +1,42 @@ +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('serverdb', '0023_attribute_multi_target_servertype'), + ] + + operations = [ + # Drop obsolete indexes. The like one is not required because we do not + # support (fuzzy) search for attribute ids and the other one is + # redundant. + migrations.RunSQL( + sql=[ + "DROP INDEX IF EXISTS " + "server_boolean_attribute_attribute_id_b1ad575f;", + "DROP INDEX IF EXISTS " + "server_boolean_attribute_attribute_id_b1ad575f_like;", + ], + reverse_sql=[ + "CREATE INDEX server_boolean_attribute_attribute_id_b1ad575f " + "ON server_boolean_attribute (attribute_id);", + "CREATE INDEX " + "server_boolean_attribute_attribute_id_b1ad575f_like " + "ON server_boolean_attribute (attribute_id varchar_pattern_ops);", + ], + state_operations=[ + migrations.AlterField( + model_name='serverbooleanattribute', + name='attribute', + field=models.ForeignKey( + db_index=False, + limit_choices_to={'type': 'boolean'}, + on_delete=django.db.models.deletion.CASCADE, + to='serverdb.attribute', + ), + ), + ], + ), + ] diff --git a/serveradmin/serverdb/migrations/0025_rename_serverbooleanattribute_attribute_server_bool_attribu_25fb6c_idx_and_more.py b/serveradmin/serverdb/migrations/0025_rename_serverbooleanattribute_attribute_server_bool_attribu_25fb6c_idx_and_more.py new file mode 100644 index 00000000..e3bb86a6 --- /dev/null +++ b/serveradmin/serverdb/migrations/0025_rename_serverbooleanattribute_attribute_server_bool_attribu_25fb6c_idx_and_more.py @@ -0,0 +1,53 @@ +# Generated by Django 5.2.15 on 2026-06-12 14:00 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('serverdb', '0024_alter_serverbooleanattribute_attribute'), + ] + + operations = [ + migrations.RenameIndex( + model_name='serverbooleanattribute', + new_name='server_bool_attribu_25fb6c_idx', + old_fields=('attribute',), + ), + migrations.RenameIndex( + model_name='serverdateattribute', + new_name='server_date_attribu_12cbb2_idx', + old_fields=('attribute', 'value'), + ), + migrations.RenameIndex( + model_name='serverdatetimeattribute', + new_name='server_date_attribu_f35d26_idx', + old_fields=('attribute', 'value'), + ), + migrations.RenameIndex( + model_name='serverinetattribute', + new_name='server_inet_attribu_8feba2_idx', + old_fields=('attribute', 'value'), + ), + migrations.RenameIndex( + model_name='servermacaddressattribute', + new_name='server_maca_attribu_1bcfb5_idx', + old_fields=('attribute', 'value'), + ), + migrations.RenameIndex( + model_name='servernumberattribute', + new_name='server_numb_attribu_6223e1_idx', + old_fields=('attribute', 'value'), + ), + migrations.RenameIndex( + model_name='serverrelationattribute', + new_name='server_rela_attribu_ecf9db_idx', + old_fields=('attribute', 'value'), + ), + migrations.RenameIndex( + model_name='serverstringattribute', + new_name='server_stri_attribu_59edc4_idx', + old_fields=('attribute', 'value'), + ), + ] diff --git a/serveradmin/serverdb/models.py b/serveradmin/serverdb/models.py index e91f0ccf..3676da37 100644 --- a/serveradmin/serverdb/models.py +++ b/serveradmin/serverdb/models.py @@ -612,7 +612,7 @@ class Meta: app_label = "serverdb" db_table = "server_string_attribute" unique_together = [["server", "attribute", "value"]] - index_together = [["attribute", "value"]] + indexes = [models.Index(fields=["attribute", "value"])] def save_value(self, value): for char in "'\"": @@ -659,7 +659,7 @@ class Meta: app_label = "serverdb" db_table = "server_relation_attribute" unique_together = [["server", "attribute", "value"]] - index_together = [["attribute", "value"]] + indexes = [models.Index(fields=["attribute", "value"])] def save_value(self, value): try: @@ -682,6 +682,7 @@ def save_value(self, value): class ServerBooleanAttribute(ServerAttribute): attribute = models.ForeignKey( Attribute, + db_index=False, on_delete=models.CASCADE, limit_choices_to=dict(type="boolean"), ) @@ -690,7 +691,7 @@ class Meta: app_label = "serverdb" db_table = "server_boolean_attribute" unique_together = [["server", "attribute"]] - index_together = [["attribute"]] + indexes = [models.Index(fields=["attribute"])] def get_value(self): return True @@ -715,7 +716,7 @@ class Meta: app_label = "serverdb" db_table = "server_number_attribute" unique_together = [["server", "attribute", "value"]] - index_together = [["attribute", "value"]] + indexes = [models.Index(fields=["attribute", "value"])] def get_value(self): return ( @@ -738,8 +739,8 @@ class Meta: app_label = "serverdb" db_table = "server_inet_attribute" unique_together = [["server", "attribute", "value"]] - index_together = [["attribute", "value"]] indexes = [ + models.Index(fields=["attribute", "value"]), GistIndex(fields=["value"], opclasses=["inet_ops"], name="server_inet_attribute_value_idx"), ] @@ -798,7 +799,7 @@ class Meta: app_label = "serverdb" db_table = "server_macaddr_attribute" unique_together = [["server", "attribute", "value"]] - index_together = [["attribute", "value"]] + indexes = [models.Index(fields=["attribute", "value"])] class ServerDateAttribute(ServerAttribute): @@ -814,7 +815,7 @@ class Meta: app_label = "serverdb" db_table = "server_date_attribute" unique_together = [["server", "attribute", "value"]] - index_together = [["attribute", "value"]] + indexes = [models.Index(fields=["attribute", "value"])] class ServerDateTimeAttribute(ServerAttribute): @@ -830,7 +831,7 @@ class Meta: app_label = "serverdb" db_table = "server_datetime_attribute" unique_together = [["server", "attribute", "value"]] - index_together = [["attribute", "value"]] + indexes = [models.Index(fields=["attribute", "value"])] class ChangeCommit(models.Model): diff --git a/serveradmin/settings.py b/serveradmin/settings.py index 65aedf48..999c16ce 100644 --- a/serveradmin/settings.py +++ b/serveradmin/settings.py @@ -92,10 +92,6 @@ # to load the internationalization machinery. USE_I18N = False -# If you set this to False, Django will not format dates, numbers and -# calendars according to the current locale. -USE_L10N = False - # If you set this to False, Django will not use timezone-aware datetimes. USE_TZ = True diff --git a/setup.py b/setup.py index ae2dab8c..a1ab4bba 100755 --- a/setup.py +++ b/setup.py @@ -66,7 +66,7 @@ 'paramiko>=2.7,<4', 'netaddr>=0.8.0,<1.4.0', ], - python_requires=">=3.9,<3.14", + python_requires=">=3.11,<3.14", author='InnoGames System Administration', author_email='it@innogames.com', )