From 75cb667314559589aaa386b04c767b16a73dfe03 Mon Sep 17 00:00:00 2001 From: mike wakerly Date: Thu, 14 Jul 2022 14:28:24 +0100 Subject: [PATCH 1/7] deps: update django 2.2 -> 3.0 --- poetry.lock | 36 ++++++++++++++++++++++++++---------- pyproject.toml | 2 +- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/poetry.lock b/poetry.lock index cec6664c..ec0e6979 100644 --- a/poetry.lock +++ b/poetry.lock @@ -25,6 +25,17 @@ python-versions = ">=3.6" [package.dependencies] vine = ">=5.0.0" +[[package]] +name = "asgiref" +version = "3.5.2" +description = "ASGI specs, helper code, and adapters" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +tests = ["pytest", "pytest-asyncio", "mypy (>=0.800)"] + [[package]] name = "async-timeout" version = "4.0.2" @@ -290,13 +301,14 @@ python-versions = "*" [[package]] name = "django" -version = "2.2.28" +version = "3.0" description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design." category = "main" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" [package.dependencies] +asgiref = ">=3.2,<4.0" pytz = "*" sqlparse = ">=0.2.2" @@ -366,15 +378,15 @@ hiredis = ["redis[hiredis] (>=3,!=4.0.0,!=4.0.1)"] [[package]] name = "django-registration" -version = "3.2" +version = "3.1.2" description = "An extensible user-registration application for Django" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.5" [package.dependencies] confusable-homoglyphs = ">=3.0,<4.0" -Django = ">=2.2,<3.0.0 || >=3.1.0" +Django = ">=2.2" [[package]] name = "docutils" @@ -1141,7 +1153,7 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [metadata] lock-version = "1.1" python-versions = "^3.10.5" -content-hash = "1f7e3cd78dce77caebcc887f16193126e3f68af3aa80ef84c9959012569b4370" +content-hash = "5d8dcab39210c911d3a2c5cd5e327ff7650e9397be136612827786420fdb06bd" [metadata.files] addict = [ @@ -1156,6 +1168,10 @@ amqp = [ {file = "amqp-5.1.1-py3-none-any.whl", hash = "sha256:6f0956d2c23d8fa6e7691934d8c3930eadb44972cbbd1a7ae3a520f735d43359"}, {file = "amqp-5.1.1.tar.gz", hash = "sha256:2c1b13fecc0893e946c65cbd5f36427861cffa4ea2201d8f6fca22e2a373b5e2"}, ] +asgiref = [ + {file = "asgiref-3.5.2-py3-none-any.whl", hash = "sha256:1d2880b792ae8757289136f1db2b7b99100ce959b2aa57fd69dab783d05afac4"}, + {file = "asgiref-3.5.2.tar.gz", hash = "sha256:4a29362a6acebe09bf1d6640db38c1dc3d9217c68e6f9f6204d72667fc19a424"}, +] async-timeout = [ {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, @@ -1319,8 +1335,8 @@ dj-email-url = [ {file = "dj_email_url-1.0.5-py2.py3-none-any.whl", hash = "sha256:64257c4f9d8139a4af8e5267229d32260e433fbf257b0cf8fc855bb0cc39ca7d"}, ] django = [ - {file = "Django-2.2.28-py3-none-any.whl", hash = "sha256:365429d07c1336eb42ba15aa79f45e1c13a0b04d5c21569e7d596696418a6a45"}, - {file = "Django-2.2.28.tar.gz", hash = "sha256:0200b657afbf1bc08003845ddda053c7641b9b24951e52acd51f6abda33a7413"}, + {file = "Django-3.0-py3-none-any.whl", hash = "sha256:6f857bd4e574442ba35a7172f1397b303167dae964cf18e53db5e85fe248d000"}, + {file = "Django-3.0.tar.gz", hash = "sha256:d98c9b6e5eed147bc51f47c014ff6826bd1ab50b166956776ee13db5a58804ae"}, ] django-appconf = [ {file = "django-appconf-1.0.5.tar.gz", hash = "sha256:be3db0be6c81fa84742000b89a81c016d70ae66a7ccb620cdef592b1f1a6aaa4"}, @@ -1343,8 +1359,8 @@ django-redis = [ {file = "django_redis-5.2.0-py3-none-any.whl", hash = "sha256:1d037dc02b11ad7aa11f655d26dac3fb1af32630f61ef4428860a2e29ff92026"}, ] django-registration = [ - {file = "django-registration-3.2.tar.gz", hash = "sha256:2ea8c7d89a8760ccde41dfd335aa28ba89073d09aab5a0f5f3d7c8c148fcc518"}, - {file = "django_registration-3.2-py3-none-any.whl", hash = "sha256:e79fdbfa22bfaf4182efccb6604391391a7de19438d2669c5b9520a7708efbd2"}, + {file = "django-registration-3.1.2.tar.gz", hash = "sha256:c9985f9ffd123534026bf5f39adb0b48fd7bf930b965f27f9a487d135f377ac6"}, + {file = "django_registration-3.1.2-py3-none-any.whl", hash = "sha256:dde525b08880da1d72b556f19dfd1c6588233a43f8bc354481f2d3e8896c44f8"}, ] docutils = [ {file = "docutils-0.18.1-py2.py3-none-any.whl", hash = "sha256:23010f129180089fbcd3bc08cfefccb3b890b0050e1ca00c867036e9d161b98c"}, diff --git a/pyproject.toml b/pyproject.toml index d68a516e..bd76c464 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,7 +43,7 @@ redis = "*" requests-oauthlib = "*" requests = "*" whitenoise = "*" -Django = "<3" +Django = "3.0" Pillow = "*" PyYAML = "*" mysqlclient = "*" From a95ca9783219cf36f15c69fca7d90a8598465693 Mon Sep 17 00:00:00 2001 From: mike wakerly Date: Thu, 14 Jul 2022 14:30:35 +0100 Subject: [PATCH 2/7] deps: update django 3.0 -> 3.1 --- poetry.lock | 30 +++++++++++++++--------------- pyproject.toml | 2 +- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/poetry.lock b/poetry.lock index ec0e6979..d6e0a452 100644 --- a/poetry.lock +++ b/poetry.lock @@ -27,14 +27,14 @@ vine = ">=5.0.0" [[package]] name = "asgiref" -version = "3.5.2" +version = "3.2.10" description = "ASGI specs, helper code, and adapters" category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.5" [package.extras] -tests = ["pytest", "pytest-asyncio", "mypy (>=0.800)"] +tests = ["pytest", "pytest-asyncio"] [[package]] name = "async-timeout" @@ -301,14 +301,14 @@ python-versions = "*" [[package]] name = "django" -version = "3.0" +version = "3.1" description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design." category = "main" optional = false python-versions = ">=3.6" [package.dependencies] -asgiref = ">=3.2,<4.0" +asgiref = ">=3.2.10,<3.3.0" pytz = "*" sqlparse = ">=0.2.2" @@ -378,15 +378,15 @@ hiredis = ["redis[hiredis] (>=3,!=4.0.0,!=4.0.1)"] [[package]] name = "django-registration" -version = "3.1.2" +version = "3.2" description = "An extensible user-registration application for Django" category = "main" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" [package.dependencies] confusable-homoglyphs = ">=3.0,<4.0" -Django = ">=2.2" +Django = ">=2.2,<3.0.0 || >=3.1.0" [[package]] name = "docutils" @@ -1153,7 +1153,7 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [metadata] lock-version = "1.1" python-versions = "^3.10.5" -content-hash = "5d8dcab39210c911d3a2c5cd5e327ff7650e9397be136612827786420fdb06bd" +content-hash = "85d1b9d4a42699a2352dc991f49ec92d01de83a46f68e0ac946194b765635d09" [metadata.files] addict = [ @@ -1169,8 +1169,8 @@ amqp = [ {file = "amqp-5.1.1.tar.gz", hash = "sha256:2c1b13fecc0893e946c65cbd5f36427861cffa4ea2201d8f6fca22e2a373b5e2"}, ] asgiref = [ - {file = "asgiref-3.5.2-py3-none-any.whl", hash = "sha256:1d2880b792ae8757289136f1db2b7b99100ce959b2aa57fd69dab783d05afac4"}, - {file = "asgiref-3.5.2.tar.gz", hash = "sha256:4a29362a6acebe09bf1d6640db38c1dc3d9217c68e6f9f6204d72667fc19a424"}, + {file = "asgiref-3.2.10-py3-none-any.whl", hash = "sha256:9fc6fb5d39b8af147ba40765234fa822b39818b12cc80b35ad9b0cef3a476aed"}, + {file = "asgiref-3.2.10.tar.gz", hash = "sha256:7e51911ee147dd685c3c8b805c0ad0cb58d360987b56953878f8c06d2d1c6f1a"}, ] async-timeout = [ {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, @@ -1335,8 +1335,8 @@ dj-email-url = [ {file = "dj_email_url-1.0.5-py2.py3-none-any.whl", hash = "sha256:64257c4f9d8139a4af8e5267229d32260e433fbf257b0cf8fc855bb0cc39ca7d"}, ] django = [ - {file = "Django-3.0-py3-none-any.whl", hash = "sha256:6f857bd4e574442ba35a7172f1397b303167dae964cf18e53db5e85fe248d000"}, - {file = "Django-3.0.tar.gz", hash = "sha256:d98c9b6e5eed147bc51f47c014ff6826bd1ab50b166956776ee13db5a58804ae"}, + {file = "Django-3.1-py3-none-any.whl", hash = "sha256:1a63f5bb6ff4d7c42f62a519edc2adbb37f9b78068a5a862beff858b68e3dc8b"}, + {file = "Django-3.1.tar.gz", hash = "sha256:2d390268a13c655c97e0e2ede9d117007996db692c1bb93eabebd4fb7ea7012b"}, ] django-appconf = [ {file = "django-appconf-1.0.5.tar.gz", hash = "sha256:be3db0be6c81fa84742000b89a81c016d70ae66a7ccb620cdef592b1f1a6aaa4"}, @@ -1359,8 +1359,8 @@ django-redis = [ {file = "django_redis-5.2.0-py3-none-any.whl", hash = "sha256:1d037dc02b11ad7aa11f655d26dac3fb1af32630f61ef4428860a2e29ff92026"}, ] django-registration = [ - {file = "django-registration-3.1.2.tar.gz", hash = "sha256:c9985f9ffd123534026bf5f39adb0b48fd7bf930b965f27f9a487d135f377ac6"}, - {file = "django_registration-3.1.2-py3-none-any.whl", hash = "sha256:dde525b08880da1d72b556f19dfd1c6588233a43f8bc354481f2d3e8896c44f8"}, + {file = "django-registration-3.2.tar.gz", hash = "sha256:2ea8c7d89a8760ccde41dfd335aa28ba89073d09aab5a0f5f3d7c8c148fcc518"}, + {file = "django_registration-3.2-py3-none-any.whl", hash = "sha256:e79fdbfa22bfaf4182efccb6604391391a7de19438d2669c5b9520a7708efbd2"}, ] docutils = [ {file = "docutils-0.18.1-py2.py3-none-any.whl", hash = "sha256:23010f129180089fbcd3bc08cfefccb3b890b0050e1ca00c867036e9d161b98c"}, diff --git a/pyproject.toml b/pyproject.toml index bd76c464..1bea01aa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,7 +43,7 @@ redis = "*" requests-oauthlib = "*" requests = "*" whitenoise = "*" -Django = "3.0" +Django = "3.1" Pillow = "*" PyYAML = "*" mysqlclient = "*" From 7379dbf622551ec1fa1b33e7050715594fc6209f Mon Sep 17 00:00:00 2001 From: mike wakerly Date: Thu, 14 Jul 2022 15:19:04 +0100 Subject: [PATCH 3/7] deps: update django 3.1 -> 3.2 --- poetry.lock | 32 ++++++++++++++++---------------- pykeg/settings.py | 2 ++ pyproject.toml | 2 +- 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/poetry.lock b/poetry.lock index d6e0a452..df065008 100644 --- a/poetry.lock +++ b/poetry.lock @@ -27,14 +27,14 @@ vine = ">=5.0.0" [[package]] name = "asgiref" -version = "3.2.10" +version = "3.5.2" description = "ASGI specs, helper code, and adapters" category = "main" optional = false -python-versions = ">=3.5" +python-versions = ">=3.7" [package.extras] -tests = ["pytest", "pytest-asyncio"] +tests = ["pytest", "pytest-asyncio", "mypy (>=0.800)"] [[package]] name = "async-timeout" @@ -301,19 +301,19 @@ python-versions = "*" [[package]] name = "django" -version = "3.1" +version = "3.2" description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design." category = "main" optional = false python-versions = ">=3.6" [package.dependencies] -asgiref = ">=3.2.10,<3.3.0" +asgiref = ">=3.3.2,<4" pytz = "*" sqlparse = ">=0.2.2" [package.extras] -argon2 = ["argon2-cffi (>=16.1.0)"] +argon2 = ["argon2-cffi (>=19.1.0)"] bcrypt = ["bcrypt"] [[package]] @@ -378,15 +378,15 @@ hiredis = ["redis[hiredis] (>=3,!=4.0.0,!=4.0.1)"] [[package]] name = "django-registration" -version = "3.2" +version = "3.3" description = "An extensible user-registration application for Django" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] confusable-homoglyphs = ">=3.0,<4.0" -Django = ">=2.2,<3.0.0 || >=3.1.0" +Django = ">=3.2" [[package]] name = "docutils" @@ -1153,7 +1153,7 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [metadata] lock-version = "1.1" python-versions = "^3.10.5" -content-hash = "85d1b9d4a42699a2352dc991f49ec92d01de83a46f68e0ac946194b765635d09" +content-hash = "a946e85e305e60f70a80cf29bb56b934feffbc514b5ca6b48035f530e8a7115e" [metadata.files] addict = [ @@ -1169,8 +1169,8 @@ amqp = [ {file = "amqp-5.1.1.tar.gz", hash = "sha256:2c1b13fecc0893e946c65cbd5f36427861cffa4ea2201d8f6fca22e2a373b5e2"}, ] asgiref = [ - {file = "asgiref-3.2.10-py3-none-any.whl", hash = "sha256:9fc6fb5d39b8af147ba40765234fa822b39818b12cc80b35ad9b0cef3a476aed"}, - {file = "asgiref-3.2.10.tar.gz", hash = "sha256:7e51911ee147dd685c3c8b805c0ad0cb58d360987b56953878f8c06d2d1c6f1a"}, + {file = "asgiref-3.5.2-py3-none-any.whl", hash = "sha256:1d2880b792ae8757289136f1db2b7b99100ce959b2aa57fd69dab783d05afac4"}, + {file = "asgiref-3.5.2.tar.gz", hash = "sha256:4a29362a6acebe09bf1d6640db38c1dc3d9217c68e6f9f6204d72667fc19a424"}, ] async-timeout = [ {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, @@ -1335,8 +1335,8 @@ dj-email-url = [ {file = "dj_email_url-1.0.5-py2.py3-none-any.whl", hash = "sha256:64257c4f9d8139a4af8e5267229d32260e433fbf257b0cf8fc855bb0cc39ca7d"}, ] django = [ - {file = "Django-3.1-py3-none-any.whl", hash = "sha256:1a63f5bb6ff4d7c42f62a519edc2adbb37f9b78068a5a862beff858b68e3dc8b"}, - {file = "Django-3.1.tar.gz", hash = "sha256:2d390268a13c655c97e0e2ede9d117007996db692c1bb93eabebd4fb7ea7012b"}, + {file = "Django-3.2-py3-none-any.whl", hash = "sha256:0604e84c4fb698a5e53e5857b5aea945b2f19a18f25f10b8748dbdf935788927"}, + {file = "Django-3.2.tar.gz", hash = "sha256:21f0f9643722675976004eb683c55d33c05486f94506672df3d6a141546f389d"}, ] django-appconf = [ {file = "django-appconf-1.0.5.tar.gz", hash = "sha256:be3db0be6c81fa84742000b89a81c016d70ae66a7ccb620cdef592b1f1a6aaa4"}, @@ -1359,8 +1359,8 @@ django-redis = [ {file = "django_redis-5.2.0-py3-none-any.whl", hash = "sha256:1d037dc02b11ad7aa11f655d26dac3fb1af32630f61ef4428860a2e29ff92026"}, ] django-registration = [ - {file = "django-registration-3.2.tar.gz", hash = "sha256:2ea8c7d89a8760ccde41dfd335aa28ba89073d09aab5a0f5f3d7c8c148fcc518"}, - {file = "django_registration-3.2-py3-none-any.whl", hash = "sha256:e79fdbfa22bfaf4182efccb6604391391a7de19438d2669c5b9520a7708efbd2"}, + {file = "django-registration-3.3.tar.gz", hash = "sha256:884a4cc9ec87b9f1c0ceb6b6c4b7ba491c1877997a3cd29cc923697dac785eb8"}, + {file = "django_registration-3.3-py3-none-any.whl", hash = "sha256:dfa176f594fb465c93495caa55686be723a15829769511383e25172d2efbd0e6"}, ] docutils = [ {file = "docutils-0.18.1-py2.py3-none-any.whl", hash = "sha256:23010f129180089fbcd3bc08cfefccb3b890b0050e1ca00c867036e9d161b98c"}, diff --git a/pykeg/settings.py b/pykeg/settings.py index 6c002760..df2697b0 100644 --- a/pykeg/settings.py +++ b/pykeg/settings.py @@ -291,3 +291,5 @@ # Override any user-specified timezone: As of Kegbot 0.9.12, this is # specified in site settings. TIME_ZONE = "UTC" + +DEFAULT_AUTO_FIELD = "django.db.models.AutoField" diff --git a/pyproject.toml b/pyproject.toml index 1bea01aa..94911178 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,7 +43,7 @@ redis = "*" requests-oauthlib = "*" requests = "*" whitenoise = "*" -Django = "3.1" +Django = "3.2" Pillow = "*" PyYAML = "*" mysqlclient = "*" From 87101a78414d64747a7139021e8e1ebad9d59e50 Mon Sep 17 00:00:00 2001 From: mike wakerly Date: Thu, 14 Jul 2022 15:37:32 +0100 Subject: [PATCH 4/7] chore: update url maps to use `path`/`re_path` Required in Django 4.0 --- pykeg/web/account/urls.py | 24 +++--- pykeg/web/api/urls.py | 121 ++++++++++++++++--------------- pykeg/web/kbregistration/urls.py | 24 +++--- pykeg/web/kegadmin/urls.py | 88 +++++++++++----------- pykeg/web/kegweb/urls.py | 48 ++++++------ pykeg/web/setup_wizard/urls.py | 16 ++-- pykeg/web/urls.py | 3 +- 7 files changed, 162 insertions(+), 162 deletions(-) diff --git a/pykeg/web/account/urls.py b/pykeg/web/account/urls.py index c8065b1a..f2fa3d78 100644 --- a/pykeg/web/account/urls.py +++ b/pykeg/web/account/urls.py @@ -1,24 +1,24 @@ -from django.conf.urls import url from django.contrib.auth.views import PasswordChangeDoneView, PasswordChangeView +from django.urls import path from pykeg.plugin import util from pykeg.web.account import views urlpatterns = [ - url(r"^$", views.account_main, name="kb-account-main"), - url( - r"^activate/(?P[0-9a-zA-Z]+)/$", + path("", views.account_main, name="kb-account-main"), + path( + "activate//", views.activate_account, name="activate-account", ), - url(r"^password/done/$", PasswordChangeDoneView.as_view(), name="password_change_done"), - url(r"^password/$", PasswordChangeView.as_view(), name="password_change"), - url(r"^profile/$", views.edit_profile, name="account-profile"), - url(r"^invite/$", views.invite, name="account-invite"), - url(r"^confirm-email/(?P.+)$", views.confirm_email, name="account-confirm-email"), - url(r"^notifications/$", views.notifications, name="account-notifications"), - url(r"^regenerate-api-key/$", views.regenerate_api_key, name="regen-api-key"), - url(r"^plugin/(?P\w+)/$", views.plugin_settings, name="account-plugin-settings"), + path("password/done/", PasswordChangeDoneView.as_view(), name="password_change_done"), + path("password/", PasswordChangeView.as_view(), name="password_change"), + path("profile/", views.edit_profile, name="account-profile"), + path("invite/", views.invite, name="account-invite"), + path("confirm-email/", views.confirm_email, name="account-confirm-email"), + path("notifications/", views.notifications, name="account-notifications"), + path("regenerate-api-key/", views.regenerate_api_key, name="regen-api-key"), + path("plugin//", views.plugin_settings, name="account-plugin-settings"), ] urlpatterns += util.get_account_urls() diff --git a/pykeg/web/api/urls.py b/pykeg/web/api/urls.py index 08b8aa56..165a493d 100644 --- a/pykeg/web/api/urls.py +++ b/pykeg/web/api/urls.py @@ -1,72 +1,73 @@ -from django.conf.urls import url +from django.urls import path from . import views urlpatterns = [ # General endpoints - url(r"^status/?$", views.get_status), - url(r"^version/?$", views.get_version), + path("status/", views.get_status), + path("version/", views.get_version), # API authorization - url(r"^login/?$", views.login), - url(r"^logout/?$", views.logout), - url(r"^get-api-key/?$", views.get_api_key), - url(r"^devices/link/?$", views.link_device_new), - url(r"^devices/link/status/(?P[^/]+)?$", views.link_device_status), + path("login/", views.login), + path("logout/", views.logout), + path("get-api-key/", views.get_api_key), + path("devices/link/", views.link_device_new), + path("devices/link/status/", views.link_device_status), + path("devices/link/status/", views.link_device_status), # Kegbot objects - url(r"^auth-tokens/(?P[\w\.]+)/(?P\w+)/?$", views.get_auth_token), - url( - r"^auth-tokens/(?P[\w\.]+)/(?P\w+)/assign/?$", + path("auth-tokens///", views.get_auth_token), + path( + "auth-tokens///assign/", views.assign_auth_token, ), - url(r"^controllers/?$", views.all_controllers), - url(r"^controllers/(?P\d+)/?$", views.get_controller), - url(r"^drinks/?$", views.all_drinks), - url(r"^drinks/last/?$", views.last_drink), - url(r"^drinks/(?P\d+)/?$", views.get_drink), - url(r"^drinks/(?P\d+)/add-photo/?$", views.add_drink_photo), - url(r"^cancel-drink/?$", views.cancel_drink), - url(r"^events/?$", views.all_events), - url(r"^flow-meters/?$", views.all_flow_meters), - url(r"^flow-meters/(?P\d+)/?$", views.get_flow_meter), - url(r"^flow-toggles/?$", views.all_flow_toggles), - url(r"^flow-toggles/(?P\d+)/?$", views.get_flow_toggle), - url(r"^kegs/?$", views.all_kegs), - url(r"^kegs/(?P\d+)/?$", views.get_keg), - url(r"^kegs/(?P\d+)/end/?$", views.end_keg), - url(r"^kegs/(?P\d+)/drinks/?$", views.get_keg_drinks), - url(r"^kegs/(?P\d+)/events/?$", views.get_keg_events), - url(r"^kegs/(?P\d+)/sessions/?$", views.get_keg_sessions), - url(r"^kegs/(?P\d+)/stats/?$", views.get_keg_stats), - url(r"^keg-sizes/?$", views.get_keg_sizes), - url(r"^pictures/?$", views.pictures), - url(r"^sessions/?$", views.all_sessions), - url(r"^sessions/current/?$", views.current_session), - url(r"^sessions/(?P\d+)/?$", views.get_session), - url(r"^sessions/(?P\d+)/stats/?$", views.get_session_stats), - url(r"^taps/?$", views.all_taps), - url(r"^taps/(?P[\w\.-]+)/activate/?$", views.tap_activate), - url(r"^taps/(?P[\w\.-]+)/calibrate/?$", views.tap_calibrate), - url(r"^taps/(?P[\w\.-]+)/spill/?$", views.tap_spill), - url(r"^taps/(?P[\w\.-]+)/connect-meter/?$", views.tap_connect_meter), - url(r"^taps/(?P[\w\.-]+)/disconnect-meter/?$", views.tap_disconnect_meter), - url(r"^taps/(?P[\w\.-]+)/connect-toggle/?$", views.tap_connect_toggle), - url(r"^taps/(?P[\w\.-]+)/disconnect-toggle/?$", views.tap_disconnect_toggle), - url(r"^taps/(?P[\w\.-]+)/?$", views.tap_detail), - url(r"^thermo-sensors/?$", views.all_thermo_sensors), - url(r"^thermo-sensors/(?P[^/]+)/?$", views.get_thermo_sensor), - url(r"^thermo-sensors/(?P[^/]+)/logs/?$", views.get_thermo_sensor_logs), - url(r"^taps/(?P[\w\.-]+)/connect-thermo/?$", views.tap_connect_thermo), - url(r"^taps/(?P[\w\.-]+)/disconnect-thermo/?$", views.tap_disconnect_thermo), - url(r"^users/?$", views.user_list), - url(r"^users/(?P[\w@.+-_]+)/drinks/?$", views.get_user_drinks), - url(r"^users/(?P[\w@.+-_]+)/events/?$", views.get_user_events), - url(r"^users/(?P[\w@.+-_]+)/stats/?$", views.get_user_stats), - url(r"^users/(?P[\w@.+-_]+)/photo/?$", views.user_photo), - url(r"^users/(?P[\w@.+-_]+)/?$", views.get_user), - url(r"^new-user/?$", views.register), - url(r"^stats/?$", views.get_system_stats), + path("controllers/", views.all_controllers), + path("controllers/", views.get_controller), + path("drinks/", views.all_drinks), + path("drinks/last/", views.last_drink), + path("drinks//", views.get_drink), + path("drinks//add-photo/", views.add_drink_photo), + path("cancel-drink/", views.cancel_drink), + path("events/", views.all_events), + path("flow-meters/", views.all_flow_meters), + path("flow-meters//", views.get_flow_meter), + path("flow-toggles/", views.all_flow_toggles), + path("flow-toggles//", views.get_flow_toggle), + path("kegs/", views.all_kegs), + path("kegs//", views.get_keg), + path("kegs//end/", views.end_keg), + path("kegs//drinks/", views.get_keg_drinks), + path("kegs//events/", views.get_keg_events), + path("kegs//sessions/", views.get_keg_sessions), + path("kegs//stats/", views.get_keg_stats), + path("keg-sizes/", views.get_keg_sizes), + path("pictures/", views.pictures), + path("sessions/", views.all_sessions), + path("sessions/current/", views.current_session), + path("sessions//", views.get_session), + path("sessions//stats/", views.get_session_stats), + path("taps/", views.all_taps), + path("taps//activate/", views.tap_activate), + path("taps//calibrate/", views.tap_calibrate), + path("taps//spill/", views.tap_spill), + path("taps//connect-meter/", views.tap_connect_meter), + path("taps//disconnect-meter/", views.tap_disconnect_meter), + path("taps//connect-toggle/", views.tap_connect_toggle), + path("taps//disconnect-toggle/", views.tap_disconnect_toggle), + path("taps//", views.tap_detail), + path("thermo-sensors/", views.all_thermo_sensors), + path("thermo-sensors//", views.get_thermo_sensor), + path("thermo-sensors//logs/", views.get_thermo_sensor_logs), + path("taps//connect-thermo/", views.tap_connect_thermo), + path("taps//disconnect-thermo/", views.tap_disconnect_thermo), + path("users/", views.user_list), + path("users//drinks/", views.get_user_drinks), + path("users//events/", views.get_user_events), + path("users//stats/", views.get_user_stats), + path("users//photo/", views.user_photo), + path("users//", views.get_user), + path("new-user/", views.register), + path("stats/", views.get_system_stats), # Deprecated endpoints - url(r"^sound-events/?$", views.all_sound_events), + path("sound-events/", views.all_sound_events), # Catch-all - url(r"", views.default_handler), + path(r"", views.default_handler), ] diff --git a/pykeg/web/kbregistration/urls.py b/pykeg/web/kbregistration/urls.py index a070c6ae..56aee4d9 100644 --- a/pykeg/web/kbregistration/urls.py +++ b/pykeg/web/kbregistration/urls.py @@ -1,4 +1,3 @@ -from django.conf.urls import include, url from django.contrib.auth.views import ( PasswordChangeDoneView, PasswordChangeView, @@ -7,30 +6,31 @@ PasswordResetDoneView, PasswordResetView, ) +from django.urls import include, path from pykeg.web.kbregistration import views from pykeg.web.kbregistration.forms import PasswordResetForm urlpatterns = [ - url(r"^register/?$", views.register, name="registration_register"), - url(r"^password/change/$", PasswordChangeView.as_view(), name="password_change"), - url(r"^password/change/done/$", PasswordChangeDoneView.as_view(), name="password_change_done"), - url( - r"^password/reset/$", + path("register/", views.register, name="registration_register"), + path("password/change/", PasswordChangeView.as_view(), name="password_change"), + path("password/change/done/", PasswordChangeDoneView.as_view(), name="password_change_done"), + path( + "password/reset/", PasswordResetView.as_view(), kwargs={"password_reset_form": PasswordResetForm}, name="password_reset", ), - url(r"^password/reset/done/$", PasswordResetDoneView.as_view(), name="password_reset_done"), - url( - r"^password/reset/complete/$", + path("password/reset/done/", PasswordResetDoneView.as_view(), name="password_reset_done"), + path( + "password/reset/complete/", PasswordResetCompleteView.as_view(), name="password_reset_complete", ), - url( - r"^password/reset/confirm/(?P[0-9A-Za-z]+)-(?P.+)/$", + path( + "password/reset/confirm/-/", PasswordResetConfirmView.as_view(), name="password_reset_confirm", ), - url("", include("django.contrib.auth.urls")), + path("", include("django.contrib.auth.urls")), ] diff --git a/pykeg/web/kegadmin/urls.py b/pykeg/web/kegadmin/urls.py index 61fa2505..7bef86dd 100644 --- a/pykeg/web/kegadmin/urls.py +++ b/pykeg/web/kegadmin/urls.py @@ -1,62 +1,62 @@ -from django.conf.urls import url +from django.urls import path from pykeg.plugin import util from pykeg.web.kegadmin import views urlpatterns = [ # main page - url(r"^$", views.dashboard, name="kegadmin-dashboard"), - url(r"^settings/general/$", views.general_settings, name="kegadmin-main"), - url(r"^settings/location/$", views.location_settings, name="kegadmin-location-settings"), - url(r"^settings/advanced/$", views.advanced_settings, name="kegadmin-advanced-settings"), - url(r"^bugreport/$", views.bugreport, name="kegadmin-bugreport"), - url(r"^export/$", views.export, name="kegadmin-export"), - url(r"^beers/$", views.beverages_list, name="kegadmin-beverages"), - url(r"^beers/add/$", views.beverage_add, name="kegadmin-add-beverage"), - url(r"^beers/(?P\d+)/$", views.beverage_detail, name="kegadmin-edit-beverage"), - url(r"^devices/link/$", views.link_device, name="kegadmin-link-device"), - url(r"^kegs/$", views.keg_list, name="kegadmin-kegs"), - url(r"^kegs/online/$", views.keg_list_online, name="kegadmin-kegs-online"), - url(r"^kegs/available/$", views.keg_list_available, name="kegadmin-kegs-available"), - url(r"^kegs/kicked/$", views.keg_list_kicked, name="kegadmin-kegs-kicked"), - url(r"^kegs/add/$", views.keg_add, name="kegadmin-add-keg"), - url(r"^kegs/(?P\d+)/$", views.keg_detail, name="kegadmin-edit-keg"), - url(r"^brewers/$", views.beverage_producer_list, name="kegadmin-beverage-producers"), - url(r"^brewers/add/$", views.beverage_producer_add, name="kegadmin-add-beverage-producer"), - url( - r"^brewers/(?P\d+)/$", + path(r"", views.dashboard, name="kegadmin-dashboard"), + path("settings/general/", views.general_settings, name="kegadmin-main"), + path("settings/location/", views.location_settings, name="kegadmin-location-settings"), + path("settings/advanced/", views.advanced_settings, name="kegadmin-advanced-settings"), + path("bugreport/", views.bugreport, name="kegadmin-bugreport"), + path("export/", views.export, name="kegadmin-export"), + path("beers/", views.beverages_list, name="kegadmin-beverages"), + path("beers/add/", views.beverage_add, name="kegadmin-add-beverage"), + path("beers//", views.beverage_detail, name="kegadmin-edit-beverage"), + path("devices/link/", views.link_device, name="kegadmin-link-device"), + path("kegs/", views.keg_list, name="kegadmin-kegs"), + path("kegs/online/", views.keg_list_online, name="kegadmin-kegs-online"), + path("kegs/available/", views.keg_list_available, name="kegadmin-kegs-available"), + path("kegs/kicked/", views.keg_list_kicked, name="kegadmin-kegs-kicked"), + path("kegs/add/", views.keg_add, name="kegadmin-add-keg"), + path("kegs//", views.keg_detail, name="kegadmin-edit-keg"), + path("brewers/", views.beverage_producer_list, name="kegadmin-beverage-producers"), + path("brewers/add/", views.beverage_producer_add, name="kegadmin-add-beverage-producer"), + path( + "brewers//", views.beverage_producer_detail, name="kegadmin-edit-beverage-producer", ), - url(r"^controllers/$", views.controller_list, name="kegadmin-controllers"), - url(r"^controllers/create/$", views.add_controller, name="kegadmin-add-controller"), - url( - r"^controllers/(?P\d+)/$", + path("controllers/", views.controller_list, name="kegadmin-controllers"), + path("controllers/create/", views.add_controller, name="kegadmin-add-controller"), + path( + "controllers//", views.controller_detail, name="kegadmin-edit-controller", ), - url(r"^taps/$", views.tap_list, name="kegadmin-taps"), - url(r"^taps/create/$", views.add_tap, name="kegadmin-add-tap"), - url(r"^taps/(?P\d+)/$", views.tap_detail, name="kegadmin-edit-tap"), - url(r"^users/$", views.user_list, name="kegadmin-users"), - url(r"^users/(?P\d+)/$", views.user_detail, name="kegadmin-edit-user"), - url(r"^drinks/$", views.drink_list, name="kegadmin-drinks"), - url(r"^drinks/(?P\d+)/$", views.drink_edit, name="kegadmin-edit-drink"), - url(r"^tokens/$", views.token_list, name="kegadmin-tokens"), - url(r"^tokens/create/$", views.add_token, name="kegadmin-add-token"), - url(r"^tokens/(?P\d+)/$", views.token_detail, name="kegadmin-edit-token"), - url( - r"^autocomplete/beverage/$", + path("taps/", views.tap_list, name="kegadmin-taps"), + path("taps/create/", views.add_tap, name="kegadmin-add-tap"), + path("taps//", views.tap_detail, name="kegadmin-edit-tap"), + path("users/", views.user_list, name="kegadmin-users"), + path("users//", views.user_detail, name="kegadmin-edit-user"), + path("drinks/", views.drink_list, name="kegadmin-drinks"), + path("drinks//", views.drink_edit, name="kegadmin-edit-drink"), + path("tokens/", views.token_list, name="kegadmin-tokens"), + path("tokens/create/", views.add_token, name="kegadmin-add-token"), + path("tokens//", views.token_detail, name="kegadmin-edit-token"), + path( + "autocomplete/beverage/", views.autocomplete_beverage, name="kegadmin-autocomplete-beverage", ), - url(r"^autocomplete/user/$", views.autocomplete_user, name="kegadmin-autocomplete-user"), - url(r"^autocomplete/token/$", views.autocomplete_token, name="kegadmin-autocomplete-token"), - url(r"^plugin/(?P\w+)/$", views.plugin_settings, name="kegadmin-plugin-settings"), - url(r"^email/$", views.email, name="kegadmin-email"), - url(r"^logs/$", views.logs, name="kegadmin-logs"), - url(r"^users/create/$", views.add_user, name="kegadmin-add-user"), - url(r"^system/$", views.system_status, name="kegadmin-system-status"), + path("autocomplete/user/", views.autocomplete_user, name="kegadmin-autocomplete-user"), + path("autocomplete/token/", views.autocomplete_token, name="kegadmin-autocomplete-token"), + path("plugin//", views.plugin_settings, name="kegadmin-plugin-settings"), + path("email/", views.email, name="kegadmin-email"), + path("logs/", views.logs, name="kegadmin-logs"), + path("users/create/", views.add_user, name="kegadmin-add-user"), + path("system/", views.system_status, name="kegadmin-system-status"), ] if util.get_plugins(): diff --git a/pykeg/web/kegweb/urls.py b/pykeg/web/kegweb/urls.py index 25004bb3..d60bdfd7 100644 --- a/pykeg/web/kegweb/urls.py +++ b/pykeg/web/kegweb/urls.py @@ -1,50 +1,50 @@ -from django.conf.urls import url +from django.urls import path from . import views urlpatterns = [ # main page - url(r"^$", views.index, name="kb-home"), + path("", views.index, name="kb-home"), # stats - url(r"^stats/$", views.system_stats, name="kb-stats"), + path("stats/", views.system_stats, name="kb-stats"), # kegs - url(r"^kegs/$", views.KegListView.as_view(), name="kb-kegs"), - url(r"^kegs/(?P\d+)/?$", views.keg_detail, name="kb-keg"), - url(r"^kegs/(?P\d+)/sessions/?$", views.keg_sessions, name="kb-keg-sessions"), + path("kegs/", views.KegListView.as_view(), name="kb-kegs"), + path("kegs//", views.keg_detail, name="kb-keg"), + path("kegs//sessions/", views.keg_sessions, name="kb-keg-sessions"), # fullscreen mode - url(r"^fullscreen/?$", views.fullscreen, name="kb-fullscreen"), + path("fullscreen/", views.fullscreen, name="kb-fullscreen"), # drinkers - url(r"^drinkers/(?P[\w@\.+\-_]+)/?$", views.user_detail, name="kb-drinker"), - url( - r"^drinkers/(?P[\w@\.+\-_]+)/sessions/?$", + path("drinkers//", views.user_detail, name="kb-drinker"), + path( + "drinkers//sessions/", views.drinker_sessions, name="kb-drinker-sessions", ), # drinks - url(r"^drinks/(?P\d+)/?$", views.drink_detail, name="kb-drink"), - url(r"^drink/(?P\d+)/?$", views.short_drink_detail), - url(r"^d/(?P\d+)/?$", views.short_drink_detail, name="kb-drink-short"), + path("drinks//", views.drink_detail, name="kb-drink"), + path("drink//", views.short_drink_detail), + path("d//", views.short_drink_detail, name="kb-drink-short"), # sessions - url(r"^session/(?P\d+)/?$", views.short_session_detail), - url(r"^s/(?P\d+)/?$", views.short_session_detail, name="kb-session-short"), - url(r"^sessions/$", views.SessionArchiveIndexView.as_view(), name="kb-sessions"), - url( - r"^sessions/(?P\d{4})/$", + path("session//", views.short_session_detail), + path("s//", views.short_session_detail, name="kb-session-short"), + path("sessions/", views.SessionArchiveIndexView.as_view(), name="kb-sessions"), + path( + "sessions//", views.SessionYearArchiveView.as_view(), name="kb-sessions-year", ), - url( - r"^sessions/(?P\d{4})/(?P\d+)/$", + path( + "sessions///", views.SessionMonthArchiveView.as_view(month_format="%m"), name="kb-sessions-month", ), - url( - r"^sessions/(?P\d{4})/(?P\d+)/(?P\d+)/$", + path( + "sessions////", views.SessionDayArchiveView.as_view(month_format="%m"), name="kb-sessions-day", ), - url( - r"^sessions/(?P\d+)/(?P\d+)/(?P\d+)/(?P\d+)/?$", + path( + "sessions/////", views.SessionDateDetailView.as_view(month_format="%m"), name="kb-session-detail", ), diff --git a/pykeg/web/setup_wizard/urls.py b/pykeg/web/setup_wizard/urls.py index 134ac80f..03c3d488 100644 --- a/pykeg/web/setup_wizard/urls.py +++ b/pykeg/web/setup_wizard/urls.py @@ -1,13 +1,13 @@ -from django.conf.urls import url +from django.urls import path from pykeg.web.setup_wizard import views urlpatterns = [ - url(r"^$", views.start, name="setup_wizard_start"), - url(r"^upgrade/$", views.upgrade, name="setup_upgrade"), - url(r"^mode/$", views.mode, name="setup_mode"), - url(r"^setup-accounts/$", views.setup_accounts, name="setup_accounts"), - url(r"^settings/$", views.site_settings, name="setup_site_settings"), - url(r"^admin-user/$", views.admin, name="setup_admin"), - url(r"^finished/$", views.finish, name="setup_finish"), + path("", views.start, name="setup_wizard_start"), + path("upgrade/", views.upgrade, name="setup_upgrade"), + path("mode/", views.mode, name="setup_mode"), + path("setup-accounts/", views.setup_accounts, name="setup_accounts"), + path("settings/", views.site_settings, name="setup_site_settings"), + path("admin-user/", views.admin, name="setup_admin"), + path("finished/", views.finish, name="setup_finish"), ] diff --git a/pykeg/web/urls.py b/pykeg/web/urls.py index fcc68372..33351c7c 100644 --- a/pykeg/web/urls.py +++ b/pykeg/web/urls.py @@ -1,5 +1,4 @@ from django.conf import settings -from django.conf.urls import url from django.conf.urls.static import static from django.contrib import admin from django.urls import include, path, re_path @@ -18,7 +17,7 @@ path("accounts/", include(kbregistration_urls)), path("kegadmin/", include(kegadmin_urls)), # Shortcuts - url(r"^link/?$", RedirectView.as_view(pattern_name="kegadmin-link-device")), + path("link", RedirectView.as_view(pattern_name="kegadmin-link-device")), ] if "pykeg.web.setup_wizard" in settings.INSTALLED_APPS: From 39bf31d0df1c15712d1e3a20f4d712378f2b7fe8 Mon Sep 17 00:00:00 2001 From: mike wakerly Date: Thu, 14 Jul 2022 15:55:58 +0100 Subject: [PATCH 5/7] chore: fix py3 warnings --- pykeg/backend/backends_test.py | 8 +++---- pykeg/backend/signals.py | 22 ++++++++++---------- pykeg/core/fields.py | 2 +- pykeg/core/management/commands/upgrade.py | 6 +++--- pykeg/core/models.py | 6 +++--- pykeg/core/util.py | 10 ++++----- pykeg/core/util_test.py | 10 ++++----- pykeg/web/kbregistration/forms.py | 2 +- pykeg/web/setup_wizard/setup_wizard_tests.py | 6 +++--- 9 files changed, 36 insertions(+), 36 deletions(-) diff --git a/pykeg/backend/backends_test.py b/pykeg/backend/backends_test.py index 201b6892..31aa1ec3 100644 --- a/pykeg/backend/backends_test.py +++ b/pykeg/backend/backends_test.py @@ -222,7 +222,7 @@ def test_keg_management(self): new_keg = self.backend.start_keg(METER_NAME, beverage=beverage) tap = models.KegTap.get_from_meter_name(METER_NAME) self.assertIsNotNone(new_keg) - self.assertNotEquals(new_keg, keg) + self.assertNotEqual(new_keg, keg) self.assertTrue(tap.is_active()) self.assertTrue(new_keg.is_on_tap()) @@ -240,7 +240,7 @@ def test_keg_management(self): ) self.assertEqual(new_keg_2.type.producer, keg.type.producer) self.assertEqual(new_keg_2.type.style, keg.type.style) - self.assertNotEquals(new_keg_2.type, keg.type) + self.assertNotEqual(new_keg_2.type, keg.type) # New brewer, identical beer name == new beer type. tap = models.KegTap.get_from_meter_name(METER_NAME) @@ -252,9 +252,9 @@ def test_keg_management(self): producer_name="Other Brewer", style_name=FAKE_BEER_STYLE, ) - self.assertNotEquals(new_keg_3.type.producer, keg.type.producer) + self.assertNotEqual(new_keg_3.type.producer, keg.type.producer) self.assertEqual(new_keg_3.type.name, keg.type.name) - self.assertNotEquals(new_keg_3.type, keg.type) + self.assertNotEqual(new_keg_3.type, keg.type) def test_meters(self): tap = models.KegTap.objects.all()[0] diff --git a/pykeg/backend/signals.py b/pykeg/backend/signals.py index 3207f7dd..e23961ea 100644 --- a/pykeg/backend/signals.py +++ b/pykeg/backend/signals.py @@ -2,24 +2,24 @@ from django.dispatch import Signal -user_created = Signal(providing_args=["user"]) +user_created = Signal() -tap_created = Signal(providing_args=["tap"]) +tap_created = Signal() -auth_token_created = Signal(providing_args=["token"]) +auth_token_created = Signal() -drink_recorded = Signal(providing_args=["drink"]) +drink_recorded = Signal() -drink_canceled = Signal(providing_args=["drink"]) +drink_canceled = Signal() -drink_assigned = Signal(providing_args=["drink", "previous_user"]) +drink_assigned = Signal() -drink_adjusted = Signal(providing_args=["drink", "previous_volume"]) +drink_adjusted = Signal() -temperature_recorded = Signal(providing_args=["record"]) +temperature_recorded = Signal() -keg_created = Signal(providing_args=["keg"]) +keg_created = Signal() -keg_attached = Signal(providing_args=["keg", "tap"]) +keg_attached = Signal() -keg_ended = Signal(providing_args=["keg"]) +keg_ended = Signal() diff --git a/pykeg/core/fields.py b/pykeg/core/fields.py index 221c161e..3af57824 100644 --- a/pykeg/core/fields.py +++ b/pykeg/core/fields.py @@ -1,5 +1,5 @@ from django.db import models -from django.utils.translation import ugettext as _ +from django.utils.translation import gettext as _ # CountryField # Source: http://www.djangosnippets.org/snippets/1281/ diff --git a/pykeg/core/management/commands/upgrade.py b/pykeg/core/management/commands/upgrade.py index e6d06f53..b6bf483c 100644 --- a/pykeg/core/management/commands/upgrade.py +++ b/pykeg/core/management/commands/upgrade.py @@ -1,10 +1,10 @@ import sys from builtins import str -from distutils.version import StrictVersion from django.contrib.staticfiles.management.commands import collectstatic from django.core.management.base import BaseCommand from django.core.management.commands import migrate +from packaging.version import Version from pykeg.core import models from pykeg.core.management.commands import regen_stats @@ -14,7 +14,7 @@ # v0.9.35 - migrations rebased to 0001 # v1.1.1 - last release with South-based migrations # v1.2.0 - first Django 1.7 migrations -MINIMUM_INSTALLED_VERSION = StrictVersion("1.1.1") +MINIMUM_INSTALLED_VERSION = Version("1.1.1") def run(cmd, args=[]): @@ -104,5 +104,5 @@ def handle(self, *args, **options): print("Upgrade complete!") def do_version_upgrades(self, installed_version): - if installed_version.version < (1, 2, 0): + if installed_version.release < (1, 2, 0): print("Upgrading from v1.1.x") diff --git a/pykeg/core/models.py b/pykeg/core/models.py index 1aef7324..3a794127 100644 --- a/pykeg/core/models.py +++ b/pykeg/core/models.py @@ -8,7 +8,6 @@ import re import urllib.parse from builtins import object, str -from distutils.version import StrictVersion from uuid import uuid4 import pytz @@ -24,9 +23,10 @@ from django.db.models.signals import post_save, pre_save from django.urls import reverse, reverse_lazy from django.utils import timezone -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from imagekit.models import ImageSpecField from imagekit.processors import Adjust, resize +from packaging.version import Version from pykeg.backend import get_kegbot_backend from pykeg.core import colors, fields, kb_common, keg_sizes, managers @@ -337,7 +337,7 @@ def get_installed_version(cls): rows = cls.objects.filter(name="default").values("server_version") if not rows: return None - return StrictVersion(rows[0].get("server_version", "0.0.0")) + return Version(rows[0].get("server_version", "0.0.0")) def get_stats(self): return Stats.get_latest_for_view() diff --git a/pykeg/core/util.py b/pykeg/core/util.py index d11e3cd6..d57bcd0a 100644 --- a/pykeg/core/util.py +++ b/pykeg/core/util.py @@ -6,17 +6,17 @@ import logging import os import pkgutil -import sys import tempfile from builtins import object, str from collections import OrderedDict from contextlib import closing -from distutils.version import StrictVersion from importlib import metadata as importlib_metadata +from importlib.metadata import version from threading import current_thread import requests from django.core.exceptions import ImproperlyConfigured +from packaging.version import Version from redis.exceptions import RedisError logger = logging.getLogger(__name__) @@ -28,18 +28,18 @@ def get_version(): try: - return importlib_metadata.version("kegbot") + return version("kegbot") except importlib_metadata.PackageNotFoundError: return "0.0.0" def get_version_object(): - return StrictVersion(get_version()) + return Version(get_version()) def must_upgrade(installed_version, new_version): # Compare major and minor (only). - return installed_version.version[:2] < new_version.version[:2] + return installed_version.release[:2] < new_version.release[:2] def should_upgrade(installed_verison, new_version): diff --git a/pykeg/core/util_test.py b/pykeg/core/util_test.py index 22596577..738c2517 100644 --- a/pykeg/core/util_test.py +++ b/pykeg/core/util_test.py @@ -1,9 +1,9 @@ """Test for util module.""" from builtins import str -from distutils.version import StrictVersion from django.test import TestCase +from packaging.version import Version from pykeg.core import util @@ -15,12 +15,12 @@ def test_get_version(self): util.get_version_object() except ValueError as e: self.fail("Illegal version: " + str(e)) - self.assertTrue(util.get_version_object().version >= (0, 9, 23)) + self.assertTrue(util.get_version_object().release >= (0, 9, 23)) def test_must_upgrade(self): - v100 = StrictVersion("1.0.0") - v101 = StrictVersion("1.0.1") - v110 = StrictVersion("1.1.0") + v100 = Version("1.0.0") + v101 = Version("1.0.1") + v110 = Version("1.1.0") self.assertTrue(util.should_upgrade(v100, v101)) self.assertFalse(util.should_upgrade(v100, v100)) diff --git a/pykeg/web/kbregistration/forms.py b/pykeg/web/kbregistration/forms.py index 82d35922..cc6d7346 100644 --- a/pykeg/web/kbregistration/forms.py +++ b/pykeg/web/kbregistration/forms.py @@ -7,7 +7,7 @@ from django.template import loader from django.utils.encoding import force_bytes from django.utils.http import urlsafe_base64_encode -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ try: from django.contrib.auth import get_user_model diff --git a/pykeg/web/setup_wizard/setup_wizard_tests.py b/pykeg/web/setup_wizard/setup_wizard_tests.py index 2eea4e15..a541eaa0 100644 --- a/pykeg/web/setup_wizard/setup_wizard_tests.py +++ b/pykeg/web/setup_wizard/setup_wizard_tests.py @@ -19,7 +19,7 @@ def test_settings_debug_false(self): self.assertNotContains(response, "Start Setup", status_code=403) response = self.client.get("/setup/") - self.failUnlessEqual(response.status_code, 404) + self.assertEqual(response.status_code, 404) @override_settings(DEBUG=True) def test_settings_debug_true(self): @@ -30,7 +30,7 @@ def test_settings_debug_true(self): self.assertContains(response, "Start Setup", status_code=403) response = self.client.get("/setup/") - self.failUnlessEqual(response.status_code, 200) + self.assertEqual(response.status_code, 200) def test_setup_not_shown(self): """Verify wizard is not shown on set-up site.""" @@ -44,4 +44,4 @@ def test_setup_not_shown(self): self.assertNotContains(response, "Start Setup", status_code=200) response = self.client.get("/setup/") - self.failUnlessEqual(response.status_code, 404) + self.assertEqual(response.status_code, 404) From 6519329599958ff8e622681a7429784270cd8840 Mon Sep 17 00:00:00 2001 From: mike wakerly Date: Thu, 14 Jul 2022 17:12:28 +0100 Subject: [PATCH 6/7] chore: revise trailing slash handling Prior url definitions installed routes with a `r".../?$"` path regex, meaning the same view would be served regardless of trailing slash presence. With the previous change, we use `path(..)` in most places, which provides a convenient shorthand for declaring path parameters (`` etc). However we can no longer specify a regex in that shorthand. This creates a new problem: Routes that formerly got a direct response, for example `GET /api/events/`, now get a 404. This can break existing API clients, of which I'm not sure how many there are, nor do I know their slash-or-not behaviors. To get around this we install a new middleware, `PathRewriteMiddleware`, which rewrites the internal path -- without redirecting -- when a trailing slash is found. We only activate this for `/api/` routes since browsers will deal with 301s as needed just fine. --- pykeg/backend/backends_test.py | 8 +- pykeg/notification/backends/email_test.py | 16 ++-- pykeg/settings.py | 2 + pykeg/web/api/urls.py | 112 +++++++++++----------- pykeg/web/kegweb/kegweb_test.py | 11 ++- pykeg/web/middleware.py | 48 +++++++++- 6 files changed, 119 insertions(+), 78 deletions(-) diff --git a/pykeg/backend/backends_test.py b/pykeg/backend/backends_test.py index 31aa1ec3..63d0db74 100644 --- a/pykeg/backend/backends_test.py +++ b/pykeg/backend/backends_test.py @@ -289,17 +289,17 @@ def test_urls(self): producer_name=FAKE_BREWER_NAME, style_name=FAKE_BEER_STYLE, ) - self.assertEqual("http://example.com:8000/kegs/{}".format(keg.id), keg.full_url()) + self.assertEqual("http://example.com:8000/kegs/{}/".format(keg.id), keg.full_url()) drink = self.backend.record_drink(METER_NAME, ticks=1, volume_ml=100, photo="foo") - self.assertEqual("http://example.com:8000/d/{}".format(drink.id), drink.short_url()) + self.assertEqual("http://example.com:8000/d/{}/".format(drink.id), drink.short_url()) self.assertEqual( - "http://example.com:8000/s/{}".format(drink.session.id), drink.session.short_url() + "http://example.com:8000/s/{}/".format(drink.session.id), drink.session.short_url() ) start = drink.session.start_time datepart = "{}/{}/{}".format(start.year, start.month, start.day) self.assertEqual( - "http://example.com:8000/sessions/{}/{}".format(datepart, drink.session.id), + "http://example.com:8000/sessions/{}/{}/".format(datepart, drink.session.id), drink.session.full_url(), ) diff --git a/pykeg/notification/backends/email_test.py b/pykeg/notification/backends/email_test.py index 86e69497..ea3df126 100644 --- a/pykeg/notification/backends/email_test.py +++ b/pykeg/notification/backends/email_test.py @@ -50,7 +50,7 @@ def test_keg_tapped(self): expected_body_plain = """A new keg of Unknown by Unknown was just tapped on My Kegbot! -Track it here: http://localhost:1234/kegs/%s +Track it here: http://localhost:1234/kegs/%s/ You are receiving this e-mail because you have notifications enabled on My Kegbot. To change your settings, visit http://localhost:1234/account.""" % ( @@ -65,7 +65,7 @@ def test_keg_tapped(self):

-Track it here. +Track it here.

@@ -104,7 +104,7 @@ def test_session_started(self): expected_body_plain = """A new session was just kicked off on My Kegbot. -You can follow the session here: http://localhost:1234/s/%s +You can follow the session here: http://localhost:1234/s/%s/ You are receiving this e-mail because you have notifications enabled on My Kegbot. To change your settings, visit http://localhost:1234/account.""" % ( @@ -118,7 +118,7 @@ def test_session_started(self):

-You can follow the session here. +You can follow the session here.

@@ -161,7 +161,7 @@ def test_keg_volume_low(self): expected_body_plain = """Keg %s (Unknown by Unknown) is 15.0%% full. -See full statistics here: http://localhost:1234/kegs/%s +See full statistics here: http://localhost:1234/kegs/%s/ You are receiving this e-mail because you have notifications enabled on My Kegbot. To change your settings, visit http://localhost:1234/account.""" % ( @@ -176,7 +176,7 @@ def test_keg_volume_low(self):

-See full statistics here. +See full statistics here.

@@ -215,7 +215,7 @@ def test_keg_ended(self): expected_body_plain = """Keg %s of Unknown by Unknown was just finished on My Kegbot. -See final statistics here: http://localhost:1234/kegs/%s +See final statistics here: http://localhost:1234/kegs/%s/ You are receiving this e-mail because you have notifications enabled on My Kegbot. To change your settings, visit http://localhost:1234/account.""" % ( @@ -231,7 +231,7 @@ def test_keg_ended(self):

-See final statistics here. +See final statistics here.

diff --git a/pykeg/settings.py b/pykeg/settings.py index df2697b0..0c077326 100644 --- a/pykeg/settings.py +++ b/pykeg/settings.py @@ -124,6 +124,8 @@ "django.middleware.security.SecurityMiddleware", "whitenoise.middleware.WhiteNoiseMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", + "pykeg.web.middleware.ErrorLoggingMiddleware", + "pykeg.web.middleware.PathRewriteMiddleware", "pykeg.web.middleware.IsSetupMiddleware", "pykeg.web.middleware.CurrentRequestMiddleware", "pykeg.web.middleware.KegbotSiteMiddleware", diff --git a/pykeg/web/api/urls.py b/pykeg/web/api/urls.py index 165a493d..b56bbaf3 100644 --- a/pykeg/web/api/urls.py +++ b/pykeg/web/api/urls.py @@ -4,70 +4,70 @@ urlpatterns = [ # General endpoints - path("status/", views.get_status), - path("version/", views.get_version), + path("status", views.get_status), + path("version", views.get_version), # API authorization - path("login/", views.login), - path("logout/", views.logout), - path("get-api-key/", views.get_api_key), - path("devices/link/", views.link_device_new), - path("devices/link/status/", views.link_device_status), + path("login", views.login), + path("logout", views.logout), + path("get-api-key", views.get_api_key), + path("devices/link", views.link_device_new), + path("devices/link/status", views.link_device_status), path("devices/link/status/", views.link_device_status), # Kegbot objects - path("auth-tokens///", views.get_auth_token), + path("auth-tokens//", views.get_auth_token), path( - "auth-tokens///assign/", + "auth-tokens///assign", views.assign_auth_token, ), - path("controllers/", views.all_controllers), + path("controllers", views.all_controllers), path("controllers/", views.get_controller), - path("drinks/", views.all_drinks), - path("drinks/last/", views.last_drink), - path("drinks//", views.get_drink), - path("drinks//add-photo/", views.add_drink_photo), - path("cancel-drink/", views.cancel_drink), - path("events/", views.all_events), - path("flow-meters/", views.all_flow_meters), - path("flow-meters//", views.get_flow_meter), - path("flow-toggles/", views.all_flow_toggles), - path("flow-toggles//", views.get_flow_toggle), - path("kegs/", views.all_kegs), - path("kegs//", views.get_keg), - path("kegs//end/", views.end_keg), - path("kegs//drinks/", views.get_keg_drinks), - path("kegs//events/", views.get_keg_events), - path("kegs//sessions/", views.get_keg_sessions), - path("kegs//stats/", views.get_keg_stats), - path("keg-sizes/", views.get_keg_sizes), - path("pictures/", views.pictures), - path("sessions/", views.all_sessions), - path("sessions/current/", views.current_session), - path("sessions//", views.get_session), - path("sessions//stats/", views.get_session_stats), - path("taps/", views.all_taps), - path("taps//activate/", views.tap_activate), - path("taps//calibrate/", views.tap_calibrate), - path("taps//spill/", views.tap_spill), - path("taps//connect-meter/", views.tap_connect_meter), - path("taps//disconnect-meter/", views.tap_disconnect_meter), - path("taps//connect-toggle/", views.tap_connect_toggle), - path("taps//disconnect-toggle/", views.tap_disconnect_toggle), - path("taps//", views.tap_detail), - path("thermo-sensors/", views.all_thermo_sensors), - path("thermo-sensors//", views.get_thermo_sensor), - path("thermo-sensors//logs/", views.get_thermo_sensor_logs), - path("taps//connect-thermo/", views.tap_connect_thermo), - path("taps//disconnect-thermo/", views.tap_disconnect_thermo), - path("users/", views.user_list), - path("users//drinks/", views.get_user_drinks), - path("users//events/", views.get_user_events), - path("users//stats/", views.get_user_stats), - path("users//photo/", views.user_photo), - path("users//", views.get_user), - path("new-user/", views.register), - path("stats/", views.get_system_stats), + path("drinks", views.all_drinks), + path("drinks/last", views.last_drink), + path("drinks/", views.get_drink), + path("drinks//add-photo", views.add_drink_photo), + path("cancel-drink", views.cancel_drink), + path("events", views.all_events), + path("flow-meters", views.all_flow_meters), + path("flow-meters/", views.get_flow_meter), + path("flow-toggles", views.all_flow_toggles), + path("flow-toggles/", views.get_flow_toggle), + path("kegs", views.all_kegs), + path("kegs/", views.get_keg), + path("kegs//end", views.end_keg), + path("kegs//drinks", views.get_keg_drinks), + path("kegs//events", views.get_keg_events), + path("kegs//sessions", views.get_keg_sessions), + path("kegs//stats", views.get_keg_stats), + path("keg-sizes", views.get_keg_sizes), + path("pictures", views.pictures), + path("sessions", views.all_sessions), + path("sessions/current", views.current_session), + path("sessions/", views.get_session), + path("sessions//stats", views.get_session_stats), + path("taps", views.all_taps), + path("taps//activate", views.tap_activate), + path("taps//calibrate", views.tap_calibrate), + path("taps//spill", views.tap_spill), + path("taps//connect-meter", views.tap_connect_meter), + path("taps//disconnect-meter", views.tap_disconnect_meter), + path("taps//connect-toggle", views.tap_connect_toggle), + path("taps//disconnect-toggle", views.tap_disconnect_toggle), + path("taps/", views.tap_detail), + path("thermo-sensors", views.all_thermo_sensors), + path("thermo-sensors/", views.get_thermo_sensor), + path("thermo-sensors//logs", views.get_thermo_sensor_logs), + path("taps//connect-thermo", views.tap_connect_thermo), + path("taps//disconnect-thermo", views.tap_disconnect_thermo), + path("users", views.user_list), + path("users//drinks", views.get_user_drinks), + path("users//events", views.get_user_events), + path("users//stats", views.get_user_stats), + path("users//photo", views.user_photo), + path("users/", views.get_user), + path("new-user", views.register), + path("stats", views.get_system_stats), # Deprecated endpoints - path("sound-events/", views.all_sound_events), + path("sound-events", views.all_sound_events), # Catch-all path(r"", views.default_handler), ] diff --git a/pykeg/web/kegweb/kegweb_test.py b/pykeg/web/kegweb/kegweb_test.py index 46ef581a..fde6e423 100644 --- a/pykeg/web/kegweb/kegweb_test.py +++ b/pykeg/web/kegweb/kegweb_test.py @@ -40,7 +40,7 @@ def testBasicEndpoints(self): drink_id = d.id response = self.client.get("/d/%s" % drink_id, follow=True) - self.assertRedirects(response, "/drinks/%s" % drink_id, status_code=301) + self.assertRedirects(response, "/drinks/%s/" % drink_id, status_code=301) session_id = d.session.id response = self.client.get("/s/%s" % session_id, follow=True) @@ -76,8 +76,8 @@ def test_privacy(self): "/kegs/": "Keg List", "/stats/": "System Stats", "/sessions/": "All Sessions", - "/kegs/{}".format(keg.id): "Keg {}".format(keg.id), - "/drinks/{}".format(d.id): "Drink {}".format(d.id), + "/kegs/{}/".format(keg.id): "Keg {}".format(keg.id), + "/drinks/{}/".format(d.id): "Drink {}".format(d.id), } def test_urls(expect_fail, urls=urls): @@ -234,8 +234,9 @@ def test_registration(self): }, follow=False, ) - print(response) - self.assertContains(response, "The two password fields didn't match.", status_code=200) + self.assertContains( + response, "The two password fields didn't match.", status_code=200, html=True + ) @override_settings(EMAIL_BACKEND="django.core.mail.backends.locmem.EmailBackend") @override_settings(DEFAULT_FROM_EMAIL="test-from@example") diff --git a/pykeg/web/middleware.py b/pykeg/web/middleware.py index 8a571aef..f5eb7239 100644 --- a/pykeg/web/middleware.py +++ b/pykeg/web/middleware.py @@ -1,5 +1,4 @@ import logging -from builtins import object, str from django.conf import settings from django.http import HttpResponse @@ -37,7 +36,7 @@ def _path_allowed(path, kbsite): return False -class CurrentRequestMiddleware(object): +class CurrentRequestMiddleware: """Set/clear the current request.""" def __init__(self, get_response): @@ -52,7 +51,46 @@ def __call__(self, request): return response -class IsSetupMiddleware(object): +class ErrorLoggingMiddleware: + """Log uncaught exceptions to python logging.""" + + logger = logging.getLogger(__name__) + + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + try: + response = self.get_response(request) + return response + except: + self.logger.exception("Server error") + raise + + +class PathRewriteMiddleware: + """Rewrites `request.path` to ignore trailing slashes for /api/ requests. + + Earlier versions of kegbot-server tolerated an optional trailing slash. + We don't want to define every API url as an `re_path(r".../?")`. + + Rewrite `request.{path,path_info}` so that onward request handling always + sees a path as if the client presented no trailing slash. + """ + + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + if request.path.startswith("/api") and not getattr(request, "path_rewritten", None): + if request.path.endswith("/"): + request.path = request.path[:-1] + request.path_info = request.path_info[:-1] + request.path_rewritten = True + return self.get_response(request) + + +class IsSetupMiddleware: """Adds `.need_setup`, `.need_upgrade`, and `.kbsite` to the request.""" def __init__(self, get_response): @@ -123,7 +161,7 @@ def _upgrade_required(self, request): return render(request, "setup_wizard/upgrade_required.html", context=context, status=403) -class KegbotSiteMiddleware(object): +class KegbotSiteMiddleware: def __init__(self, get_response): self.get_response = get_response @@ -138,7 +176,7 @@ def __call__(self, request): return self.get_response(request) -class PrivacyMiddleware(object): +class PrivacyMiddleware: """Enforces site privacy settings. Must be installed after ApiRequestMiddleware (in request order) to From a86a315a196634889049c631892d2755c01d9e8c Mon Sep 17 00:00:00 2001 From: mike wakerly Date: Thu, 14 Jul 2022 17:13:11 +0100 Subject: [PATCH 7/7] tests: fix flaky api_test: make insensitive to exact row ids --- pykeg/web/api/api_test.py | 72 +++++++++++++++++++++++---------------- 1 file changed, 43 insertions(+), 29 deletions(-) diff --git a/pykeg/web/api/api_test.py b/pykeg/web/api/api_test.py index 39cfc640..4cf06a72 100644 --- a/pykeg/web/api/api_test.py +++ b/pykeg/web/api/api_test.py @@ -3,7 +3,7 @@ from builtins import str from django.core import mail -from django.test import TransactionTestCase +from django.test import TestCase from django.test.utils import override_settings from django.urls import reverse @@ -19,7 +19,7 @@ def create_site(): return defaults.set_defaults(set_is_setup=True, create_controller=True) -class BaseApiTestCase(TransactionTestCase): +class BaseApiTestCase(TestCase): def get(self, subpath, data={}, follow=False, **extra): response = self.client.get("/api/%s" % subpath, data=data, follow=follow, **extra) return response, kbjson.loads(response.content) @@ -66,6 +66,8 @@ def setUp(self): self.apikey = models.ApiKey.objects.create(user=self.admin, key="123") self.bad_apikey = models.ApiKey.objects.create(user=self.normal_user, key="456") + self.tap = models.KegTap.objects.all().first() + def test_defaults(self): empty_endpoints = ("events/", "kegs/") for endpoint in empty_endpoints: @@ -146,7 +148,7 @@ def test_api_access(self): self.assertEqual(data.meta.result, "ok") def test_record_drink(self): - response, data = self.get("taps/1") + response, data = self.get(f"taps/{self.tap.id}") self.assertEqual(data.meta.result, "ok") self.assertEqual(data.object.get("current_keg"), None) @@ -160,22 +162,22 @@ def test_record_drink(self): "producer_name": "Test Producer", "style_name": "Test Style,", } - response, data = self.post("taps/1/activate", data=new_keg_data) + response, data = self.post(f"taps/{self.tap.id}/activate", data=new_keg_data) self.assertEqual(data.meta.result, "error") self.assertEqual(data.error.code, "NoAuthTokenError") response, data = self.post( - "taps/1/activate", data=new_keg_data, HTTP_X_KEGBOT_API_KEY=self.apikey.key + f"taps/{self.tap.id}/activate", data=new_keg_data, HTTP_X_KEGBOT_API_KEY=self.apikey.key ) self.assertEqual(data.meta.result, "ok") self.assertIsNotNone(data.object.get("current_keg")) - response, data = self.post("taps/1", data={"ticks": 1000}) + response, data = self.post(f"taps/{self.tap.id}", data={"ticks": 1000}) self.assertEqual(data.meta.result, "error") self.assertEqual(data.error.code, "NoAuthTokenError") response, data = self.post( - "taps/1", + f"taps/{self.tap.id}", HTTP_X_KEGBOT_API_KEY=self.apikey.key, data={"ticks": 1000, "username": self.normal_user.username}, ) @@ -202,13 +204,13 @@ def test_record_drink_usernames(self): "style_name": "Test Style,", } response, data = self.post( - "taps/1/activate", data=new_keg_data, HTTP_X_KEGBOT_API_KEY=self.apikey.key + f"taps/{self.tap.id}/activate", data=new_keg_data, HTTP_X_KEGBOT_API_KEY=self.apikey.key ) self.assertEqual(data.meta.result, "ok") models.User.objects.create(username="test.123") response, data = self.post( - "taps/1", + f"taps/{self.tap.id}", HTTP_X_KEGBOT_API_KEY=self.apikey.key, data={"ticks": 1000, "username": "test.123"}, ) @@ -267,10 +269,11 @@ def test_controller_data(self): response, data = self.get("controllers", HTTP_X_KEGBOT_API_KEY=self.apikey.key) self.assertEqual(data.meta.result, "ok") + controllers = models.Controller.objects.all() expected = { "objects": [ { - "id": 1, + "id": controllers[0].id, "name": "kegboard", } ], @@ -282,23 +285,24 @@ def test_controller_data(self): response, data = self.get("flow-meters", HTTP_X_KEGBOT_API_KEY=self.apikey.key) self.assertEqual(data.meta.result, "ok") + meters = models.FlowMeter.objects.all() expected = { "objects": [ { - "id": 1, + "id": meters[0].id, "ticks_per_ml": 2.724, "port_name": "flow0", "controller": { - "id": 1, + "id": controllers[0].id, "name": "kegboard", }, "name": "kegboard.flow0", }, { - "id": 2, + "id": meters[1].id, "ticks_per_ml": 2.724, "port_name": "flow1", - "controller": {"id": 1, "name": "kegboard"}, + "controller": {"id": controllers[0].id, "name": "kegboard"}, "name": "kegboard.flow1", }, ], @@ -308,21 +312,22 @@ def test_controller_data(self): response, data = self.get("flow-toggles", HTTP_X_KEGBOT_API_KEY=self.apikey.key) self.assertEqual(data.meta.result, "ok") + toggles = models.FlowToggle.objects.all() expected = { "objects": [ { - "id": 1, + "id": toggles[0].id, "port_name": "relay0", "controller": { - "id": 1, + "id": controllers[0].id, "name": "kegboard", }, "name": "kegboard.relay0", }, { - "id": 2, + "id": toggles[1].id, "port_name": "relay1", - "controller": {"id": 1, "name": "kegboard"}, + "controller": {"id": controllers[0].id, "name": "kegboard"}, "name": "kegboard.relay1", }, ], @@ -331,40 +336,49 @@ def test_controller_data(self): self.assertEqual(expected, data) def test_add_remove_meters(self): - response, data = self.get("taps/1", HTTP_X_KEGBOT_API_KEY=self.apikey.key) + response, data = self.get(f"taps/{self.tap.id}", HTTP_X_KEGBOT_API_KEY=self.apikey.key) self.assertEqual(data.meta.result, "ok") - self.assertEqual(data.object.meter.id, 1) + meters = models.FlowMeter.objects.all() + self.assertEqual(data.object.meter.id, meters[0].id) original_data = data - response, data = self.post("taps/1/disconnect-meter", HTTP_X_KEGBOT_API_KEY=self.apikey.key) + response, data = self.post( + f"taps/{self.tap.id}/disconnect-meter", HTTP_X_KEGBOT_API_KEY=self.apikey.key + ) self.assertEqual(data.meta.result, "ok") self.assertEqual(data.object.get("meter"), None) response, data = self.post( - "taps/1/connect-meter", HTTP_X_KEGBOT_API_KEY=self.apikey.key, data={"meter": 1} + f"taps/{self.tap.id}/connect-meter", + HTTP_X_KEGBOT_API_KEY=self.apikey.key, + data={"meter": meters[0].id}, ) self.assertEqual(data.meta.result, "ok") - self.assertEqual(data.object.meter.id, 1) + meters = models.FlowMeter.objects.all() + self.assertEqual(data.object.meter.id, meters[0].id) self.assertEqual(original_data, data) def test_add_remove_toggles(self): - response, data = self.get("taps/1", HTTP_X_KEGBOT_API_KEY=self.apikey.key) + response, data = self.get(f"taps/{self.tap.id}", HTTP_X_KEGBOT_API_KEY=self.apikey.key) self.assertEqual(data.meta.result, "ok") - self.assertEqual(data.object.toggle.id, 1) + toggles = models.FlowToggle.objects.all() + self.assertEqual(data.object.toggle.id, toggles[0].id) response, data = self.post( - "taps/1/disconnect-toggle", HTTP_X_KEGBOT_API_KEY=self.apikey.key + f"taps/{self.tap.id}/disconnect-toggle", HTTP_X_KEGBOT_API_KEY=self.apikey.key ) self.assertEqual(data.meta.result, "ok") self.assertEqual(data.object.get("toggle"), None) response, data = self.post( - "taps/1/connect-toggle", HTTP_X_KEGBOT_API_KEY=self.apikey.key, data={"toggle": 1} + f"taps/{self.tap.id}/connect-toggle", + HTTP_X_KEGBOT_API_KEY=self.apikey.key, + data={"toggle": toggles[0].id}, ) - response, data = self.get("taps/1", HTTP_X_KEGBOT_API_KEY=self.apikey.key) + response, data = self.get(f"taps/{self.tap.id}", HTTP_X_KEGBOT_API_KEY=self.apikey.key) self.assertEqual(data.meta.result, "ok") self.assertIsNotNone(data.object.get("toggle")) - self.assertEqual(data.object.toggle.id, 1) + self.assertEqual(data.object.toggle.id, toggles[0].id) def test_get_version(self): response, data = self.get("version")