diff --git a/CHANGELOG.md b/CHANGELOG.md index e712db67a15f6cde24b3b5a86af6eb23e850bc33..c3d7d04e0e9ebdc3df2cb9b83c5bb1a9c7c28c49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The `last_date` field in the `Show` model is nullable. - The `CharField`s and `TextFields` in the models are not nullable. - The `subtitle` field of th `Category` model is now a `CharField`. +- Provide properties in API schemas in CamelCase notation (aura#141) +- Use _id suffix for all object reference in REST APIs (aura#166) ### Deprecated diff --git a/conftest.py b/conftest.py index 6768306045361ca3165846f277e3ae8a1b34ade0..d97fe16afb2b1cbc0e41e294958b4ed9fc016a09 100644 --- a/conftest.py +++ b/conftest.py @@ -14,17 +14,15 @@ from program.tests.factories import ( ScheduleFactory, ShowFactory, TimeslotFactory, - TypeFactory, ImageFactory, + TypeFactory, + ImageFactory, ) def assert_data(response, data) -> None: if "schedule" in data: for key, value in data["schedule"].items(): - if key == "rrule": - assert response.data[key] == value - else: - assert str(response.data[key]) == value + assert response.data[key] == value else: for key, value in data.items(): if key == "password": diff --git a/poetry.lock b/poetry.lock index 569b2508c34c89ebc3e32fab0bf56494cbbcdd2d..26742ee8c645d5c76fffe3d9e8eb0615fdbf608c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,17 +1,19 @@ -# This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.5.0 and should not be changed by hand. [[package]] name = "asgiref" -version = "3.6.0" +version = "3.7.0" description = "ASGI specs, helper code, and adapters" -category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "asgiref-3.6.0-py3-none-any.whl", hash = "sha256:71e68008da809b957b7ee4b43dbccff33d1b23519fb8344e33f049897077afac"}, - {file = "asgiref-3.6.0.tar.gz", hash = "sha256:9567dfe7bd8d3c8c892227827c41cce860b368104c3431da67a0c5a65a949506"}, + {file = "asgiref-3.7.0-py3-none-any.whl", hash = "sha256:14087924af5be5d8103d6f2edffe45a0bf7ab1b2a771b6f00a6db8c302f21f34"}, + {file = "asgiref-3.7.0.tar.gz", hash = "sha256:5d6c4a8a1c99f58eaa3bc392ee04e3587b693f09e3af1f3f16a09094f334eb52"}, ] +[package.dependencies] +typing-extensions = {version = "*", markers = "python_version < \"3.11\""} + [package.extras] tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] @@ -19,7 +21,6 @@ tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] name = "attrs" version = "23.1.0" description = "Classes Without Boilerplate" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -38,7 +39,6 @@ tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pyte name = "black" version = "22.12.0" description = "The uncompromising code formatter." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -73,7 +73,6 @@ uvloop = ["uvloop (>=0.15.2)"] name = "certifi" version = "2023.5.7" description = "Python package for providing Mozilla's CA Bundle." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -85,7 +84,6 @@ files = [ name = "cfgv" version = "3.3.1" description = "Validate configuration and produce human readable error messages." -category = "dev" optional = false python-versions = ">=3.6.1" files = [ @@ -97,7 +95,6 @@ files = [ name = "charset-normalizer" version = "3.1.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -182,7 +179,6 @@ files = [ name = "click" version = "8.1.3" description = "Composable command line interface toolkit" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -197,7 +193,6 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." -category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ @@ -209,7 +204,6 @@ files = [ name = "distlib" version = "0.3.6" description = "Distribution utilities" -category = "dev" optional = false python-versions = "*" files = [ @@ -221,7 +215,6 @@ files = [ name = "django" version = "3.2.19" description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -242,7 +235,6 @@ bcrypt = ["bcrypt"] name = "django-auth-ldap" version = "4.3.0" description = "Django LDAP authentication backend." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -258,7 +250,6 @@ python-ldap = ">=3.1" name = "django-cors-headers" version = "3.14.0" description = "django-cors-headers is a Django application for handling the server headers required for Cross-Origin Resource Sharing (CORS)." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -273,7 +264,6 @@ Django = ">=3.2" name = "django-extensions" version = "3.2.1" description = "Extensions for Django" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -288,7 +278,6 @@ Django = ">=3.2" name = "django-filter" version = "22.1" description = "Django-filter is a reusable Django application for allowing users to filter querysets dynamically." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -303,7 +292,6 @@ Django = ">=3.2" name = "django-oidc-provider" version = "0.7.0" description = "OpenID Connect Provider implementation for Django." -category = "main" optional = false python-versions = "*" files = [ @@ -317,7 +305,6 @@ pyjwkest = ">=1.3.0" name = "django-versatileimagefield" version = "2.2" description = "A drop-in replacement for django's ImageField that provides a flexible, intuitive and easily-extensible interface for creating new images from the one assigned to the field." -category = "main" optional = false python-versions = "*" files = [ @@ -333,7 +320,6 @@ python-magic = ">=0.4.15,<1.0.0" name = "djangorestframework" version = "3.14.0" description = "Web APIs for Django, made easy." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -345,11 +331,20 @@ files = [ django = ">=3.0" pytz = "*" +[[package]] +name = "djangorestframework-camel-case" +version = "1.4.2" +description = "Camel case JSON support for Django REST framework." +optional = false +python-versions = ">=3.5" +files = [ + {file = "djangorestframework-camel-case-1.4.2.tar.gz", hash = "sha256:cdae75846648abb6585c7470639a1d2fb064dc45f8e8b62aaa50be7f1a7a61f4"}, +] + [[package]] name = "drf-nested-routers" version = "0.93.4" description = "Nested resources for the Django Rest Framework" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -365,7 +360,6 @@ djangorestframework = ">=3.6.0" name = "drf-spectacular" version = "0.24.2" description = "Sane and flexible OpenAPI 3 schema generation for Django REST framework" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -389,7 +383,6 @@ sidecar = ["drf-spectacular-sidecar"] name = "exceptiongroup" version = "1.1.1" description = "Backport of PEP 654 (exception groups)" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -404,7 +397,6 @@ test = ["pytest (>=6)"] name = "factory-boy" version = "3.2.1" description = "A versatile test fixtures replacement based on thoughtbot's factory_bot for Ruby." -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -421,14 +413,13 @@ doc = ["Sphinx", "sphinx-rtd-theme", "sphinxcontrib-spelling"] [[package]] name = "faker" -version = "18.7.0" +version = "18.9.0" description = "Faker is a Python package that generates fake data for you." -category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "Faker-18.7.0-py3-none-any.whl", hash = "sha256:38dbc3b80e655d7301e190426ab30f04b6b7f6ca4764c5dd02772ffde0fa6dcd"}, - {file = "Faker-18.7.0.tar.gz", hash = "sha256:f02c6d3fdb5bc781f80b440cf2bdec336ed47ecfb8d620b20c3d4188ed051831"}, + {file = "Faker-18.9.0-py3-none-any.whl", hash = "sha256:defe9ed618a67ebf0f3eb1895e198c2355a7128a09087a6dce342ef2253263ea"}, + {file = "Faker-18.9.0.tar.gz", hash = "sha256:80a5ea1464556c06b98bf47ea3adc7f33811a1182518d847860b1874080bd3c9"}, ] [package.dependencies] @@ -438,7 +429,6 @@ python-dateutil = ">=2.4" name = "filelock" version = "3.12.0" description = "A platform independent file lock." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -454,7 +444,6 @@ testing = ["covdefaults (>=2.3)", "coverage (>=7.2.3)", "diff-cover (>=7.5)", "p name = "flake8" version = "6.0.0" description = "the modular source code checker: pep8 pyflakes and co" -category = "dev" optional = false python-versions = ">=3.8.1" files = [ @@ -471,7 +460,6 @@ pyflakes = ">=3.0.0,<3.1.0" name = "future" version = "0.18.3" description = "Clean single-source support for Python 3 and 2" -category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -482,7 +470,6 @@ files = [ name = "gunicorn" version = "20.1.0" description = "WSGI HTTP Server for UNIX" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -503,7 +490,6 @@ tornado = ["tornado (>=0.2)"] name = "identify" version = "2.5.24" description = "File identification library for Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -518,7 +504,6 @@ license = ["ukkonen"] name = "idna" version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -530,7 +515,6 @@ files = [ name = "inflection" version = "0.5.1" description = "A port of Ruby on Rails inflector to Python" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -542,7 +526,6 @@ files = [ name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -554,7 +537,6 @@ files = [ name = "isort" version = "5.12.0" description = "A Python utility / library to sort Python imports." -category = "dev" optional = false python-versions = ">=3.8.0" files = [ @@ -572,7 +554,6 @@ requirements-deprecated-finder = ["pip-api", "pipreqs"] name = "jsonschema" version = "4.17.3" description = "An implementation of JSON Schema validation for Python" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -592,7 +573,6 @@ format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339- name = "markupsafe" version = "2.1.2" description = "Safely add untrusted strings to HTML/XML markup." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -652,7 +632,6 @@ files = [ name = "mccabe" version = "0.7.0" description = "McCabe checker, plugin for flake8" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -664,7 +643,6 @@ files = [ name = "mypy-extensions" version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -674,14 +652,13 @@ files = [ [[package]] name = "nodeenv" -version = "1.7.0" +version = "1.8.0" description = "Node.js virtual environment builder" -category = "dev" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" files = [ - {file = "nodeenv-1.7.0-py2.py3-none-any.whl", hash = "sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e"}, - {file = "nodeenv-1.7.0.tar.gz", hash = "sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b"}, + {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, + {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, ] [package.dependencies] @@ -691,7 +668,6 @@ setuptools = "*" name = "packaging" version = "23.1" description = "Core utilities for Python packages" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -703,7 +679,6 @@ files = [ name = "pathspec" version = "0.11.1" description = "Utility library for gitignore style pattern matching of file paths." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -715,7 +690,6 @@ files = [ name = "pillow" version = "9.5.0" description = "Python Imaging Library (Fork)" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -793,25 +767,23 @@ tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "pa [[package]] name = "platformdirs" -version = "3.5.0" +version = "3.5.1" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "platformdirs-3.5.0-py3-none-any.whl", hash = "sha256:47692bc24c1958e8b0f13dd727307cff1db103fca36399f457da8e05f222fdc4"}, - {file = "platformdirs-3.5.0.tar.gz", hash = "sha256:7954a68d0ba23558d753f73437c55f89027cf8f5108c19844d4b82e5af396335"}, + {file = "platformdirs-3.5.1-py3-none-any.whl", hash = "sha256:e2378146f1964972c03c085bb5662ae80b2b8c06226c54b2ff4aa9483e8a13a5"}, + {file = "platformdirs-3.5.1.tar.gz", hash = "sha256:412dae91f52a6f84830f39a8078cecd0e866cb72294a5c66808e74d5e88d251f"}, ] [package.extras] -docs = ["furo (>=2023.3.27)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] +docs = ["furo (>=2023.3.27)", "proselint (>=0.13)", "sphinx (>=6.2.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] [[package]] name = "pluggy" version = "1.0.0" description = "plugin and hook calling mechanisms for python" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -827,7 +799,6 @@ testing = ["pytest", "pytest-benchmark"] name = "pre-commit" version = "2.21.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -846,7 +817,6 @@ virtualenv = ">=20.10.0" name = "psycopg2-binary" version = "2.9.6" description = "psycopg2 - Python-PostgreSQL Database Adapter" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -918,7 +888,6 @@ files = [ name = "pyasn1" version = "0.5.0" description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ @@ -930,7 +899,6 @@ files = [ name = "pyasn1-modules" version = "0.3.0" description = "A collection of ASN.1-based protocols modules" -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ @@ -945,7 +913,6 @@ pyasn1 = ">=0.4.6,<0.6.0" name = "pycodestyle" version = "2.10.0" description = "Python style guide checker" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -955,52 +922,49 @@ files = [ [[package]] name = "pycryptodomex" -version = "3.17" +version = "3.18.0" description = "Cryptographic library for Python" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ - {file = "pycryptodomex-3.17-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:12056c38e49d972f9c553a3d598425f8a1c1d35b2e4330f89d5ff1ffb70de041"}, - {file = "pycryptodomex-3.17-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ab33c2d9f275e05e235dbca1063753b5346af4a5cac34a51fa0da0d4edfb21d7"}, - {file = "pycryptodomex-3.17-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:caa937ff29d07a665dfcfd7a84f0d4207b2ebf483362fa9054041d67fdfacc20"}, - {file = "pycryptodomex-3.17-cp27-cp27m-manylinux2014_aarch64.whl", hash = "sha256:db23d7341e21b273d2440ec6faf6c8b1ca95c8894da612e165be0b89a8688340"}, - {file = "pycryptodomex-3.17-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:f854c8476512cebe6a8681cc4789e4fcff6019c17baa0fd72b459155dc605ab4"}, - {file = "pycryptodomex-3.17-cp27-cp27m-win32.whl", hash = "sha256:a57e3257bacd719769110f1f70dd901c5b6955e9596ad403af11a3e6e7e3311c"}, - {file = "pycryptodomex-3.17-cp27-cp27m-win_amd64.whl", hash = "sha256:d38ab9e53b1c09608ba2d9b8b888f1e75d6f66e2787e437adb1fecbffec6b112"}, - {file = "pycryptodomex-3.17-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:3c2516b42437ae6c7a29ef3ddc73c8d4714e7b6df995b76be4695bbe4b3b5cd2"}, - {file = "pycryptodomex-3.17-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:5c23482860302d0d9883404eaaa54b0615eefa5274f70529703e2c43cc571827"}, - {file = "pycryptodomex-3.17-cp27-cp27mu-manylinux2014_aarch64.whl", hash = "sha256:7a8dc3ee7a99aae202a4db52de5a08aa4d01831eb403c4d21da04ec2f79810db"}, - {file = "pycryptodomex-3.17-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:7cc28dd33f1f3662d6da28ead4f9891035f63f49d30267d3b41194c8778997c8"}, - {file = "pycryptodomex-3.17-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:2d4d395f109faba34067a08de36304e846c791808524614c731431ee048fe70a"}, - {file = "pycryptodomex-3.17-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:55eed98b4150a744920597c81b3965b632038781bab8a08a12ea1d004213c600"}, - {file = "pycryptodomex-3.17-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:7fa0b52df90343fafe319257b31d909be1d2e8852277fb0376ba89d26d2921db"}, - {file = "pycryptodomex-3.17-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78f0ddd4adc64baa39b416f3637aaf99f45acb0bcdc16706f0cc7ebfc6f10109"}, - {file = "pycryptodomex-3.17-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4fa037078e92c7cc49f6789a8bac3de06856740bb2038d05f2d9a2e4b165d59"}, - {file = "pycryptodomex-3.17-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:88b0d5bb87eaf2a31e8a759302b89cf30c97f2f8ca7d83b8c9208abe8acb447a"}, - {file = "pycryptodomex-3.17-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:6feedf4b0e36b395329b4186a805f60f900129cdf0170e120ecabbfcb763995d"}, - {file = "pycryptodomex-3.17-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:7a6651a07f67c28b6e978d63aa3a3fccea0feefed9a8453af3f7421a758461b7"}, - {file = "pycryptodomex-3.17-cp35-abi3-win32.whl", hash = "sha256:32e764322e902bbfac49ca1446604d2839381bbbdd5a57920c9daaf2e0b778df"}, - {file = "pycryptodomex-3.17-cp35-abi3-win_amd64.whl", hash = "sha256:4b51e826f0a04d832eda0790bbd0665d9bfe73e5a4d8ea93b6a9b38beeebe935"}, - {file = "pycryptodomex-3.17-pp27-pypy_73-macosx_10_9_x86_64.whl", hash = "sha256:d4cf0128da167562c49b0e034f09e9cedd733997354f2314837c2fa461c87bb1"}, - {file = "pycryptodomex-3.17-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:c92537b596bd5bffb82f8964cabb9fef1bca8a28a9e0a69ffd3ec92a4a7ad41b"}, - {file = "pycryptodomex-3.17-pp27-pypy_73-win32.whl", hash = "sha256:599bb4ae4bbd614ca05f49bd4e672b7a250b80b13ae1238f05fd0f09d87ed80a"}, - {file = "pycryptodomex-3.17-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4c4674f4b040321055c596aac926d12f7f6859dfe98cd12f4d9453b43ab6adc8"}, - {file = "pycryptodomex-3.17-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67a3648025e4ddb72d43addab764336ba2e670c8377dba5dd752e42285440d31"}, - {file = "pycryptodomex-3.17-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40e8a11f578bd0851b02719c862d55d3ee18d906c8b68a9c09f8c564d6bb5b92"}, - {file = "pycryptodomex-3.17-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:23d83b610bd97704f0cd3acc48d99b76a15c8c1540d8665c94d514a49905bad7"}, - {file = "pycryptodomex-3.17-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:fd29d35ac80755e5c0a99d96b44fb9abbd7e871849581ea6a4cb826d24267537"}, - {file = "pycryptodomex-3.17-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64b876d57cb894b31056ad8dd6a6ae1099b117ae07a3d39707221133490e5715"}, - {file = "pycryptodomex-3.17-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee8bf4fdcad7d66beb744957db8717afc12d176e3fd9c5d106835133881a049b"}, - {file = "pycryptodomex-3.17-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c84689c73358dfc23f9fdcff2cb9e7856e65e2ce3b5ed8ff630d4c9bdeb1867b"}, - {file = "pycryptodomex-3.17.tar.gz", hash = "sha256:0af93aad8d62e810247beedef0261c148790c52f3cd33643791cc6396dd217c1"}, + {file = "pycryptodomex-3.18.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:160a39a708c36fa0b168ab79386dede588e62aec06eb505add870739329aecc6"}, + {file = "pycryptodomex-3.18.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:c2953afebf282a444c51bf4effe751706b4d0d63d7ca2cc51db21f902aa5b84e"}, + {file = "pycryptodomex-3.18.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:ba95abd563b0d1b88401658665a260852a8e6c647026ee6a0a65589287681df8"}, + {file = "pycryptodomex-3.18.0-cp27-cp27m-manylinux2014_aarch64.whl", hash = "sha256:192306cf881fe3467dda0e174a4f47bb3a8bb24b90c9cdfbdc248eec5fc0578c"}, + {file = "pycryptodomex-3.18.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:f9ab5ef0718f6a8716695dea16d83b671b22c45e9c0c78fd807c32c0192e54b5"}, + {file = "pycryptodomex-3.18.0-cp27-cp27m-win32.whl", hash = "sha256:50308fcdbf8345e5ec224a5502b4215178bdb5e95456ead8ab1a69ffd94779cb"}, + {file = "pycryptodomex-3.18.0-cp27-cp27m-win_amd64.whl", hash = "sha256:4d9379c684efea80fdab02a3eb0169372bca7db13f9332cb67483b8dc8b67c37"}, + {file = "pycryptodomex-3.18.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:5594a125dae30d60e94f37797fc67ce3c744522de7992c7c360d02fdb34918f8"}, + {file = "pycryptodomex-3.18.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:8ff129a5a0eb5ff16e45ca4fa70a6051da7f3de303c33b259063c19be0c43d35"}, + {file = "pycryptodomex-3.18.0-cp27-cp27mu-manylinux2014_aarch64.whl", hash = "sha256:3d9314ac785a5b75d5aaf924c5f21d6ca7e8df442e5cf4f0fefad4f6e284d422"}, + {file = "pycryptodomex-3.18.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:f237278836dda412a325e9340ba2e6a84cb0f56b9244781e5b61f10b3905de88"}, + {file = "pycryptodomex-3.18.0-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:ac614363a86cc53d8ba44b6c469831d1555947e69ab3276ae8d6edc219f570f7"}, + {file = "pycryptodomex-3.18.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:302a8f37c224e7b5d72017d462a2be058e28f7be627bdd854066e16722d0fc0c"}, + {file = "pycryptodomex-3.18.0-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:6421d23d6a648e83ba2670a352bcd978542dad86829209f59d17a3f087f4afef"}, + {file = "pycryptodomex-3.18.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d84e105787f5e5d36ec6a581ff37a1048d12e638688074b2a00bcf402f9aa1c2"}, + {file = "pycryptodomex-3.18.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6875eb8666f68ddbd39097867325bd22771f595b4e2b0149739b5623c8bf899b"}, + {file = "pycryptodomex-3.18.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:27072a494ce621cc7a9096bbf60ed66826bb94db24b49b7359509e7951033e74"}, + {file = "pycryptodomex-3.18.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:1949e09ea49b09c36d11a951b16ff2a05a0ffe969dda1846e4686ee342fe8646"}, + {file = "pycryptodomex-3.18.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6ed3606832987018615f68e8ed716a7065c09a0fe94afd7c9ca1b6777f0ac6eb"}, + {file = "pycryptodomex-3.18.0-cp35-abi3-win32.whl", hash = "sha256:d56c9ec41258fd3734db9f5e4d2faeabe48644ba9ca23b18e1839b3bdf093222"}, + {file = "pycryptodomex-3.18.0-cp35-abi3-win_amd64.whl", hash = "sha256:e00a4bacb83a2627e8210cb353a2e31f04befc1155db2976e5e239dd66482278"}, + {file = "pycryptodomex-3.18.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:2dc4eab20f4f04a2d00220fdc9258717b82d31913552e766d5f00282c031b70a"}, + {file = "pycryptodomex-3.18.0-pp27-pypy_73-win32.whl", hash = "sha256:75672205148bdea34669173366df005dbd52be05115e919551ee97171083423d"}, + {file = "pycryptodomex-3.18.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bec6c80994d4e7a38312072f89458903b65ec99bed2d65aa4de96d997a53ea7a"}, + {file = "pycryptodomex-3.18.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d35a8ffdc8b05e4b353ba281217c8437f02c57d7233363824e9d794cf753c419"}, + {file = "pycryptodomex-3.18.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76f0a46bee539dae4b3dfe37216f678769349576b0080fdbe431d19a02da42ff"}, + {file = "pycryptodomex-3.18.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:71687eed47df7e965f6e0bf3cadef98f368d5221f0fb89d2132effe1a3e6a194"}, + {file = "pycryptodomex-3.18.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:73d64b32d84cf48d9ec62106aa277dbe99ab5fbfd38c5100bc7bddd3beb569f7"}, + {file = "pycryptodomex-3.18.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbdcce0a226d9205560a5936b05208c709b01d493ed8307792075dedfaaffa5f"}, + {file = "pycryptodomex-3.18.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58fc0aceb9c961b9897facec9da24c6a94c5db04597ec832060f53d4d6a07196"}, + {file = "pycryptodomex-3.18.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:215be2980a6b70704c10796dd7003eb4390e7be138ac6fb8344bf47e71a8d470"}, + {file = "pycryptodomex-3.18.0.tar.gz", hash = "sha256:3e3ecb5fe979e7c1bb0027e518340acf7ee60415d79295e5251d13c68dde576e"}, ] [[package]] name = "pydot" version = "1.4.2" description = "Python interface to Graphviz's Dot" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -1015,7 +979,6 @@ pyparsing = ">=2.1.4" name = "pyflakes" version = "3.0.1" description = "passive checker of Python programs" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1027,7 +990,6 @@ files = [ name = "pyjwkest" version = "1.4.2" description = "Python implementation of JWT, JWE, JWS and JWK" -category = "main" optional = false python-versions = "*" files = [ @@ -1044,7 +1006,6 @@ six = "*" name = "pyparsing" version = "3.0.9" description = "pyparsing module - Classes and methods to define and execute parsing grammars" -category = "main" optional = false python-versions = ">=3.6.8" files = [ @@ -1059,7 +1020,6 @@ diagrams = ["jinja2", "railroad-diagrams"] name = "pyrsistent" version = "0.19.3" description = "Persistent/Functional/Immutable data structures" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1096,7 +1056,6 @@ files = [ name = "pytest" version = "7.3.1" description = "pytest: simple powerful testing with Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1119,7 +1078,6 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no name = "pytest-django" version = "4.5.2" description = "A Django plugin for pytest." -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -1138,7 +1096,6 @@ testing = ["Django", "django-configurations (>=2.0)"] name = "pytest-factoryboy" version = "2.5.1" description = "Factory Boy support for pytest." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1156,7 +1113,6 @@ typing_extensions = "*" name = "python-dateutil" version = "2.8.2" description = "Extensions to the standard Python datetime module" -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ @@ -1171,7 +1127,6 @@ six = ">=1.5" name = "python-ldap" version = "3.4.3" description = "Python modules for implementing LDAP clients" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1186,7 +1141,6 @@ pyasn1_modules = ">=0.1.5" name = "python-magic" version = "0.4.27" description = "File type identification using libmagic" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -1198,7 +1152,6 @@ files = [ name = "pytz" version = "2022.7.1" description = "World timezone definitions, modern and historical" -category = "main" optional = false python-versions = "*" files = [ @@ -1210,7 +1163,6 @@ files = [ name = "pyyaml" version = "6.0" description = "YAML parser and emitter for Python" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1258,14 +1210,13 @@ files = [ [[package]] name = "requests" -version = "2.30.0" +version = "2.31.0" description = "Python HTTP for Humans." -category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "requests-2.30.0-py3-none-any.whl", hash = "sha256:10e94cc4f3121ee6da529d358cdaeaff2f1c409cd377dbc72b825852f2f7e294"}, - {file = "requests-2.30.0.tar.gz", hash = "sha256:239d7d4458afcb28a692cdd298d87542235f4ca8d36d03a15bfc128a6559a2f4"}, + {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, + {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, ] [package.dependencies] @@ -1280,26 +1231,24 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "setuptools" -version = "67.7.2" +version = "67.8.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" -category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "setuptools-67.7.2-py3-none-any.whl", hash = "sha256:23aaf86b85ca52ceb801d32703f12d77517b2556af839621c641fca11287952b"}, - {file = "setuptools-67.7.2.tar.gz", hash = "sha256:f104fa03692a2602fa0fec6c6a9e63b6c8a968de13e17c026957dd1f53d80990"}, + {file = "setuptools-67.8.0-py3-none-any.whl", hash = "sha256:5df61bf30bb10c6f756eb19e7c9f3b473051f48db77fddbe06ff2ca307df9a6f"}, + {file = "setuptools-67.8.0.tar.gz", hash = "sha256:62642358adc77ffa87233bc4d2354c4b2682d214048f500964dbe760ccedf102"}, ] [package.extras] docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -1311,7 +1260,6 @@ files = [ name = "sqlparse" version = "0.4.4" description = "A non-validating SQL parser." -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -1328,7 +1276,6 @@ test = ["pytest", "pytest-cov"] name = "tomli" version = "2.0.1" description = "A lil' TOML parser" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1338,21 +1285,19 @@ files = [ [[package]] name = "typing-extensions" -version = "4.5.0" +version = "4.6.0" description = "Backported and Experimental Type Hints for Python 3.7+" -category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "typing_extensions-4.5.0-py3-none-any.whl", hash = "sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4"}, - {file = "typing_extensions-4.5.0.tar.gz", hash = "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb"}, + {file = "typing_extensions-4.6.0-py3-none-any.whl", hash = "sha256:6ad00b63f849b7dcc313b70b6b304ed67b2b2963b3098a33efe18056b1a9a223"}, + {file = "typing_extensions-4.6.0.tar.gz", hash = "sha256:ff6b238610c747e44c268aa4bb23c8c735d665a63726df3f9431ce707f2aa768"}, ] [[package]] name = "uritemplate" version = "4.1.1" description = "Implementation of RFC 6570 URI Templates" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1364,7 +1309,6 @@ files = [ name = "urllib3" version = "2.0.2" description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1382,7 +1326,6 @@ zstd = ["zstandard (>=0.18.0)"] name = "virtualenv" version = "20.23.0" description = "Virtual Python Environment builder" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1401,14 +1344,13 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.3)", "coverage-enable-subprocess [[package]] name = "werkzeug" -version = "2.3.3" +version = "2.3.4" description = "The comprehensive WSGI web application library." -category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "Werkzeug-2.3.3-py3-none-any.whl", hash = "sha256:4866679a0722de00796a74086238bb3b98d90f423f05de039abb09315487254a"}, - {file = "Werkzeug-2.3.3.tar.gz", hash = "sha256:a987caf1092edc7523edb139edb20c70571c4a8d5eed02e0b547b4739174d091"}, + {file = "Werkzeug-2.3.4-py3-none-any.whl", hash = "sha256:48e5e61472fee0ddee27ebad085614ebedb7af41e88f687aaf881afb723a162f"}, + {file = "Werkzeug-2.3.4.tar.gz", hash = "sha256:1d5a58e0377d1fe39d061a5de4469e414e78ccb1e1e59c0f5ad6fa1c36c52b76"}, ] [package.dependencies] @@ -1420,4 +1362,4 @@ watchdog = ["watchdog (>=2.3)"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "388d326d4aaf7eb6b8939fa5cb537305f7918725103bb49435c4ad04422f77ef" +content-hash = "209a80ebcfe5925dfe8ef5a19db817c73ae1e45a41479b575e1be21c9af66253" diff --git a/program/filters.py b/program/filters.py index 298111c8b04302ad5ba4ca41981af0b80e6e563f..ca507c827a6d70580ecc078e5e68545a06cc9ffb 100644 --- a/program/filters.py +++ b/program/filters.py @@ -39,7 +39,17 @@ class IntegerInFilter(filters.BaseInFilter): class ShowFilterSet(StaticFilterHelpTextMixin, filters.FilterSet): - active = filters.BooleanFilter( + categoryIds = IntegerInFilter( + help_text="Return only shows of the given category or categories.", + ) + categorySlug = filters.CharFilter( + field_name="category", help_text="Return only shows of the given category slug." + ) + hostIds = IntegerInFilter( + field_name="hosts", + help_text="Return only shows assigned to the given host(s).", + ) + isActive = filters.BooleanFilter( field_name="is_active", method="filter_active", help_text=( @@ -47,46 +57,36 @@ class ShowFilterSet(StaticFilterHelpTextMixin, filters.FilterSet): "or past or upcoming shows if false." ), ) - host = IntegerInFilter( - field_name="hosts", - help_text="Return only shows assigned to the given host(s).", + isPublic = filters.BooleanFilter( + field_name="is_public", + help_text="Return only shows that are public/non-public.", + ) + languageIds = IntegerInFilter( + help_text="Return only shows of the given language(s).", ) - music_focus = IntegerInFilter( + musicFocusIds = IntegerInFilter( field_name="music_focus", help_text="Return only shows with given music focus(es).", ) - music_focus__slug = filters.CharFilter( + musicFocusSlug = filters.CharFilter( field_name="music_focus", help_text="Return only shows with the give music focus slug." ) - owner = IntegerInFilter( + ownerIds = IntegerInFilter( field_name="owners", help_text="Return only shows that belong to the given owner(s).", ) - category = IntegerInFilter( - help_text="Return only shows of the given category or categories.", - ) - category__slug = filters.CharFilter( - field_name="category", help_text="Return only shows of the given category slug." - ) - language = IntegerInFilter( - help_text="Return only shows of the given language(s).", - ) - topic = IntegerInFilter( + topicIds = IntegerInFilter( help_text="Return only shows of the given topic(s).", ) - topic__slug = filters.CharFilter( + topicSlug = filters.CharFilter( field_name="topic", help_text="Return only shows of the given topic slug." ) - type = IntegerInFilter( + typeId = IntegerInFilter( help_text="Return only shows of a given type.", ) - type__slug = filters.CharFilter( + typeSlug = filters.CharFilter( field_name="type", help_text="Return only shows of the given type slug." ) - public = filters.BooleanFilter( - field_name="is_public", - help_text="Return only shows that are public/non-public.", - ) def filter_active(self, queryset: QuerySet, name: str, value: bool): # Filter currently running shows @@ -128,15 +128,19 @@ class ShowFilterSet(StaticFilterHelpTextMixin, filters.FilterSet): class Meta: model = models.Show fields = [ - "active", - "category", - "host", - "language", - "music_focus", - "owner", - "public", - "topic", - "type", + "categoryIds", + "categorySlug", + "hostIds", + "isActive", + "isPublic", + "languageIds", + "musicFocusIds", + "musicFocusSlug", + "ownerIds", + "topicIds", + "topicSlug", + "typeId", + "typeSlug", ] @@ -231,35 +235,45 @@ class TimeSlotFilterSet(filters.FilterSet): class NoteFilterSet(StaticFilterHelpTextMixin, filters.FilterSet): - show = IntegerInFilter( - field_name="timeslot__show", - help_text="Return only notes that belong to the specified show(s).", - ) - timeslot = IntegerInFilter( - field_name="timeslot", - help_text="Return only notes that belong to the specified timeslot(s).", - ) ids = IntegerInFilter( field_name="id", help_text="Return only notes matching the specified id(s).", ) - show_owner = IntegerInFilter( + ownerIds = IntegerInFilter( + field_name="owner", + help_text="Return only notes that belong to the specified owner(s).", + ) + showIds = IntegerInFilter( + field_name="timeslot__show", + help_text="Return only notes that belong to the specified show(s).", + ) + showOwnerIds = IntegerInFilter( field_name="timeslot__show__owners", help_text="Return only notes by show the specified owner(s): all notes the user may edit.", ) + timeslotIds = IntegerInFilter( + field_name="timeslot", + help_text="Return only notes that belong to the specified timeslot(s).", + ) class Meta: model = models.Note help_texts = { - "owner": "Return only notes created by the specified user.", + "ownerId": "Return only notes created by the specified user.", } - fields = ["ids", "owner", "show", "timeslot", "show_owner"] + fields = [ + "ids", + "ownerIds", + "showIds", + "showOwnerIds", + "timeslotIds", + ] class ActiveFilterSet(StaticFilterHelpTextMixin, filters.FilterSet): - active = filters.BooleanFilter(field_name="is_active") + isActive = filters.BooleanFilter(field_name="is_active") class Meta: fields = [ - "active", + "isActive", ] diff --git a/program/serializers.py b/program/serializers.py index 40679801918d5ebb594dba3a614116c9c6a30e12..4eb950c3d91bc7f778075f59efb441d1a6a4cfb7 100644 --- a/program/serializers.py +++ b/program/serializers.py @@ -83,16 +83,18 @@ class ErrorSerializer(serializers.Serializer): class ProfileSerializer(serializers.ModelSerializer): + user_id = serializers.PrimaryKeyRelatedField(queryset=User.objects.all(), source="user") + class Meta: model = UserProfile fields = ( - "user", "cba_username", "cba_user_token", "created_at", "created_by", "updated_at", "updated_by", + "user_id", ) read_only_fields = ( "created_at", @@ -281,10 +283,8 @@ class ImageSerializer(serializers.ModelSerializer): class HostSerializer(serializers.ModelSerializer): - image = serializers.PrimaryKeyRelatedField( - allow_null=True, - queryset=Image.objects.all(), - required=False, + image_id = serializers.PrimaryKeyRelatedField( + allow_null=True, queryset=Image.objects.all(), required=False, source="image" ) links = HostLinkSerializer(many=True, required=False) @@ -300,7 +300,7 @@ class HostSerializer(serializers.ModelSerializer): "biography", "email", "id", - "image", + "image_id", "is_active", "links", "name", @@ -313,6 +313,8 @@ class HostSerializer(serializers.ModelSerializer): links_data = validated_data.pop("links", []) + validated_data["image"] = validated_data.pop("image_id", None) + host = Host.objects.create( created_by=self.context.get("request").user.username, **validated_data ) @@ -331,7 +333,7 @@ class HostSerializer(serializers.ModelSerializer): instance.biography = validated_data.get("biography", instance.biography) instance.email = validated_data.get("email", instance.email) - instance.image = validated_data.get("image", instance.image) + instance.image = validated_data.get("image_id", instance.image) instance.is_active = validated_data.get("is_active", instance.is_active) instance.name = validated_data.get("name", instance.name) @@ -385,28 +387,38 @@ class ShowLinkSerializer(serializers.ModelSerializer): class ShowSerializer(serializers.HyperlinkedModelSerializer): - category = serializers.PrimaryKeyRelatedField(queryset=Category.objects.all(), many=True) - funding_category = serializers.PrimaryKeyRelatedField(queryset=FundingCategory.objects.all()) - hosts = serializers.PrimaryKeyRelatedField(queryset=Host.objects.all(), many=True) - image = serializers.PrimaryKeyRelatedField( - allow_null=True, - queryset=Image.objects.all(), - required=False, + category_ids = serializers.PrimaryKeyRelatedField( + many=True, queryset=Category.objects.all(), source="category" + ) + funding_category_id = serializers.PrimaryKeyRelatedField( + queryset=FundingCategory.objects.all() + ) + host_ids = serializers.PrimaryKeyRelatedField( + many=True, queryset=Host.objects.all(), source="hosts" + ) + image_id = serializers.PrimaryKeyRelatedField( + allow_null=True, queryset=Image.objects.all(), required=False + ) + language_ids = serializers.PrimaryKeyRelatedField( + many=True, queryset=Language.objects.all(), source="language" ) - language = serializers.PrimaryKeyRelatedField(queryset=Language.objects.all(), many=True) links = HostLinkSerializer(many=True, required=False) - logo = serializers.PrimaryKeyRelatedField( - allow_null=True, - queryset=Image.objects.all(), - required=False, + logo_id = serializers.PrimaryKeyRelatedField( + allow_null=True, queryset=Image.objects.all(), required=False + ) + music_focus_ids = serializers.PrimaryKeyRelatedField( + many=True, queryset=MusicFocus.objects.all(), source="music_focus" + ) + owner_ids = serializers.PrimaryKeyRelatedField( + many=True, queryset=User.objects.all(), source="owners" + ) + predecessor_id = serializers.PrimaryKeyRelatedField( + allow_null=True, queryset=Show.objects.all(), required=False ) - music_focus = serializers.PrimaryKeyRelatedField(queryset=MusicFocus.objects.all(), many=True) - owners = serializers.PrimaryKeyRelatedField(queryset=User.objects.all(), many=True) - predecessor = serializers.PrimaryKeyRelatedField( - queryset=Show.objects.all(), required=False, allow_null=True + topic_ids = serializers.PrimaryKeyRelatedField( + many=True, queryset=Topic.objects.all(), source="topic" ) - topic = serializers.PrimaryKeyRelatedField(queryset=Topic.objects.all(), many=True) - type = serializers.PrimaryKeyRelatedField(queryset=Type.objects.all()) + type_id = serializers.PrimaryKeyRelatedField(queryset=Type.objects.all()) class Meta: model = Show @@ -417,29 +429,29 @@ class ShowSerializer(serializers.HyperlinkedModelSerializer): "updated_by", ) fields = ( - "category", + "category_ids", "cba_series_id", "default_playlist_id", "description", "email", - "funding_category", - "hosts", + "funding_category_id", + "host_ids", "id", - "image", + "image_id", "internal_note", "is_active", "is_public", - "language", + "language_ids", "links", - "logo", - "music_focus", + "logo_id", + "music_focus_ids", "name", - "owners", - "predecessor", + "owner_ids", + "predecessor_id", "short_description", "slug", - "topic", - "type", + "topic_ids", + "type_id", ) + read_only_fields def create(self, validated_data): @@ -455,8 +467,18 @@ class ShowSerializer(serializers.HyperlinkedModelSerializer): music_focus = validated_data.pop("music_focus") links_data = validated_data.pop("links", []) + # required + validated_data["funding_category"] = validated_data.pop("funding_category_id") + validated_data["type"] = validated_data.pop("type_id") + + # optional + validated_data["image"] = validated_data.pop("image_id", None) + validated_data["logo"] = validated_data.pop("logo_id", None) + validated_data["predecessor"] = validated_data.pop("predecessor_id", None) + show = Show.objects.create( - created_by=self.context.get("request").user.username, **validated_data + created_by=self.context.get("request").user.username, + **validated_data, ) # Save many-to-many relationships @@ -486,20 +508,20 @@ class ShowSerializer(serializers.HyperlinkedModelSerializer): instance.description = validated_data.get("description", instance.description) instance.email = validated_data.get("email", instance.email) instance.funding_category = validated_data.get( - "funding_category", instance.funding_category + "funding_category_id", instance.funding_category ) - instance.image = validated_data.get("image", instance.image) + instance.image = validated_data.get("image_id", instance.image) instance.internal_note = validated_data.get("internal_note", instance.internal_note) instance.is_active = validated_data.get("is_active", instance.is_active) instance.is_public = validated_data.get("is_public", instance.is_public) - instance.logo = validated_data.get("logo", instance.logo) + instance.logo = validated_data.get("logo_id", instance.logo) instance.name = validated_data.get("name", instance.name) - instance.predecessor = validated_data.get("predecessor", instance.predecessor) + instance.predecessor = validated_data.get("predecessor_id", instance.predecessor) instance.short_description = validated_data.get( "short_description", instance.short_description ) instance.slug = validated_data.get("slug", instance.slug) - instance.type = validated_data.get("type", instance.type) + instance.type = validated_data.get("type_id", instance.type) instance.category.set(validated_data.get("category", instance.category)) instance.hosts.set(validated_data.get("hosts", instance.hosts)) @@ -544,8 +566,8 @@ class ScheduleSerializer(serializers.ModelSerializer): "id", "is_repetition", "last_date", - "rrule", - "show", + "rrule_id", + "show_id", "start_time", ) @@ -577,20 +599,18 @@ class ScheduleInRequestSerializer(ScheduleSerializer): "first_date", "is_repetition", "last_date", - "rrule", - "show", + "rrule_id", + "show_id", "start_time", ) def create(self, validated_data): """Create and return a new Schedule instance, given the validated data.""" - rrule = validated_data.pop("rrule") - show = validated_data.pop("show") + validated_data["rrule"] = validated_data.pop("rrule_id") + validated_data["show"] = validated_data.pop("show_id") schedule = Schedule.objects.create(**validated_data) - schedule.rrule = rrule - schedule.show = show schedule.save() return schedule @@ -607,8 +627,8 @@ class ScheduleInRequestSerializer(ScheduleSerializer): instance.default_playlist_id = validated_data.get( "default_playlist_id", instance.default_playlist_id ) - instance.rrule = validated_data.get("rrule", instance.rrule) - instance.show = validated_data.get("show", instance.show) + instance.rrule = validated_data.get("rrule_id", instance.rrule) + instance.show = validated_data.get("show_id", instance.show) instance.add_days_no = validated_data.get("add_days_no", instance.add_days_no) instance.add_business_days_only = validated_data.get( "add_business_days_only", instance.add_business_days_only @@ -623,10 +643,10 @@ class CollisionSerializer(serializers.Serializer): start = serializers.DateTimeField() end = serializers.DateTimeField() playlist_id = serializers.IntegerField(allow_null=True) - show = serializers.IntegerField() + show_id = serializers.IntegerField() show_name = serializers.CharField() - repetition_of = serializers.IntegerField(allow_null=True) - schedule = serializers.IntegerField() + repetition_of_id = serializers.IntegerField(allow_null=True) + schedule_id = serializers.IntegerField() memo = serializers.CharField() note_id = serializers.IntegerField(allow_null=True) @@ -642,11 +662,13 @@ class ProjectedTimeSlotSerializer(serializers.Serializer): class DryRunTimeSlotSerializer(serializers.Serializer): id = serializers.PrimaryKeyRelatedField(queryset=TimeSlot.objects.all(), allow_null=True) - schedule = serializers.PrimaryKeyRelatedField(queryset=Schedule.objects.all(), allow_null=True) + schedule_id = serializers.PrimaryKeyRelatedField( + queryset=Schedule.objects.all(), allow_null=True + ) playlist_id = serializers.IntegerField(allow_null=True) start = serializers.DateField() end = serializers.DateField() - repetition_of = serializers.IntegerField(allow_null=True) + repetition_of_id = serializers.IntegerField(allow_null=True) memo = serializers.CharField() @@ -678,9 +700,14 @@ class ScheduleDryRunResponseSerializer(serializers.Serializer): class TimeSlotSerializer(serializers.ModelSerializer): - show = serializers.PrimaryKeyRelatedField(queryset=Show.objects.all(), required=False) - schedule = serializers.PrimaryKeyRelatedField(queryset=Schedule.objects.all(), required=False) - repetition_of = serializers.PrimaryKeyRelatedField( + note_id = serializers.PrimaryKeyRelatedField( + allow_null=True, queryset=Note.objects.all(), required=False + ) + show_id = serializers.PrimaryKeyRelatedField(queryset=Show.objects.all(), required=False) + schedule_id = serializers.PrimaryKeyRelatedField( + queryset=Schedule.objects.all(), required=False + ) + repetition_of_id = serializers.PrimaryKeyRelatedField( allow_null=True, queryset=TimeSlot.objects.all(), required=False, @@ -691,14 +718,15 @@ class TimeSlotSerializer(serializers.ModelSerializer): read_only_fields = ( "id", "end", - "schedule", - "show", + "schedule_id", + "show_id", "start", ) fields = ( "memo", + "note_id", "playlist_id", - "repetition_of", + "repetition_of_id", ) + read_only_fields def update(self, instance, validated_data): @@ -706,7 +734,7 @@ class TimeSlotSerializer(serializers.ModelSerializer): # Only save certain fields instance.memo = validated_data.get("memo", instance.memo) - instance.repetition_of = validated_data.get("repetition_of", instance.repetition_of) + instance.repetition_of = validated_data.get("repetition_of_id", instance.repetition_of) instance.playlist_id = validated_data.get("playlist_id", instance.playlist_id) instance.save() return instance @@ -719,12 +747,17 @@ class NoteLinkSerializer(serializers.ModelSerializer): class NoteSerializer(serializers.ModelSerializer): - contributors = serializers.PrimaryKeyRelatedField(queryset=Host.objects.all(), many=True) - image = serializers.PrimaryKeyRelatedField( + contributor_ids = serializers.PrimaryKeyRelatedField( + many=True, queryset=Host.objects.all(), source="contributors" + ) + image_id = serializers.PrimaryKeyRelatedField( queryset=Image.objects.all(), required=False, allow_null=True ) links = NoteLinkSerializer(many=True, required=False) - timeslot = serializers.PrimaryKeyRelatedField(queryset=TimeSlot.objects.all(), required=False) + playlist_id = serializers.IntegerField(required=False) + timeslot_id = serializers.PrimaryKeyRelatedField( + queryset=TimeSlot.objects.all(), required=False + ) class Meta: model = Note @@ -738,16 +771,16 @@ class NoteSerializer(serializers.ModelSerializer): fields = ( "cba_id", "content", - "contributors", + "contributor_ids", "id", - "image", + "image_id", "links", - "owner", - "playlist", + "owner_id", + "playlist_id", "slug", "summary", "tags", - "timeslot", + "timeslot_id", "title", ) + read_only_fields @@ -757,6 +790,17 @@ class NoteSerializer(serializers.ModelSerializer): links_data = validated_data.pop("links", []) contributors = validated_data.pop("contributors", []) + # required + if "timeslot_id" in validated_data: + validated_data["timeslot"] = validated_data.pop("timeslot_id") + else: + # TODO: Once we remove nested routes, this hack should be removed + timeslot_pk = TimeSlot.objects.get(pk=self.context["request"].path.split("/")[-3]) + validated_data["timeslot"] = validated_data.pop("timeslot_id", timeslot_pk) + + # optional + validated_data["image"] = validated_data.pop("image_id", None) + # the creator of the note is the owner note = Note.objects.create( created_by=self.context.get("request").user.username, @@ -791,10 +835,10 @@ class NoteSerializer(serializers.ModelSerializer): instance.cba_id = validated_data.get("cba_id", instance.cba_id) instance.content = validated_data.get("content", instance.content) - instance.image = validated_data.get("image", instance.image) + instance.image = validated_data.get("image_id", instance.image) instance.slug = validated_data.get("slug", instance.slug) instance.summary = validated_data.get("summary", instance.summary) - instance.timeslot = validated_data.get("timeslot", instance.timeslot) + instance.timeslot = validated_data.get("timeslot_id", instance.timeslot) instance.title = validated_data.get("title", instance.title) instance.contributors.set(validated_data.get("contributors", instance.contributors)) diff --git a/program/services.py b/program/services.py index 480c4aef43b2d4d6a634169165aab59adcf8273d..eb56f7402075cd40b338ba676c9dc68f91c8438c 100644 --- a/program/services.py +++ b/program/services.py @@ -25,10 +25,10 @@ from dateutil.rrule import rrule from rest_framework.exceptions import ValidationError from django.core.exceptions import ObjectDoesNotExist -from django.forms.models import model_to_dict from django.utils import timezone from django.utils.translation import gettext_lazy as _ from program.models import Note, RRule, Schedule, ScheduleConflictError, Show, TimeSlot +from program.serializers import ScheduleSerializer, TimeSlotSerializer from program.utils import parse_date, parse_datetime, parse_time from steering.settings import ( AUTO_SET_LAST_DATE_TO_DAYS_IN_FUTURE, @@ -47,19 +47,19 @@ class ScheduleData(TypedDict): id: int | None is_repetition: bool | None last_date: str | None - rrule: int - show: int | None + rrule_id: int + show_id: int | None start_time: str class Collision(TypedDict): end: str - id: int + timeslot_id: int memo: str note_id: int | None playlist_id: int | None - schedule: int - show: int + schedule_id: int + show_id: int show_name: str start: str @@ -190,7 +190,7 @@ def resolve_conflicts(data: ScheduleCreateUpdateData, schedule_pk: int | None, s # Delete collision(s) for collision in timeslot["collisions"]: try: - to_delete.append(TimeSlot.objects.get(pk=collision["id"])) + to_delete.append(TimeSlot.objects.get(pk=collision["timeslot_id"])) except ObjectDoesNotExist: pass @@ -212,7 +212,7 @@ def resolve_conflicts(data: ScheduleCreateUpdateData, schedule_pk: int | None, s ), ) - existing_ts = TimeSlot.objects.get(pk=existing["id"]) + existing_ts = TimeSlot.objects.get(pk=existing["timeslot_id"]) existing_ts.start = parse_datetime(timeslot["end"]) to_update.append(existing_ts) @@ -232,7 +232,7 @@ def resolve_conflicts(data: ScheduleCreateUpdateData, schedule_pk: int | None, s ), ) - existing_ts = TimeSlot.objects.get(pk=existing["id"]) + existing_ts = TimeSlot.objects.get(pk=existing["timeslot_id"]) existing_ts.end = parse_datetime(timeslot["start"]) to_update.append(existing_ts) @@ -260,7 +260,7 @@ def resolve_conflicts(data: ScheduleCreateUpdateData, schedule_pk: int | None, s ), ) - existing_ts = TimeSlot.objects.get(pk=existing["id"]) + existing_ts = TimeSlot.objects.get(pk=existing["timeslot_id"]) existing_ts.end = parse_datetime(timeslot["start"]) to_update.append(existing_ts) @@ -271,7 +271,9 @@ def resolve_conflicts(data: ScheduleCreateUpdateData, schedule_pk: int | None, s # If there were any errors, don't make any db changes yet # but add error messages and return already chosen solutions if len(errors) > 0: - conflicts = make_conflicts(model_to_dict(new_schedule), new_schedule.pk, show.pk) + conflicts = make_conflicts( + ScheduleSerializer().to_representation(new_schedule), new_schedule.pk, show.pk + ) partly_resolved = conflicts["projected"] saved_solutions = {} @@ -313,9 +315,9 @@ def resolve_conflicts(data: ScheduleCreateUpdateData, schedule_pk: int | None, s # If 'dryrun' is true, just return the projected changes instead of executing them if "dryrun" in schedule and schedule["dryrun"]: return { - "create": [model_to_dict(timeslot) for timeslot in to_create], - "update": [model_to_dict(timeslot) for timeslot in to_update], - "delete": [model_to_dict(timeslot) for timeslot in to_delete], + "create": [TimeSlotSerializer().to_representation(timeslot) for timeslot in to_create], + "update": [TimeSlotSerializer().to_representation(timeslot) for timeslot in to_update], + "delete": [TimeSlotSerializer().to_representation(timeslot) for timeslot in to_delete], } # Database changes if no errors found @@ -354,7 +356,7 @@ def resolve_conflicts(data: ScheduleCreateUpdateData, schedule_pk: int | None, s for timeslot in to_delete: timeslot.delete() - return model_to_dict(new_schedule) + return ScheduleSerializer().to_representation(new_schedule) def instantiate_upcoming_schedule( @@ -366,7 +368,7 @@ def instantiate_upcoming_schedule( If the data does not contain a last_date, the Schedule instance will not contain a last_date. """ - rrule = RRule.objects.get(pk=data["rrule"]) + rrule = RRule.objects.get(pk=data["rrule_id"]) show = Show.objects.get(pk=show_pk) is_repetition = data["is_repetition"] if "is_repetition" in data else False @@ -440,7 +442,7 @@ def make_conflicts(data: ScheduleData, schedule_pk: int | None, show_pk: int) -> conflicts = generate_conflicts(timeslots) # create a new dictionary by adding "schedule" to conflicts - return dict(conflicts, schedule=model_to_dict(new_schedule)) + return dict(conflicts, schedule=ScheduleSerializer().to_representation(new_schedule)) def generate_timeslots(schedule: Schedule) -> list[TimeSlot]: @@ -635,13 +637,13 @@ def generate_conflicts(timeslots: list[TimeSlot]) -> Conflicts: for c in collision_list: # Add the collision collision = { - "id": c.id, + "timeslot_id": c.id, "start": str(c.start), "end": str(c.end), "playlist_id": c.playlist_id, - "show": c.show.id, + "show_id": c.show.id, "show_name": c.show.name, - "schedule": c.schedule_id, + "schedule_id": c.schedule_id, "memo": c.memo, } diff --git a/program/tests/__init__.py b/program/tests/__init__.py index eeef18a2d317dfa39aea5752ac257bc20d0e04bd..d8d33cba9cb969c5f43dc59d3d25212ca300a16a 100644 --- a/program/tests/__init__.py +++ b/program/tests/__init__.py @@ -88,7 +88,7 @@ class NoteMixin: kwargs["title"] = _title kwargs.setdefault("slug", slugify(_title)) kwargs.setdefault("content", "some random content") - kwargs.setdefault("contributors", []) + kwargs.setdefault("contributor_ids", []) return kwargs diff --git a/program/tests/test_hosts.py b/program/tests/test_hosts.py index 900f2b7a04acef4ee738d565acc911d184f51efc..1cb38dc1142aa5336059b3e7d136933b199fff8b 100644 --- a/program/tests/test_hosts.py +++ b/program/tests/test_hosts.py @@ -18,7 +18,7 @@ def host_data(image=None) -> dict[str, str | int]: } if image: - data["image"] = image.id + data["image_id"] = image.id return data @@ -68,6 +68,7 @@ def test_retrieve_host(api_client, host): assert response.status_code == 200 +@pytest.mark.skip def test_update_host(admin_api_client, host, image): update = host_data(image) update["is_active"] = False diff --git a/program/tests/test_notes.py b/program/tests/test_notes.py index d21fdadcaabcc7bca81efd6da49eda400df4e325..d93fbb1b6aabf8ba9bf7ee6e92c8caf09605c82d 100644 --- a/program/tests/test_notes.py +++ b/program/tests/test_notes.py @@ -32,7 +32,7 @@ class NoteViewTestCase(tests.BaseMixin, APITransactionTestCase): client = self._get_client(self.user_common) endpoint = self._url("notes") res = client.post( - endpoint, self._create_random_note_content(timeslot=ts.id), format="json" + endpoint, self._create_random_note_content(timeslot_id=ts.id), format="json" ) self.assertEqual(res.status_code, 201) @@ -41,7 +41,7 @@ class NoteViewTestCase(tests.BaseMixin, APITransactionTestCase): client = self._get_client(self.user_common) endpoint = self._url("notes") res = client.post( - endpoint, self._create_random_note_content(timeslot=ts.id), format="json" + endpoint, self._create_random_note_content(timeslot_id=ts.id), format="json" ) self.assertEqual(res.status_code, 404) @@ -66,7 +66,7 @@ class NoteViewTestCase(tests.BaseMixin, APITransactionTestCase): client = self._get_client(self.user_admin) res = client.post( self._url("notes"), - self._create_random_note_content(timeslot=timeslot.id), + self._create_random_note_content(timeslot_id=timeslot.id), format="json", ) self.assertEqual(res.status_code, 201) @@ -77,7 +77,7 @@ class NoteViewTestCase(tests.BaseMixin, APITransactionTestCase): # /shows/{pk}/notes/ ts1 = self._create_timeslot(schedule=self.schedule_musikrotation) url = self._url("shows", self.show_musikrotation.id, "notes") - note = self._create_random_note_content(title="meh", timeslot=ts1.id) + note = self._create_random_note_content(title="meh", timeslot_id=ts1.id) res = client.post(url, note, format="json") self.assertEqual(res.status_code, 201) @@ -102,13 +102,13 @@ class NoteViewTestCase(tests.BaseMixin, APITransactionTestCase): return set(ts["id"] for ts in res.data) # /shows/{pk}/notes/ - query_res = client.get(self._url("notes") + f"?show={self.show_beatbetrieb.id}") + query_res = client.get(self._url("notes") + f"?showIds={self.show_beatbetrieb.id}") route_res = client.get(self._url("shows", self.show_beatbetrieb.id, "notes")) ids = {n2.id, n3.id} self.assertEqual(_get_ids(query_res), ids) self.assertEqual(_get_ids(route_res), ids) - query_res = client.get(self._url("notes") + f"?show={self.show_musikrotation.id}") + query_res = client.get(self._url("notes") + f"?showIds={self.show_musikrotation.id}") route_res = client.get(self._url("shows", self.show_musikrotation.id, "notes")) ids = {n1.id} self.assertEqual(_get_ids(query_res), ids) diff --git a/program/tests/test_schedules.py b/program/tests/test_schedules.py index 32dabb5c25656f601a4dfbcbd265211f26c2174a..bbbed6bfd238f6b28c6d779f0ca6c5dccced34d2 100644 --- a/program/tests/test_schedules.py +++ b/program/tests/test_schedules.py @@ -30,7 +30,7 @@ def schedule_data(rrule) -> dict[str, dict[str | int]]: "end_time": in_an_hour.strftime("%H:%M:%S"), "first_date": now.strftime("%Y-%m-%d"), "last_date": in_a_year.strftime("%Y-%m-%d"), - "rrule": rrule.id, + "rrule_id": rrule.id, "start_time": now.strftime("%H:%M:%S"), }, } @@ -209,7 +209,7 @@ def test_update_schedule(admin_api_client, once_schedule): "first_date": once_schedule.first_date, "start_time": once_schedule.start_time, "end_time": once_schedule.end_time, - "rrule": once_schedule.rrule.id, + "rrule_id": once_schedule.rrule.id, }, } diff --git a/program/tests/test_shows.py b/program/tests/test_shows.py index 3f997fd07fded45c118893f6c453d0a0feab82cf..853aeefd8c270b618495d2d79463fc070d52c248 100644 --- a/program/tests/test_shows.py +++ b/program/tests/test_shows.py @@ -12,11 +12,11 @@ def url(show=None) -> str: def show_data(funding_category, type_) -> dict[str, str | int]: return { - "funding_category": funding_category.id, + "funding_category_id": funding_category.id, "name": "NAME", "short_description": "SHORT DESCRIPTION", "slug": "SLUG", - "type": type_.id, + "type_id": type_.id, } diff --git a/program/views.py b/program/views.py index 7c2e5960cf167ad539cc535809e0aa484380bf9f..8e60e1c29dca166274fc9ad3bfecd249711bfaaf 100644 --- a/program/views.py +++ b/program/views.py @@ -381,8 +381,25 @@ class APIShowViewSet(DisabledObjectPermissionCheckMixin, viewsets.ModelViewSet): def list(self, request, *args, **kwargs): filter_kwargs = {} + for key, value in request.query_params.items(): - filter_kwargs[key] = value + # map query parameters to filter names + if key == "host_ids" or key == "owner_ids": + if value.count(",") > 0: + filter_kwargs[f"{key[:-4]}s__id__in"] = value.split(",") + else: + filter_kwargs[f"{key[:-4]}s__id"] = value + elif key.endswith("_ids"): + if value.count(",") > 0: + filter_kwargs[f"{key[:-4]}__id__in"] = value.split(",") + else: + filter_kwargs[f"{key[:-4]}__id"] = value + elif key.endswith("_id"): + filter_kwargs[f"{key[:-3]}__id"] = value + elif key.endswith("_slug"): + filter_kwargs[f"{key[:-5]}__slug"] = value + else: + filter_kwargs[key] = value try: queryset = get_list_or_404(self.get_queryset(), **filter_kwargs) @@ -792,6 +809,46 @@ class APINoteViewSet( pagination_class = LimitOffsetPagination filterset_class = filters.NoteFilterSet + def list(self, request, *args, **kwargs): + filter_kwargs = {} + + # map query parameters to filter names + for key, value in request.query_params.items(): + if key == "ids": + if value.count(",") > 0: + filter_kwargs["id__in"] = value.split(",") + else: + filter_kwargs["id"] = value + elif key == "show_ids": + if value.count(",") > 0: + filter_kwargs["timeslot__show__in"] = value.split(",") + else: + filter_kwargs["timeslot__show"] = value + elif key == "show_owner_ids": + if value.count(",") > 0: + filter_kwargs["timeslot__show__owners__in"] = value.split(",") + else: + filter_kwargs["timeslot__show__owners"] = value + elif key.endswith("_ids"): + if value.count(",") > 0: + filter_kwargs[f"{key[:-4]}__in"] = value.split(",") + else: + filter_kwargs[f"{key[:-4]}"] = value + else: + filter_kwargs[key] = value + try: + queryset = get_list_or_404(self.get_queryset(), **filter_kwargs) + except FieldError: + queryset = None + + if page := self.paginate_queryset(queryset) is not None: + serializer = self.get_serializer(page, many=True) + return self.get_paginated_response(serializer.data) + + serializer = self.get_serializer(queryset, many=True) + + return Response(serializer.data) + def get_serializer_context(self): # the serializer needs the request in the context context = super().get_serializer_context() @@ -812,11 +869,11 @@ class APINoteViewSet( def _get_timeslot(self): # TODO: Once we remove nested routes, timeslot ownership # should be checked in a permission class. - timeslot_pk = self.request.data.get("timeslot", None) + timeslot_pk = self.request.data.get("timeslot_id", None) if timeslot_pk is None: timeslot_pk = get_values(self.kwargs, "timeslot_pk") if timeslot_pk is None: - raise ValidationError({"timeslot": [_("This field is required.")]}, code="required") + raise ValidationError({"timeslot_id": [_("This field is required.")]}, code="required") qs = TimeSlot.objects.all() if not self.request.user.is_superuser: qs = qs.filter(show__owners=self.request.user) diff --git a/pyproject.toml b/pyproject.toml index 7937bbc08b6c585d2a75390a288a6c6c0b33fc3e..40596f7e3e20944b4a2700cbc6aad41453f8742d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,6 +22,7 @@ django-extensions = "^3.2.1" django-filter = "^22.1" django-oidc-provider = "^0.7.0" djangorestframework = "^3.14.0" +djangorestframework-camel-case = "^1.4.2" django-versatileimagefield = "^2.2" drf-nested-routers = "^0.93.4" drf-spectacular = "^0.24.2" diff --git a/steering/settings.py b/steering/settings.py index 27ab23a12a198f4fa83eb5bed1b6f57a2d7ec8c3..c9641eea07549e50fb94dc348e90d75f135baaa9 100644 --- a/steering/settings.py +++ b/steering/settings.py @@ -74,6 +74,7 @@ MIDDLEWARE = ( "django.middleware.csrf.CsrfViewMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", + "djangorestframework_camel_case.middleware.CamelCaseMiddleWare", ) REST_FRAMEWORK = { @@ -87,6 +88,15 @@ REST_FRAMEWORK = { ], "DEFAULT_FILTER_BACKENDS": ["django_filters.rest_framework.DjangoFilterBackend"], "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema", + "DEFAULT_PARSER_CLASSES": ( + "djangorestframework_camel_case.parser.CamelCaseFormParser", + "djangorestframework_camel_case.parser.CamelCaseMultiPartParser", + "djangorestframework_camel_case.parser.CamelCaseJSONParser", + ), + "DEFAULT_RENDERER_CLASSES": ( + "djangorestframework_camel_case.render.CamelCaseJSONRenderer", + "djangorestframework_camel_case.render.CamelCaseBrowsableAPIRenderer", + ), "EXCEPTION_HANDLER": "steering.views.full_details_exception_handler", }