diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 9b88b9be93..f61692ef79 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -30,7 +30,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: '3.12' + python-version: '3.13' cache: 'pip' cache-dependency-path: 'docs/requirements.txt' - run: python -m pip install -r docs/requirements.txt @@ -48,7 +48,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: '3.12' + python-version: '3.13' - run: python -m pip install blacken-docs - name: Build docs run: | diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml index 3875e755f9..7c64dc98ff 100644 --- a/.github/workflows/linters.yml +++ b/.github/workflows/linters.yml @@ -27,7 +27,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: '3.12' + python-version: '3.13' - run: python -m pip install flake8 - name: flake8 # Pinned to v3.0.0. @@ -44,7 +44,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: '3.12' + python-version: '3.13' - run: python -m pip install isort - name: isort # Pinned to v3.0.0. diff --git a/.github/workflows/python_matrix.yml b/.github/workflows/python_matrix.yml new file mode 100644 index 0000000000..ab48c2be83 --- /dev/null +++ b/.github/workflows/python_matrix.yml @@ -0,0 +1,52 @@ +name: Python Matrix from config file + +on: + pull_request: + types: [labeled, synchronize, opened, reopened] + paths-ignore: + - 'docs/**' + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + define-matrix: + if: contains(github.event.pull_request.labels.*.name, 'python-matrix') + runs-on: ubuntu-latest + outputs: + python_versions_output: ${{ steps.set-matrix.outputs.python_versions }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + - id: set-matrix + run: | + python_versions=$(sed -n "s/^.*Programming Language :: Python :: \([[:digit:]]\+\.[[:digit:]]\+\).*$/'\1', /p" pyproject.toml | tr -d '\n' | sed 's/, $//g') + echo "Supported Python versions: $python_versions" + echo "python_versions=[$python_versions]" >> "$GITHUB_OUTPUT" + python: + runs-on: ubuntu-latest + needs: define-matrix + strategy: + matrix: + python-version: ${{ fromJson(needs.define-matrix.outputs.python_versions_output) }} + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + cache: 'pip' + cache-dependency-path: 'tests/requirements/py3.txt' + - name: Install libmemcached-dev for pylibmc + run: sudo apt-get install libmemcached-dev + - name: Install and upgrade packaging tools + run: python -m pip install --upgrade pip setuptools wheel + - run: python -m pip install -r tests/requirements/py3.txt -e . + - name: Run tests + run: python tests/runtests.py -v2 diff --git a/.github/workflows/schedule_tests.yml b/.github/workflows/schedule_tests.yml index 78b06ba5de..f99ef218aa 100644 --- a/.github/workflows/schedule_tests.yml +++ b/.github/workflows/schedule_tests.yml @@ -19,7 +19,7 @@ jobs: - '3.10' - '3.11' - '3.12' - - '3.13-dev' + - '3.13' name: Windows, SQLite, Python ${{ matrix.python-version }} continue-on-error: true steps: @@ -46,7 +46,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: '3.12' + python-version: '3.13' cache: 'pip' - name: Install libmemcached-dev for pylibmc run: sudo apt-get install libmemcached-dev @@ -145,7 +145,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: '3.12' + python-version: '3.13' cache: 'pip' cache-dependency-path: 'tests/requirements/py3.txt' - name: Install libmemcached-dev for pylibmc @@ -181,7 +181,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: '3.12' + python-version: '3.13' cache: 'pip' cache-dependency-path: 'tests/requirements/py3.txt' - name: Install libmemcached-dev for pylibmc @@ -200,10 +200,10 @@ jobs: strategy: fail-fast: false matrix: - version: [16, 17rc1] + version: [16, 17] server_side_bindings: [0, 1] runs-on: ubuntu-latest - name: Newer PostgreSQL Versions + name: PostgreSQL Versions env: SERVER_SIDE_BINDING: ${{ matrix.server_side_bindings }} services: @@ -226,7 +226,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: '3.12' + python-version: '3.13' cache: 'pip' cache-dependency-path: 'tests/requirements/py3.txt' - name: Install libmemcached-dev for pylibmc diff --git a/.github/workflows/selenium.yml b/.github/workflows/selenium.yml index 7e46e0cfb1..14a95f3b66 100644 --- a/.github/workflows/selenium.yml +++ b/.github/workflows/selenium.yml @@ -24,7 +24,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: '3.12' + python-version: '3.13' cache: 'pip' cache-dependency-path: 'tests/requirements/py3.txt' - name: Install libmemcached-dev for pylibmc @@ -61,7 +61,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: '3.12' + python-version: '3.13' cache: 'pip' cache-dependency-path: 'tests/requirements/py3.txt' - name: Install libmemcached-dev for pylibmc diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index abe7c78a25..5de554721d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -23,7 +23,7 @@ jobs: strategy: matrix: python-version: - - '3.12' + - '3.13' name: Windows, SQLite, Python ${{ matrix.python-version }} steps: - name: Checkout diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a1a734accf..5f20568b34 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,15 +1,15 @@ repos: - repo: https://github.com/psf/black-pre-commit-mirror - rev: 24.4.2 + rev: 24.10.0 hooks: - id: black exclude: \.py-tpl$ - repo: https://github.com/adamchainz/blacken-docs - rev: 1.18.0 + rev: 1.19.0 hooks: - id: blacken-docs additional_dependencies: - - black==24.4.2 + - black==24.10.0 files: 'docs/.*\.txt$' args: ["--rst-literal-block"] - repo: https://github.com/PyCQA/isort @@ -17,10 +17,10 @@ repos: hooks: - id: isort - repo: https://github.com/PyCQA/flake8 - rev: 7.1.0 + rev: 7.1.1 hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-eslint - rev: v9.7.0 + rev: v9.12.0 hooks: - id: eslint diff --git a/django/conf/locale/cs/LC_MESSAGES/django.mo b/django/conf/locale/cs/LC_MESSAGES/django.mo index 66e08fed43..95086e3d52 100644 Binary files a/django/conf/locale/cs/LC_MESSAGES/django.mo and b/django/conf/locale/cs/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/cs/LC_MESSAGES/django.po b/django/conf/locale/cs/LC_MESSAGES/django.po index bf85e0c41d..14f5ef9684 100644 --- a/django/conf/locale/cs/LC_MESSAGES/django.po +++ b/django/conf/locale/cs/LC_MESSAGES/django.po @@ -3,10 +3,11 @@ # Translators: # Claude Paroz , 2020 # Jannis Leidel , 2011 -# Jan Papež , 2012 -# trendspotter , 2022 +# Jan Papež , 2012,2024 +# Jiří Podhorecký , 2024 +# Jiří Podhorecký , 2022 # Jirka Vejrazka , 2011 -# trendspotter , 2020 +# Jiří Podhorecký , 2020 # Tomáš Ehrlich , 2015 # Vláďa Macek , 2012-2014 # Vláďa Macek , 2015-2022 @@ -14,10 +15,10 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-05-17 05:23-0500\n" -"PO-Revision-Date: 2022-07-25 06:49+0000\n" -"Last-Translator: trendspotter \n" -"Language-Team: Czech (http://www.transifex.com/django/django/language/cs/)\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-10-07 06:49+0000\n" +"Last-Translator: Jan Papež , 2012,2024\n" +"Language-Team: Czech (http://app.transifex.com/django/django/language/cs/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -58,6 +59,9 @@ msgstr "bosensky" msgid "Catalan" msgstr "katalánsky" +msgid "Central Kurdish (Sorani)" +msgstr "Střední kurdština (soranština)" + msgid "Czech" msgstr "česky" @@ -298,6 +302,9 @@ msgstr "tatarsky" msgid "Udmurt" msgstr "udmurtsky" +msgid "Uyghur" +msgstr "Ujgurština" + msgid "Ukrainian" msgstr "ukrajinsky" @@ -345,6 +352,9 @@ msgstr "Stránka je bez výsledků" msgid "Enter a valid value." msgstr "Zadejte platnou hodnotu." +msgid "Enter a valid domain name." +msgstr "Zadejte platný název domény." + msgid "Enter a valid URL." msgstr "Zadejte platnou adresu URL." @@ -368,14 +378,18 @@ msgstr "" "Zadejte platný identifikátor složený pouze z písmen, čísel, podtržítek a " "pomlček typu Unicode." -msgid "Enter a valid IPv4 address." -msgstr "Zadejte platnou adresu typu IPv4." +#, python-format +msgid "Enter a valid %(protocol)s address." +msgstr "Zadejte platnou %(protocol)s adresu." -msgid "Enter a valid IPv6 address." -msgstr "Zadejte platnou adresu typu IPv6." +msgid "IPv4" +msgstr "IPv4" -msgid "Enter a valid IPv4 or IPv6 address." -msgstr "Zadejte platnou adresu typu IPv4 nebo IPv6." +msgid "IPv6" +msgstr "IPv6" + +msgid "IPv4 or IPv6" +msgstr "IPv4 nebo IPv6" msgid "Enter only digits separated by commas." msgstr "Zadejte pouze číslice oddělené čárkami." @@ -397,6 +411,15 @@ msgid "Ensure this value is a multiple of step size %(limit_value)s." msgstr "" "Ujistěte se, že tato hodnota je násobkem velikosti kroku %(limit_value)s." +#, python-format +msgid "" +"Ensure this value is a multiple of step size %(limit_value)s, starting from " +"%(offset)s, e.g. %(offset)s, %(valid_value1)s, %(valid_value2)s, and so on." +msgstr "" +"Zajistěte, aby tato hodnota byla %(limit_value)s násobkem velikosti kroku , " +"počínaje %(offset)s, např. %(offset)s, %(valid_value1)s, %(valid_value2)s, a " +"tak dále." + #, python-format msgid "" "Ensure this value has at least %(limit_value)d character (it has " @@ -533,6 +556,9 @@ msgstr "Pravdivost (buď Ano (True), nebo Ne (False))" msgid "String (up to %(max_length)s)" msgstr "Řetězec (max. %(max_length)s znaků)" +msgid "String (unlimited)" +msgstr "Řetězec (neomezený)" + msgid "Comma-separated integers" msgstr "Celá čísla oddělená čárkou" @@ -806,18 +832,18 @@ msgstr "" #, python-format msgid "Please submit at most %(num)d form." msgid_plural "Please submit at most %(num)d forms." -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" -msgstr[3] "" +msgstr[0] "Odešlete prosím nejvíce %(num)d formulář." +msgstr[1] "Odešlete prosím nejvíce %(num)d formuláře." +msgstr[2] "Odešlete prosím nejvíce %(num)d formulářů." +msgstr[3] "Odešlete prosím nejvíce %(num)d formulářů." #, python-format msgid "Please submit at least %(num)d form." msgid_plural "Please submit at least %(num)d forms." -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" -msgstr[3] "" +msgstr[0] "Odešlete prosím alespoň %(num)d formulář." +msgstr[1] "Odešlete prosím alespoň %(num)d formuláře." +msgstr[2] "Odešlete prosím alespoň %(num)d formulářů." +msgstr[3] "Odešlete prosím alespoň %(num)d formulářů." msgid "Order" msgstr "Pořadí" @@ -1233,8 +1259,8 @@ msgid "" "If you are using the tag or " "including the “Referrer-Policy: no-referrer” header, please remove them. The " "CSRF protection requires the “Referer” header to do strict referer checking. " -"If you’re concerned about privacy, use alternatives like for links to third-party sites." +"If you’re concerned about privacy, use alternatives like for links to third-party sites." msgstr "" "Pokud používáte značku nebo " "záhlaví \"Referrer-Policy: no-referrer\", odeberte je. Ochrana typu CSRF " @@ -1334,13 +1360,13 @@ msgstr "" #, python-format msgid "" "You are seeing this page because DEBUG=True is in your settings file and you have not configured any " -"URLs." +"%(version)s/ref/settings/#debug\" target=\"_blank\" " +"rel=\"noopener\">DEBUG=True is in your settings file and you have not " +"configured any URLs." msgstr "" "Tuto zprávu vidíte, protože máte v nastavení Djanga zapnutý vývojový režim " -"DEBUG=True a zatím nemáte " +"DEBUG=True a zatím nemáte " "nastavena žádná URL." msgid "Django Documentation" diff --git a/django/conf/locale/dsb/LC_MESSAGES/django.mo b/django/conf/locale/dsb/LC_MESSAGES/django.mo index 645e6c7c44..4d70da4f1c 100644 Binary files a/django/conf/locale/dsb/LC_MESSAGES/django.mo and b/django/conf/locale/dsb/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/dsb/LC_MESSAGES/django.po b/django/conf/locale/dsb/LC_MESSAGES/django.po index e7c0fb6ce5..4448d1a2d3 100644 --- a/django/conf/locale/dsb/LC_MESSAGES/django.po +++ b/django/conf/locale/dsb/LC_MESSAGES/django.po @@ -1,14 +1,14 @@ # This file is distributed under the same license as the Django package. # # Translators: -# Michael Wolf , 2016-2023 +# Michael Wolf , 2016-2024 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-09-18 11:41-0300\n" -"PO-Revision-Date: 2023-12-04 06:49+0000\n" -"Last-Translator: Michael Wolf , 2016-2023\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-10-07 06:49+0000\n" +"Last-Translator: Michael Wolf , 2016-2024\n" "Language-Team: Lower Sorbian (http://app.transifex.com/django/django/" "language/dsb/)\n" "MIME-Version: 1.0\n" @@ -344,6 +344,9 @@ msgstr "Toś ten bok njewopśimujo wuslědki" msgid "Enter a valid value." msgstr "Zapódajśo płaśiwu gódnotu." +msgid "Enter a valid domain name." +msgstr "Zapódajśo płaśiwe domenowe mě." + msgid "Enter a valid URL." msgstr "Zapódajśo płaśiwy URL." @@ -367,14 +370,18 @@ msgstr "" "Zapódajśo płaśiwe „adresowe mě“, kótarež jano wopśimujo unicodowe pismiki, " "licby, pódmužki abo wězawki." -msgid "Enter a valid IPv4 address." -msgstr "Zapódajśo płaśiwu IPv4-adresu." +#, python-format +msgid "Enter a valid %(protocol)s address." +msgstr "Zapódajśo płaśiwu %(protocol)s-adresu." -msgid "Enter a valid IPv6 address." -msgstr "Zapódajśo płaśiwu IPv6-adresu." +msgid "IPv4" +msgstr "IPv4" -msgid "Enter a valid IPv4 or IPv6 address." -msgstr "Zapódajśo płaśiwu IPv4- abo IPv6-adresu." +msgid "IPv6" +msgstr "IPv6" + +msgid "IPv4 or IPv6" +msgstr "IPv4 abo IPv6" msgid "Enter only digits separated by commas." msgstr "Zapódajśo jano cyfry źělone pśez komy." diff --git a/django/conf/locale/es_AR/LC_MESSAGES/django.mo b/django/conf/locale/es_AR/LC_MESSAGES/django.mo index 5f62b7125a..cdb8444a35 100644 Binary files a/django/conf/locale/es_AR/LC_MESSAGES/django.mo and b/django/conf/locale/es_AR/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/es_AR/LC_MESSAGES/django.po b/django/conf/locale/es_AR/LC_MESSAGES/django.po index ada4c3170f..17680c6f3f 100644 --- a/django/conf/locale/es_AR/LC_MESSAGES/django.po +++ b/django/conf/locale/es_AR/LC_MESSAGES/django.po @@ -3,16 +3,16 @@ # Translators: # Jannis Leidel , 2011 # lardissone , 2014 -# Natalia (Django Fellow), 2023 +# Natalia, 2023 # poli , 2014 -# Ramiro Morales, 2013-2023 +# Ramiro Morales, 2013-2024 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-09-18 11:41-0300\n" -"PO-Revision-Date: 2023-12-04 06:49+0000\n" -"Last-Translator: Natalia (Django Fellow), 2023\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-10-07 06:49+0000\n" +"Last-Translator: Ramiro Morales, 2013-2024\n" "Language-Team: Spanish (Argentina) (http://app.transifex.com/django/django/" "language/es_AR/)\n" "MIME-Version: 1.0\n" @@ -347,6 +347,9 @@ msgstr "Esa página no contiene resultados" msgid "Enter a valid value." msgstr "Introduzca un valor válido." +msgid "Enter a valid domain name." +msgstr "Introduzca un nombre de dominio válido." + msgid "Enter a valid URL." msgstr "Introduzca una URL válida." @@ -368,14 +371,18 @@ msgstr "" "Introduzca un “slug” compuesto por letras Unicode, números, guiones bajos o " "guiones." -msgid "Enter a valid IPv4 address." -msgstr "Introduzca una dirección IPv4 válida." +#, python-format +msgid "Enter a valid %(protocol)s address." +msgstr "Introduzca una dirección de %(protocol)s válida." -msgid "Enter a valid IPv6 address." -msgstr "Introduzca una dirección IPv6 válida." +msgid "IPv4" +msgstr "IPv4" -msgid "Enter a valid IPv4 or IPv6 address." -msgstr "Introduzca una dirección IPv4 o IPv6 válida." +msgid "IPv6" +msgstr "IPv6" + +msgid "IPv4 or IPv6" +msgstr "IPv4 o IPv6" msgid "Enter only digits separated by commas." msgstr "Introduzca sólo dígitos separados por comas." diff --git a/django/conf/locale/fr/LC_MESSAGES/django.mo b/django/conf/locale/fr/LC_MESSAGES/django.mo index 3178ac8eff..bf4a0fe19f 100644 Binary files a/django/conf/locale/fr/LC_MESSAGES/django.mo and b/django/conf/locale/fr/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/fr/LC_MESSAGES/django.po b/django/conf/locale/fr/LC_MESSAGES/django.po index 291e051703..253672ee44 100644 --- a/django/conf/locale/fr/LC_MESSAGES/django.po +++ b/django/conf/locale/fr/LC_MESSAGES/django.po @@ -14,7 +14,7 @@ msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-05-22 11:46-0300\n" -"PO-Revision-Date: 2024-08-07 06:49+0000\n" +"PO-Revision-Date: 2024-10-07 06:49+0000\n" "Last-Translator: Claude Paroz , 2013-2024\n" "Language-Team: French (http://app.transifex.com/django/django/language/fr/)\n" "MIME-Version: 1.0\n" @@ -228,7 +228,7 @@ msgid "Nepali" msgstr "Népalais" msgid "Dutch" -msgstr "Hollandais" +msgstr "Néerlandais" msgid "Norwegian Nynorsk" msgstr "Norvégien nynorsk" diff --git a/django/conf/locale/ga/LC_MESSAGES/django.mo b/django/conf/locale/ga/LC_MESSAGES/django.mo index c2a8a88d70..e55658a322 100644 Binary files a/django/conf/locale/ga/LC_MESSAGES/django.mo and b/django/conf/locale/ga/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/ga/LC_MESSAGES/django.po b/django/conf/locale/ga/LC_MESSAGES/django.po index 2b1b5289e1..21a1e19712 100644 --- a/django/conf/locale/ga/LC_MESSAGES/django.po +++ b/django/conf/locale/ga/LC_MESSAGES/django.po @@ -1,6 +1,7 @@ # This file is distributed under the same license as the Django package. # # Translators: +# Aindriú Mac Giolla Eoin, 2024 # Claude Paroz , 2020 # Jannis Leidel , 2011 # John Moylan , 2013 @@ -13,10 +14,10 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-05-19 20:23+0200\n" -"PO-Revision-Date: 2020-07-14 21:42+0000\n" -"Last-Translator: Transifex Bot <>\n" -"Language-Team: Irish (http://www.transifex.com/django/django/language/ga/)\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-10-07 06:49+0000\n" +"Last-Translator: Aindriú Mac Giolla Eoin, 2024\n" +"Language-Team: Irish (http://app.transifex.com/django/django/language/ga/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -31,7 +32,7 @@ msgid "Arabic" msgstr "Araibis" msgid "Algerian Arabic" -msgstr "" +msgstr "Araibis na hAilgéire" msgid "Asturian" msgstr "Astúiris" @@ -57,6 +58,9 @@ msgstr "Boisnis" msgid "Catalan" msgstr "Catalóinis" +msgid "Central Kurdish (Sorani)" +msgstr "Coirdis Láir (Sorani)" + msgid "Czech" msgstr "Seicis" @@ -70,7 +74,7 @@ msgid "German" msgstr "Gearmáinis" msgid "Lower Sorbian" -msgstr "" +msgstr "Sorbais Íochtarach" msgid "Greek" msgstr "Gréigis" @@ -94,7 +98,7 @@ msgid "Argentinian Spanish" msgstr "Spáinnis na hAirgintíne" msgid "Colombian Spanish" -msgstr "" +msgstr "Spáinnis na Colóime" msgid "Mexican Spanish" msgstr "Spáinnis Mheicsiceo " @@ -142,13 +146,13 @@ msgid "Croatian" msgstr "Cróitis" msgid "Upper Sorbian" -msgstr "" +msgstr "Sorbian Uachtarach" msgid "Hungarian" msgstr "Ungáiris" msgid "Armenian" -msgstr "" +msgstr "Airméinis" msgid "Interlingua" msgstr "Interlingua" @@ -157,7 +161,7 @@ msgid "Indonesian" msgstr "Indinéisis" msgid "Igbo" -msgstr "" +msgstr "Igbo" msgid "Ido" msgstr "Ido" @@ -175,7 +179,7 @@ msgid "Georgian" msgstr "Seoirsis" msgid "Kabyle" -msgstr "" +msgstr "Cabaill" msgid "Kazakh" msgstr "Casaicis" @@ -190,7 +194,7 @@ msgid "Korean" msgstr "Cóiréis" msgid "Kyrgyz" -msgstr "" +msgstr "Chirgeastáin" msgid "Luxembourgish" msgstr "Lucsamburgach" @@ -213,11 +217,14 @@ msgstr "Mongóilis" msgid "Marathi" msgstr "Maraitis" +msgid "Malay" +msgstr "Malaeis" + msgid "Burmese" msgstr "Burmais" msgid "Norwegian Bokmål" -msgstr "" +msgstr "Ioruais Bokmål" msgid "Nepali" msgstr "Neipeailis" @@ -268,7 +275,7 @@ msgid "Swedish" msgstr "Sualainnis" msgid "Swahili" -msgstr "" +msgstr "Svahaílis" msgid "Tamil" msgstr "Tamailis" @@ -277,22 +284,25 @@ msgid "Telugu" msgstr "Teileagúis" msgid "Tajik" -msgstr "" +msgstr "Táidsíc" msgid "Thai" msgstr "Téalainnis" msgid "Turkmen" -msgstr "" +msgstr "Tuircméinis" msgid "Turkish" msgstr "Tuircis" msgid "Tatar" -msgstr "" +msgstr "Tatairis" msgid "Udmurt" -msgstr "" +msgstr "Udmurt" + +msgid "Uyghur" +msgstr "Uighur" msgid "Ukrainian" msgstr "Úcráinis" @@ -301,7 +311,7 @@ msgid "Urdu" msgstr "Urdais" msgid "Uzbek" -msgstr "" +msgstr "Úisbéicis" msgid "Vietnamese" msgstr "Vítneamais" @@ -316,7 +326,7 @@ msgid "Messages" msgstr "Teachtaireachtaí" msgid "Site Maps" -msgstr "" +msgstr "Léarscáileanna Suímh" msgid "Static Files" msgstr "Comhaid Statach" @@ -324,45 +334,61 @@ msgstr "Comhaid Statach" msgid "Syndication" msgstr "Sindeacáitiú" +#. Translators: String used to replace omitted page numbers in elided page +#. range generated by paginators, e.g. [1, 2, '…', 5, 6, 7, '…', 9, 10]. +msgid "…" +msgstr "…" + msgid "That page number is not an integer" -msgstr "" +msgstr "Ní slánuimhir í an uimhir leathanaigh sin" msgid "That page number is less than 1" -msgstr "" +msgstr "Tá uimhir an leathanaigh sin níos lú ná 1" msgid "That page contains no results" -msgstr "" +msgstr "Níl aon torthaí ar an leathanach sin" msgid "Enter a valid value." msgstr "Iontráil luach bailí" +msgid "Enter a valid domain name." +msgstr "Cuir isteach ainm fearainn bailí." + msgid "Enter a valid URL." msgstr "Iontráil URL bailí." msgid "Enter a valid integer." -msgstr "" +msgstr "Cuir isteach slánuimhir bhailí." msgid "Enter a valid email address." -msgstr "" +msgstr "Cuir isteach seoladh ríomhphoist bailí." #. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid “slug” consisting of letters, numbers, underscores or hyphens." msgstr "" +"Cuir isteach “sluga” bailí ar a bhfuil litreacha, uimhreacha, foscórthaí nó " +"fleiscíní." msgid "" "Enter a valid “slug” consisting of Unicode letters, numbers, underscores, or " "hyphens." msgstr "" +"Cuir isteach “sluga” bailí ar a bhfuil litreacha Unicode, uimhreacha, fo-" +"scóranna, nó fleiscíní." -msgid "Enter a valid IPv4 address." -msgstr "Iontráil seoladh IPv4 bailí." +#, python-format +msgid "Enter a valid %(protocol)s address." +msgstr "Cuir isteach seoladh bailí %(protocol)s." -msgid "Enter a valid IPv6 address." -msgstr "Cuir seoladh bailí IPv6 isteach." +msgid "IPv4" +msgstr "IPv4" -msgid "Enter a valid IPv4 or IPv6 address." -msgstr "Cuir seoladh bailí IPv4 nó IPv6 isteach." +msgid "IPv6" +msgstr "IPv6" + +msgid "IPv4 or IPv6" +msgstr "IPv4 nó IPv6" msgid "Enter only digits separated by commas." msgstr "Ná hiontráil ach digití atá deighilte le camóga." @@ -382,6 +408,19 @@ msgid "Ensure this value is greater than or equal to %(limit_value)s." msgstr "" "Cinntigh go bhfuil an luach seo níos mó ná nó cothrom le %(limit_value)s." +#, python-format +msgid "Ensure this value is a multiple of step size %(limit_value)s." +msgstr "Cinntigh gur iolraí de chéimmhéid %(limit_value)s an luach seo." + +#, python-format +msgid "" +"Ensure this value is a multiple of step size %(limit_value)s, starting from " +"%(offset)s, e.g. %(offset)s, %(valid_value1)s, %(valid_value2)s, and so on." +msgstr "" +"Cinntigh gur iolraí de chéimmhéid %(limit_value)s an luach seo, ag tosú ó " +"%(offset)s, m.sh. %(offset)s, %(valid_value1)s, %(valid_value2)s, agus mar " +"sin de." + #, python-format msgid "" "Ensure this value has at least %(limit_value)d character (it has " @@ -390,10 +429,20 @@ msgid_plural "" "Ensure this value has at least %(limit_value)d characters (it has " "%(show_value)d)." msgstr[0] "" +"Cinntigh go bhfuil ar a laghad %(limit_value)d carachtar ag an luach seo (tá " +"%(show_value)d aige)." msgstr[1] "" +"Cinntigh go bhfuil ar a laghad %(limit_value)d carachtar ag an luach seo (tá " +"%(show_value)d aige)." msgstr[2] "" +"Cinntigh go bhfuil ar a laghad %(limit_value)d carachtar ag an luach seo (tá " +"%(show_value)d aige)." msgstr[3] "" +"Cinntigh go bhfuil ar a laghad %(limit_value)d carachtar ag an luach seo (tá " +"%(show_value)d aige)." msgstr[4] "" +"Cinntigh go bhfuil ar a laghad %(limit_value)d carachtar ag an luach seo (tá " +"%(show_value)d aige)." #, python-format msgid "" @@ -403,10 +452,20 @@ msgid_plural "" "Ensure this value has at most %(limit_value)d characters (it has " "%(show_value)d)." msgstr[0] "" +"Cinntigh go bhfuil %(limit_value)d carachtar ar a mhéad ag an luach seo (tá " +"%(show_value)d aige)." msgstr[1] "" +"Cinntigh go bhfuil %(limit_value)d carachtar ar a mhéad ag an luach seo (tá " +"%(show_value)d aige)." msgstr[2] "" +"Cinntigh go bhfuil %(limit_value)d carachtar ar a mhéad ag an luach seo (tá " +"%(show_value)d aige)." msgstr[3] "" +"Cinntigh go bhfuil %(limit_value)d carachtar ar a mhéad ag an luach seo (tá " +"%(show_value)d aige)." msgstr[4] "" +"Cinntigh go bhfuil %(limit_value)d carachtar ar a mhéad ag an luach seo (tá " +"%(show_value)d aige)." msgid "Enter a number." msgstr "Iontráil uimhir." @@ -414,20 +473,20 @@ msgstr "Iontráil uimhir." #, python-format msgid "Ensure that there are no more than %(max)s digit in total." msgid_plural "Ensure that there are no more than %(max)s digits in total." -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" -msgstr[3] "" -msgstr[4] "" +msgstr[0] "Cinntigh nach bhfuil níos mó ná %(max)s digit san iomlán." +msgstr[1] "Cinntigh nach bhfuil níos mó ná %(max)s digit san iomlán." +msgstr[2] "Cinntigh nach bhfuil níos mó ná %(max)s digit san iomlán." +msgstr[3] "Cinntigh nach bhfuil níos mó ná %(max)s digit san iomlán." +msgstr[4] "Cinntigh nach bhfuil níos mó ná %(max)s digit san iomlán." #, python-format msgid "Ensure that there are no more than %(max)s decimal place." msgid_plural "Ensure that there are no more than %(max)s decimal places." -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" -msgstr[3] "" -msgstr[4] "" +msgstr[0] "Cinntigh nach bhfuil níos mó ná %(max)s ionad deachúlach ann." +msgstr[1] "Cinntigh nach bhfuil níos mó ná %(max)s de dheachúlacha ann." +msgstr[2] "Cinntigh nach bhfuil níos mó ná %(max)s de dheachúlacha ann." +msgstr[3] "Cinntigh nach bhfuil níos mó ná %(max)s de dheachúlacha ann." +msgstr[4] "Cinntigh nach bhfuil níos mó ná %(max)s de dheachúlacha ann." #, python-format msgid "" @@ -435,30 +494,41 @@ msgid "" msgid_plural "" "Ensure that there are no more than %(max)s digits before the decimal point." msgstr[0] "" +"Cinntigh nach bhfuil níos mó ná %(max)s digit ann roimh an bpointe deachúil." msgstr[1] "" +"Cinntigh nach bhfuil níos mó ná %(max)s dhigit roimh an bpointe deachúil." msgstr[2] "" +"Cinntigh nach bhfuil níos mó ná %(max)s dhigit roimh an bpointe deachúil." msgstr[3] "" +"Cinntigh nach bhfuil níos mó ná %(max)s dhigit roimh an bpointe deachúil." msgstr[4] "" +"Cinntigh nach bhfuil níos mó ná %(max)s dhigit roimh an bpointe deachúil." #, python-format msgid "" "File extension “%(extension)s” is not allowed. Allowed extensions are: " "%(allowed_extensions)s." msgstr "" +"Ní cheadaítear iarmhír chomhaid “%(extension)s”. Is iad seo a leanas " +"eisínteachtaí ceadaithe: %(allowed_extensions)s." msgid "Null characters are not allowed." -msgstr "" +msgstr "Ní cheadaítear carachtair null." msgid "and" msgstr "agus" #, python-format msgid "%(model_name)s with this %(field_labels)s already exists." -msgstr "" +msgstr "Tá %(model_name)s leis an %(field_labels)s seo ann cheana." + +#, python-format +msgid "Constraint “%(name)s” is violated." +msgstr "Tá srian “%(name)s” sáraithe." #, python-format msgid "Value %(value)r is not a valid choice." -msgstr "" +msgstr "Ní rogha bhailí é luach %(value)r." msgid "This field cannot be null." msgstr "Ní cheadaítear luach nialasach sa réimse seo." @@ -470,12 +540,14 @@ msgstr "Ní cheadaítear luach nialasach sa réimse seo." msgid "%(model_name)s with this %(field_label)s already exists." msgstr "Tá %(model_name)s leis an %(field_label)s seo ann cheana." -#. Translators: The 'lookup_type' is one of 'date', 'year' or 'month'. -#. Eg: "Title must be unique for pub_date year" +#. Translators: The 'lookup_type' is one of 'date', 'year' or +#. 'month'. Eg: "Title must be unique for pub_date year" #, python-format msgid "" "%(field_label)s must be unique for %(date_field_label)s %(lookup_type)s." msgstr "" +"Caithfidh %(field_label)s a bheith uathúil le haghaidh %(date_field_label)s " +"%(lookup_type)s." #, python-format msgid "Field of type: %(field_type)s" @@ -483,11 +555,11 @@ msgstr "Réimse de Cineál: %(field_type)s" #, python-format msgid "“%(value)s” value must be either True or False." -msgstr "" +msgstr "Caithfidh luach “%(value)s” a bheith Fíor nó Bréagach." #, python-format msgid "“%(value)s” value must be either True, False, or None." -msgstr "" +msgstr "Caithfidh luach “%(value)s” a bheith Fíor, Bréagach, nó Neamhní." msgid "Boolean (Either True or False)" msgstr "Boole" @@ -496,6 +568,9 @@ msgstr "Boole" msgid "String (up to %(max_length)s)" msgstr "Teaghrán (suas go %(max_length)s)" +msgid "String (unlimited)" +msgstr "Teaghrán (gan teorainn)" + msgid "Comma-separated integers" msgstr "Slánuimhireacha camóg-scartha" @@ -504,12 +579,16 @@ msgid "" "“%(value)s” value has an invalid date format. It must be in YYYY-MM-DD " "format." msgstr "" +"Tá formáid dáta neamhbhailí ag luach “%(value)s”. Caithfidh sé a bheith i " +"bhformáid BBBB-MM-LL." #, python-format msgid "" "“%(value)s” value has the correct format (YYYY-MM-DD) but it is an invalid " "date." msgstr "" +"Tá an fhormáid cheart ag luach “%(value)s” (BBBB-MM-DD) ach is dáta " +"neamhbhailí é." msgid "Date (without time)" msgstr "Dáta (gan am)" @@ -519,19 +598,23 @@ msgid "" "“%(value)s” value has an invalid format. It must be in YYYY-MM-DD HH:MM[:ss[." "uuuuuu]][TZ] format." msgstr "" +"Tá formáid neamhbhailí ag luach “%(value)s”. Caithfidh sé a bheith san " +"fhormáid BBBB-MM-DD HH:MM[:ss[.uuuuuu]][TZ]." #, python-format msgid "" "“%(value)s” value has the correct format (YYYY-MM-DD HH:MM[:ss[.uuuuuu]]" "[TZ]) but it is an invalid date/time." msgstr "" +"Tá an fhormáid cheart ag luach “%(value)s” (YYYY-MM-DD HH:MM[:ss[.uuuuuu]]" +"[TZ]) ach is dáta/am neamhbhailí é." msgid "Date (with time)" msgstr "Dáta (le am)" #, python-format msgid "“%(value)s” value must be a decimal number." -msgstr "" +msgstr "Caithfidh luach “%(value)s” a bheith ina uimhir dheachúil." msgid "Decimal number" msgstr "Uimhir deachúlach" @@ -541,6 +624,8 @@ msgid "" "“%(value)s” value has an invalid format. It must be in [DD] [[HH:]MM:]ss[." "uuuuuu] format." msgstr "" +"Tá formáid neamhbhailí ag luach “%(value)s”. Caithfidh sé a bheith i " +"bhformáid [DD] [[HH:]MM:]ss[.uuuuuu]." msgid "Duration" msgstr "Fad" @@ -553,14 +638,14 @@ msgstr "Conair comhaid" #, python-format msgid "“%(value)s” value must be a float." -msgstr "" +msgstr "Caithfidh luach “%(value)s” a bheith ina shnámhán." msgid "Floating point number" msgstr "Snámhphointe" #, python-format msgid "“%(value)s” value must be an integer." -msgstr "" +msgstr "Caithfidh luach “%(value)s” a bheith ina shlánuimhir." msgid "Integer" msgstr "Slánuimhir" @@ -568,6 +653,9 @@ msgstr "Slánuimhir" msgid "Big (8 byte) integer" msgstr "Mór (8 byte) slánuimhi" +msgid "Small integer" +msgstr "Slánuimhir beag" + msgid "IPv4 address" msgstr "Seoladh IPv4" @@ -576,13 +664,13 @@ msgstr "Seoladh IP" #, python-format msgid "“%(value)s” value must be either None, True or False." -msgstr "" +msgstr "Ní mór luach “%(value)s” a bheith Easpa, Fíor nó Bréagach." msgid "Boolean (Either True, False or None)" msgstr "Boole (Fíor, Bréagach nó Dada)" msgid "Positive big integer" -msgstr "" +msgstr "Slánuimhir mhór dhearfach" msgid "Positive integer" msgstr "Slánuimhir dearfach" @@ -594,9 +682,6 @@ msgstr "Slánuimhir beag dearfach" msgid "Slug (up to %(max_length)s)" msgstr "Slug (suas go %(max_length)s)" -msgid "Small integer" -msgstr "Slánuimhir beag" - msgid "Text" msgstr "Téacs" @@ -605,12 +690,16 @@ msgid "" "“%(value)s” value has an invalid format. It must be in HH:MM[:ss[.uuuuuu]] " "format." msgstr "" +"Tá formáid neamhbhailí ag luach “%(value)s”. Caithfidh sé a bheith i " +"bhformáid HH:MM[:ss[.uuuuuu]]." #, python-format msgid "" "“%(value)s” value has the correct format (HH:MM[:ss[.uuuuuu]]) but it is an " "invalid time." msgstr "" +"Tá an fhormáid cheart ag luach “%(value)s” (HH:MM[:ss[.uuuuuu]]) ach is am " +"neamhbhailí é." msgid "Time" msgstr "Am" @@ -619,14 +708,14 @@ msgid "URL" msgstr "URL" msgid "Raw binary data" -msgstr "" +msgstr "Sonraí dénártha amh" #, python-format msgid "“%(value)s” is not a valid UUID." -msgstr "" +msgstr "Ní UUID bailí é “%(value)s”." msgid "Universally unique identifier" -msgstr "" +msgstr "Aitheantóir uathúil uilíoch" msgid "File" msgstr "Comhaid" @@ -635,14 +724,14 @@ msgid "Image" msgstr "Íomhá" msgid "A JSON object" -msgstr "" +msgstr "Réad JSON" msgid "Value must be valid JSON." -msgstr "" +msgstr "Caithfidh an luach a bheith bailí JSON." #, python-format msgid "%(model)s instance with %(field)s %(value)r does not exist." -msgstr "" +msgstr "Níl sampla %(model)s le %(field)s %(value)r ann." msgid "Foreign Key (type determined by related field)" msgstr "Eochair Eachtracha (cineál a chinnfear de réir réimse a bhaineann)" @@ -652,11 +741,11 @@ msgstr "Duine-le-duine caidreamh" #, python-format msgid "%(from)s-%(to)s relationship" -msgstr "" +msgstr "%(from)s-%(to)s caidreamh" #, python-format msgid "%(from)s-%(to)s relationships" -msgstr "" +msgstr "%(from)s-%(to)s caidrimh" msgid "Many-to-many relationship" msgstr "Go leor le go leor caidreamh" @@ -683,11 +772,11 @@ msgid "Enter a valid date/time." msgstr "Iontráil dáta/am bailí." msgid "Enter a valid duration." -msgstr "" +msgstr "Cuir isteach ré bailí." #, python-brace-format msgid "The number of days must be between {min_days} and {max_days}." -msgstr "" +msgstr "Caithfidh líon na laethanta a bheith idir {min_days} agus {max_days}." msgid "No file was submitted. Check the encoding type on the form." msgstr "Níor seoladh comhad. Deimhnigh cineál an ionchódaithe ar an bhfoirm." @@ -703,10 +792,20 @@ msgid "Ensure this filename has at most %(max)d character (it has %(length)d)." msgid_plural "" "Ensure this filename has at most %(max)d characters (it has %(length)d)." msgstr[0] "" +"Cinntigh go bhfuil %(max)d carachtar ar a mhéad ag an gcomhadainm seo (tá " +"%(length)d aige)." msgstr[1] "" +"Cinntigh go bhfuil %(max)d carachtar ar a mhéad ag an gcomhadainm seo (tá " +"%(length)d aige)." msgstr[2] "" +"Cinntigh go bhfuil %(max)d carachtar ar a mhéad ag an gcomhadainm seo (tá " +"%(length)d aige)." msgstr[3] "" +"Cinntigh go bhfuil %(max)d carachtar ar a mhéad ag an gcomhadainm seo (tá " +"%(length)d aige)." msgstr[4] "" +"Cinntigh go bhfuil %(max)d carachtar ar a mhéad ag an gcomhadainm seo (tá " +"%(length)d aige)." msgid "Please either submit a file or check the clear checkbox, not both." msgstr "" @@ -728,13 +827,13 @@ msgid "Enter a list of values." msgstr "Cuir liosta de luachanna isteach." msgid "Enter a complete value." -msgstr "" +msgstr "Cuir isteach luach iomlán." msgid "Enter a valid UUID." -msgstr "" +msgstr "Cuir isteach UUID bailí." msgid "Enter a valid JSON." -msgstr "" +msgstr "Cuir isteach JSON bailí." #. Translators: This is the default suffix added to form field labels msgid ":" @@ -742,28 +841,34 @@ msgstr ":" #, python-format msgid "(Hidden field %(name)s) %(error)s" -msgstr "" - -msgid "ManagementForm data is missing or has been tampered with" -msgstr "" +msgstr "(Réimse folaithe %(name)s) %(error)s" #, python-format -msgid "Please submit %d or fewer forms." -msgid_plural "Please submit %d or fewer forms." -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" -msgstr[3] "" -msgstr[4] "" +msgid "" +"ManagementForm data is missing or has been tampered with. Missing fields: " +"%(field_names)s. You may need to file a bug report if the issue persists." +msgstr "" +"Tá sonraí ManagementForm in easnamh nó ar cuireadh isteach orthu. Réimsí ar " +"iarraidh: %(field_names)s. Seans go mbeidh ort tuairisc fhabht a chomhdú má " +"leanann an cheist." #, python-format -msgid "Please submit %d or more forms." -msgid_plural "Please submit %d or more forms." -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" -msgstr[3] "" -msgstr[4] "" +msgid "Please submit at most %(num)d form." +msgid_plural "Please submit at most %(num)d forms." +msgstr[0] "Cuir isteach %(num)d foirm ar a mhéad." +msgstr[1] "Cuir isteach %(num)d foirm ar a mhéad." +msgstr[2] "Cuir isteach %(num)d foirm ar a mhéad." +msgstr[3] "Cuir isteach %(num)d foirm ar a mhéad." +msgstr[4] "Cuir isteach %(num)d foirm ar a mhéad." + +#, python-format +msgid "Please submit at least %(num)d form." +msgid_plural "Please submit at least %(num)d forms." +msgstr[0] "Cuir isteach ar a laghad %(num)d foirm." +msgstr[1] "Cuir isteach %(num)d foirm ar a laghad." +msgstr[2] "Cuir isteach %(num)d foirm ar a laghad." +msgstr[3] "Cuir isteach %(num)d foirm ar a laghad." +msgstr[4] "Cuir isteach %(num)d foirm ar a laghad." msgid "Order" msgstr "Ord" @@ -793,20 +898,22 @@ msgid "Please correct the duplicate values below." msgstr "Le do thoil ceartaigh na luachanna dúbail thíos." msgid "The inline value did not match the parent instance." -msgstr "" +msgstr "Níor mheaitseáil an luach inlíne leis an gcás tuismitheora." msgid "Select a valid choice. That choice is not one of the available choices." msgstr "Déan rogha bhailí. Ní ceann de na roghanna é do roghasa." #, python-format msgid "“%(pk)s” is not a valid value." -msgstr "" +msgstr "Ní luach bailí é “%(pk)s”." #, python-format msgid "" "%(datetime)s couldn’t be interpreted in time zone %(current_timezone)s; it " "may be ambiguous or it may not exist." msgstr "" +"Níorbh fhéidir %(datetime)s a léirmhíniú i gcrios ama %(current_timezone)s; " +"d'fhéadfadh sé a bheith débhríoch nó b'fhéidir nach bhfuil sé ann." msgid "Clear" msgstr "Glan" @@ -1088,12 +1195,12 @@ msgid "December" msgstr "Mí na Nollag" msgid "This is not a valid IPv6 address." -msgstr "" +msgstr "Ní seoladh IPv6 bailí é seo." #, python-format msgctxt "String to return when truncating text" msgid "%(truncated_text)s…" -msgstr "" +msgstr "%(truncated_text)s…" msgid "or" msgstr "nó" @@ -1103,96 +1210,117 @@ msgid ", " msgstr ", " #, python-format -msgid "%d year" -msgid_plural "%d years" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" -msgstr[3] "" -msgstr[4] "" +msgid "%(num)d year" +msgid_plural "%(num)d years" +msgstr[0] "%(num)d bhliain" +msgstr[1] "%(num)d bliain" +msgstr[2] "%(num)d bliain" +msgstr[3] "%(num)d bliain" +msgstr[4] "%(num)d bliain" #, python-format -msgid "%d month" -msgid_plural "%d months" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" -msgstr[3] "" -msgstr[4] "" +msgid "%(num)d month" +msgid_plural "%(num)d months" +msgstr[0] "%(num)d mí" +msgstr[1] "%(num)d míonna" +msgstr[2] "%(num)d míonna" +msgstr[3] "%(num)d míonna" +msgstr[4] "%(num)d míonna" #, python-format -msgid "%d week" -msgid_plural "%d weeks" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" -msgstr[3] "" -msgstr[4] "" +msgid "%(num)d week" +msgid_plural "%(num)d weeks" +msgstr[0] "%(num)d seachtain" +msgstr[1] "%(num)d seachtainí" +msgstr[2] "%(num)d seachtainí" +msgstr[3] "%(num)d seachtainí" +msgstr[4] "%(num)d seachtainí" #, python-format -msgid "%d day" -msgid_plural "%d days" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" -msgstr[3] "" -msgstr[4] "" +msgid "%(num)d day" +msgid_plural "%(num)d days" +msgstr[0] "%(num)d lá" +msgstr[1] "%(num)d laethanta" +msgstr[2] "%(num)d laethanta" +msgstr[3] "%(num)d laethanta" +msgstr[4] "%(num)d laethanta" #, python-format -msgid "%d hour" -msgid_plural "%d hours" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" -msgstr[3] "" -msgstr[4] "" +msgid "%(num)d hour" +msgid_plural "%(num)d hours" +msgstr[0] "%(num)d uair" +msgstr[1] "%(num)d huaireanta" +msgstr[2] "%(num)d huaireanta" +msgstr[3] "%(num)d huaireanta" +msgstr[4] "%(num)d huaireanta" #, python-format -msgid "%d minute" -msgid_plural "%d minutes" -msgstr[0] "%d nóiméad" -msgstr[1] "%d nóiméad" -msgstr[2] "%d nóiméad" -msgstr[3] "%d nóiméad" -msgstr[4] "%d nóiméad" +msgid "%(num)d minute" +msgid_plural "%(num)d minutes" +msgstr[0] "%(num)d nóiméad" +msgstr[1] "%(num)d nóiméad" +msgstr[2] "%(num)d nóiméad" +msgstr[3] "%(num)d nóiméad" +msgstr[4] "%(num)d nóiméad" msgid "Forbidden" msgstr "Toirmiscthe" msgid "CSRF verification failed. Request aborted." -msgstr "" +msgstr "Theip ar fhíorú CSRF. Cuireadh deireadh leis an iarratas." msgid "" "You are seeing this message because this HTTPS site requires a “Referer " -"header” to be sent by your Web browser, but none was sent. This header is " +"header” to be sent by your web browser, but none was sent. This header is " "required for security reasons, to ensure that your browser is not being " "hijacked by third parties." msgstr "" +"Tá an teachtaireacht seo á fheiceáil agat toisc go bhfuil “ceanntásc " +"tarchuir” ag teastáil ón suíomh HTTPS seo le bheith seolta ag do bhrabhsálaí " +"gréasáin, ach níor seoladh aon cheann. Tá an ceanntásc seo ag teastáil ar " +"chúiseanna slándála, lena chinntiú nach bhfuil do bhrabhsálaí á fuadach ag " +"tríú páirtithe." msgid "" "If you have configured your browser to disable “Referer” headers, please re-" "enable them, at least for this site, or for HTTPS connections, or for “same-" "origin” requests." msgstr "" +"Má tá do bhrabhsálaí cumraithe agat chun ceanntásca “Tagairtí” a dhíchumasú, " +"le do thoil déan iad a athchumasú, le do thoil don suíomh seo, nó do naisc " +"HTTPS, nó d’iarratais “ar an mbunús céanna”." msgid "" "If you are using the tag or " "including the “Referrer-Policy: no-referrer” header, please remove them. The " "CSRF protection requires the “Referer” header to do strict referer checking. " -"If you’re concerned about privacy, use alternatives like for links to third-party sites." +"If you’re concerned about privacy, use alternatives like for links to third-party sites." msgstr "" +"Má tá an chlib 1 á úsáid agat nó má tá an ceanntásc “Polasaí Atreoraithe: " +"gan atreorú” san áireamh, bain amach iad le do thoil. Éilíonn an chosaint " +"CSRF go bhfuil an ceanntásc “Tagairtí” chun seiceáil docht atreoraithe a " +"dhéanamh. Má tá imní ort faoi phríobháideachas, bain úsáid as roghanna eile " +"amhail le haghaidh naisc chuig láithreáin tríú " +"páirtí." msgid "" "You are seeing this message because this site requires a CSRF cookie when " "submitting forms. This cookie is required for security reasons, to ensure " "that your browser is not being hijacked by third parties." msgstr "" +"Tá an teachtaireacht seo á fheiceáil agat toisc go bhfuil fianán CSRF ag " +"teastáil ón suíomh seo agus foirmeacha á gcur isteach agat. Tá an fianán seo " +"ag teastáil ar chúiseanna slándála, lena chinntiú nach bhfuil do bhrabhsálaí " +"á fuadach ag tríú páirtithe." msgid "" "If you have configured your browser to disable cookies, please re-enable " "them, at least for this site, or for “same-origin” requests." msgstr "" +"Má tá do bhrabhsálaí cumraithe agat chun fianáin a dhíchumasú, le do thoil " +"athchumasaigh iad, le do thoil, le haghaidh an tsuímh seo ar a laghad, nó le " +"haghaidh iarratais “ar an mbunús céanna”." msgid "More information is available with DEBUG=True." msgstr "Tá tuilleadh eolais ar fáil le DEBUG=True." @@ -1201,7 +1329,7 @@ msgid "No year specified" msgstr "Bliain gan sonrú" msgid "Date out of range" -msgstr "" +msgstr "Dáta as raon" msgid "No month specified" msgstr "Mí gan sonrú" @@ -1226,7 +1354,7 @@ msgstr "" #, python-format msgid "Invalid date string “%(datestr)s” given format “%(format)s”" -msgstr "" +msgstr "Teaghrán dáta neamhbhailí “%(datestr)s” tugtha formáid “%(format)s”" #, python-format msgid "No %(verbose_name)s found matching the query" @@ -1234,6 +1362,7 @@ msgstr "Níl bhfuarthas %(verbose_name)s le hadhaigh an iarratas" msgid "Page is not “last”, nor can it be converted to an int." msgstr "" +"Níl an leathanach “deireadh”, agus ní féidir é a thiontú go slánuimhir." #, python-format msgid "Invalid page (%(page_number)s): %(message)s" @@ -1241,53 +1370,57 @@ msgstr "Leathanach neamhbhailí (%(page_number)s): %(message)s" #, python-format msgid "Empty list and “%(class_name)s.allow_empty” is False." -msgstr "" +msgstr "Tá liosta folamh agus “%(class_name)s.allow_empty” bréagach." msgid "Directory indexes are not allowed here." msgstr "Níl innéacsanna chomhadlann cheadaítear anseo." #, python-format msgid "“%(path)s” does not exist" -msgstr "" +msgstr "Níl “%(path)s” ann" #, python-format msgid "Index of %(directory)s" msgstr "Innéacs de %(directory)s" -msgid "Django: the Web framework for perfectionists with deadlines." -msgstr "" +msgid "The install worked successfully! Congratulations!" +msgstr "D'éirigh leis an suiteáil! Comhghairdeachas!" #, python-format msgid "" "View release notes for Django %(version)s" msgstr "" - -msgid "The install worked successfully! Congratulations!" -msgstr "" +"Féach ar nótaí scaoilte le haghaidh Django " +"%(version)s" #, python-format msgid "" "You are seeing this page because DEBUG=True is in your settings file and you have not configured any " -"URLs." +"%(version)s/ref/settings/#debug\" target=\"_blank\" " +"rel=\"noopener\">DEBUG=True is in your settings file and you have not " +"configured any URLs." msgstr "" +"Tá an leathanach seo á fheiceáil agat toisc go bhfuil DEBUG=True i do chomhad socruithe agus nach bhfuil aon " +"URL cumraithe agat." msgid "Django Documentation" -msgstr "" +msgstr "Doiciméadú Django" msgid "Topics, references, & how-to’s" -msgstr "" +msgstr "Ábhair, tagairtí, & conas atá" msgid "Tutorial: A Polling App" -msgstr "" +msgstr "Teagaisc: A Vótaíocht Aip" msgid "Get started with Django" msgstr "Tosaigh le Django" msgid "Django Community" -msgstr "" +msgstr "Pobal Django" msgid "Connect, get help, or contribute" -msgstr "" +msgstr "Ceangail, faigh cúnamh, nó ranníoc" diff --git a/django/conf/locale/hsb/LC_MESSAGES/django.mo b/django/conf/locale/hsb/LC_MESSAGES/django.mo index 600b787b05..16f40d3094 100644 Binary files a/django/conf/locale/hsb/LC_MESSAGES/django.mo and b/django/conf/locale/hsb/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/hsb/LC_MESSAGES/django.po b/django/conf/locale/hsb/LC_MESSAGES/django.po index a5fb97cfb5..46f12d09bc 100644 --- a/django/conf/locale/hsb/LC_MESSAGES/django.po +++ b/django/conf/locale/hsb/LC_MESSAGES/django.po @@ -1,14 +1,14 @@ # This file is distributed under the same license as the Django package. # # Translators: -# Michael Wolf , 2016-2023 +# Michael Wolf , 2016-2024 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-09-18 11:41-0300\n" -"PO-Revision-Date: 2023-12-04 06:49+0000\n" -"Last-Translator: Michael Wolf , 2016-2023\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-10-07 06:49+0000\n" +"Last-Translator: Michael Wolf , 2016-2024\n" "Language-Team: Upper Sorbian (http://app.transifex.com/django/django/" "language/hsb/)\n" "MIME-Version: 1.0\n" @@ -344,6 +344,9 @@ msgstr "Tuta strona wuslědki njewobsahuje" msgid "Enter a valid value." msgstr "Zapodajće płaćiwu hódnotu." +msgid "Enter a valid domain name." +msgstr "Zapodajće płaćiwe domenowe mjeno." + msgid "Enter a valid URL." msgstr "Zapodajće płaćiwy URL." @@ -367,14 +370,18 @@ msgstr "" "Zapodajće płaćiwe „adresowe mjeno“, kotrež jenož pismiki, ličby, podsmužki " "abo wjazawki wobsahuje." -msgid "Enter a valid IPv4 address." -msgstr "Zapodajće płaćiwu IPv4-adresu." +#, python-format +msgid "Enter a valid %(protocol)s address." +msgstr "Zapodajće płaćiwu %(protocol)s-adresu." -msgid "Enter a valid IPv6 address." -msgstr "Zapodajće płaćiwu IPv6-adresu." +msgid "IPv4" +msgstr "IPv4" -msgid "Enter a valid IPv4 or IPv6 address." -msgstr "Zapodajće płaćiwu IPv4- abo IPv6-adresu." +msgid "IPv6" +msgstr "IPv6" + +msgid "IPv4 or IPv6" +msgstr "IPv4 abo IPv6" msgid "Enter only digits separated by commas." msgstr "Zapodajće jenož přez komy dźělene cyfry," diff --git a/django/conf/locale/sk/LC_MESSAGES/django.mo b/django/conf/locale/sk/LC_MESSAGES/django.mo index e1f7271601..ddfe767cc9 100644 Binary files a/django/conf/locale/sk/LC_MESSAGES/django.mo and b/django/conf/locale/sk/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/sk/LC_MESSAGES/django.po b/django/conf/locale/sk/LC_MESSAGES/django.po index 14da7f96f2..e245cb14c5 100644 --- a/django/conf/locale/sk/LC_MESSAGES/django.po +++ b/django/conf/locale/sk/LC_MESSAGES/django.po @@ -1,7 +1,7 @@ # This file is distributed under the same license as the Django package. # # Translators: -# Adam Zahradník, 2023 +# Adam Zahradník, 2023-2024 # Jannis Leidel , 2011 # 18f25ad6fa9930fc67cb11aca9d16a27, 2012-2013 # Marian Andre , 2013,2015,2017-2018 @@ -14,9 +14,9 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-09-18 11:41-0300\n" -"PO-Revision-Date: 2023-12-04 06:49+0000\n" -"Last-Translator: Martin Tóth , 2017,2023\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-10-07 06:49+0000\n" +"Last-Translator: Adam Zahradník, 2023-2024\n" "Language-Team: Slovak (http://app.transifex.com/django/django/language/sk/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -351,6 +351,9 @@ msgstr "Stránka neobsahuje žiadne výsledky" msgid "Enter a valid value." msgstr "Zadajte platnú hodnotu." +msgid "Enter a valid domain name." +msgstr "Zadajte platný názov domény." + msgid "Enter a valid URL." msgstr "Zadajte platnú URL adresu." @@ -374,14 +377,18 @@ msgstr "" "Zadajte platnú skratku pozostávajúcu z písmen Unicode, čísel, " "podčiarkovníkov alebo pomlčiek." -msgid "Enter a valid IPv4 address." -msgstr "Zadajte platnú IPv4 adresu." +#, python-format +msgid "Enter a valid %(protocol)s address." +msgstr "Zadajte platnú %(protocol)s adresu." -msgid "Enter a valid IPv6 address." -msgstr "Zadajte platnú IPv6 adresu." +msgid "IPv4" +msgstr "IPv4" -msgid "Enter a valid IPv4 or IPv6 address." -msgstr "Zadajte platnú IPv4 alebo IPv6 adresu." +msgid "IPv6" +msgstr "IPv6" + +msgid "IPv4 or IPv6" +msgstr "IPv4 alebo IPv6" msgid "Enter only digits separated by commas." msgstr "Zadajte len číslice oddelené čiarkami." diff --git a/django/conf/locale/ug/LC_MESSAGES/django.mo b/django/conf/locale/ug/LC_MESSAGES/django.mo index 4b9aed37fd..ce10d7df7f 100644 Binary files a/django/conf/locale/ug/LC_MESSAGES/django.mo and b/django/conf/locale/ug/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/ug/LC_MESSAGES/django.po b/django/conf/locale/ug/LC_MESSAGES/django.po index ccaf4dae10..daa3973d8d 100644 --- a/django/conf/locale/ug/LC_MESSAGES/django.po +++ b/django/conf/locale/ug/LC_MESSAGES/django.po @@ -2,7 +2,7 @@ # # Translators: # abdl erkin <84247764@qq.com>, 2018 -# Abduqadir Abliz , 2023 +# Abduqadir Abliz , 2023-2024 # Abduqadir Abliz , 2023 # Azat, 2023 # Murat Orhun , 2023 @@ -10,9 +10,9 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-09-18 11:41-0300\n" -"PO-Revision-Date: 2023-12-04 06:49+0000\n" -"Last-Translator: Abduqadir Abliz , 2023\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-10-07 06:49+0000\n" +"Last-Translator: Abduqadir Abliz , 2023-2024\n" "Language-Team: Uyghur (http://app.transifex.com/django/django/language/ug/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -348,6 +348,9 @@ msgstr "ئۇ بەتتە ھېچقانداق نەتىجە يوق" msgid "Enter a valid value." msgstr "بىر ئىناۋەتلىك قىممەتنى تولدۇرۇڭ" +msgid "Enter a valid domain name." +msgstr "ئىناۋەتلىك دائىرە ئىسمى كىرگۈزۈلىدۇ." + msgid "Enter a valid URL." msgstr "ئىناۋەتلىك URL نى تولدۇرۇڭ" @@ -371,14 +374,18 @@ msgstr "" "يۇنىكودلۇق ھەرپ، سان، ئاستى سىزىق ياكى سىزىقچىلاردىن تەركىب تاپقان ئۈنۈملۈك " "«slug» نى كىرگۈزۈڭ." -msgid "Enter a valid IPv4 address." -msgstr "ئىناۋەتلىك IPv4 ئادرېسىنى كىرگۈزۈڭ." +#, python-format +msgid "Enter a valid %(protocol)s address." +msgstr "ئىناۋەتلىك %(protocol)sئادرېسى كىرگۈزۈلىدۇ." -msgid "Enter a valid IPv6 address." -msgstr "ئىناۋەتلىك IPv6 ئادرېسىنى كىرگۈزۈڭ." +msgid "IPv4" +msgstr "IPv4" -msgid "Enter a valid IPv4 or IPv6 address." -msgstr "ئىناۋەتلىك IPv4 ياكى IPv6 ئادرېسىنى كىرگۈزۈڭ." +msgid "IPv6" +msgstr "IPv6" + +msgid "IPv4 or IPv6" +msgstr "IPv4 ياكى IPv6" msgid "Enter only digits separated by commas." msgstr "پەش ئارقىلىق ئايرىلغان رەقەملەرنىلا كىرگۈزۈڭ." diff --git a/django/conf/locale/uk/LC_MESSAGES/django.mo b/django/conf/locale/uk/LC_MESSAGES/django.mo index 7c96efb7d8..0e9dc9f079 100644 Binary files a/django/conf/locale/uk/LC_MESSAGES/django.mo and b/django/conf/locale/uk/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/uk/LC_MESSAGES/django.po b/django/conf/locale/uk/LC_MESSAGES/django.po index 8f50fc4d95..b2a71874be 100644 --- a/django/conf/locale/uk/LC_MESSAGES/django.po +++ b/django/conf/locale/uk/LC_MESSAGES/django.po @@ -11,6 +11,7 @@ # Max V. Stotsky , 2014 # Mikhail Kolesnik , 2015 # Mykola Zamkovoi , 2014 +# Natalia, 2024 # Alex Bolotov , 2013-2014 # Roman Kozlovskyi , 2012 # Sergiy Kuzmenko , 2011 @@ -21,10 +22,10 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-01-17 02:13-0600\n" -"PO-Revision-Date: 2023-04-25 06:49+0000\n" -"Last-Translator: Illia Volochii , 2019,2021-2023\n" -"Language-Team: Ukrainian (http://www.transifex.com/django/django/language/" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-10-07 06:49+0000\n" +"Last-Translator: Natalia, 2024\n" +"Language-Team: Ukrainian (http://app.transifex.com/django/django/language/" "uk/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -311,6 +312,9 @@ msgstr "Татарська" msgid "Udmurt" msgstr "Удмуртська" +msgid "Uyghur" +msgstr "" + msgid "Ukrainian" msgstr "Українська" @@ -358,6 +362,9 @@ msgstr "Сторінка не містить результатів" msgid "Enter a valid value." msgstr "Введіть коректне значення." +msgid "Enter a valid domain name." +msgstr "" + msgid "Enter a valid URL." msgstr "Введіть коректний URL." @@ -379,14 +386,18 @@ msgstr "" "Введіть коректне значення 'slug' (короткого заголовку), що може містити " "тільки літери, числа, символи підкреслювання або дефіси." -msgid "Enter a valid IPv4 address." -msgstr "Введіть коректну IPv4 адресу." +#, python-format +msgid "Enter a valid %(protocol)s address." +msgstr "" -msgid "Enter a valid IPv6 address." -msgstr "Введіть дійсну IPv6 адресу." +msgid "IPv4" +msgstr "" -msgid "Enter a valid IPv4 or IPv6 address." -msgstr "Введіть дійсну IPv4 чи IPv6 адресу." +msgid "IPv6" +msgstr "" + +msgid "IPv4 or IPv6" +msgstr "" msgid "Enter only digits separated by commas." msgstr "Введіть тільки цифри, що розділені комами." @@ -409,6 +420,12 @@ msgstr "Переконайтеся, що це значення більше чи msgid "Ensure this value is a multiple of step size %(limit_value)s." msgstr "" +#, python-format +msgid "" +"Ensure this value is a multiple of step size %(limit_value)s, starting from " +"%(offset)s, e.g. %(offset)s, %(valid_value1)s, %(valid_value2)s, and so on." +msgstr "" + #, python-format msgid "" "Ensure this value has at least %(limit_value)d character (it has " @@ -799,7 +816,7 @@ msgid "Enter a complete value." msgstr "Введіть значення повністю." msgid "Enter a valid UUID." -msgstr "Введіть коректне значення UUID," +msgstr "Введіть коректне значення UUID." msgid "Enter a valid JSON." msgstr "Введіть коректний JSON." diff --git a/django/conf/locale/zh_Hans/LC_MESSAGES/django.mo b/django/conf/locale/zh_Hans/LC_MESSAGES/django.mo index 55517ea7b6..c68035c84d 100644 Binary files a/django/conf/locale/zh_Hans/LC_MESSAGES/django.mo and b/django/conf/locale/zh_Hans/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/zh_Hans/LC_MESSAGES/django.po b/django/conf/locale/zh_Hans/LC_MESSAGES/django.po index 097e77d4b4..012741fb5a 100644 --- a/django/conf/locale/zh_Hans/LC_MESSAGES/django.po +++ b/django/conf/locale/zh_Hans/LC_MESSAGES/django.po @@ -15,6 +15,7 @@ # Le Yang , 2018 # li beite , 2020 # Liping Wang , 2016-2017 +# L., 2024 # matthew Yip , 2020 # mozillazg , 2016 # Ronald White , 2014 @@ -33,6 +34,7 @@ # ced773123cfad7b4e8b79ca80f736af9, 2011-2012 # Ziya Tang , 2018 # 付峥 , 2018 +# L., 2024 # LatteFang <370358679@qq.com>, 2020 # Kevin Sze , 2012 # 高乐喆 , 2023 @@ -40,9 +42,9 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-09-18 11:41-0300\n" -"PO-Revision-Date: 2023-12-25 06:49+0000\n" -"Last-Translator: jack yang, 2023\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-10-07 06:49+0000\n" +"Last-Translator: L., 2024\n" "Language-Team: Chinese (China) (http://app.transifex.com/django/django/" "language/zh_CN/)\n" "MIME-Version: 1.0\n" @@ -377,6 +379,9 @@ msgstr "本页结果为空" msgid "Enter a valid value." msgstr "输入一个有效的值。" +msgid "Enter a valid domain name." +msgstr "输入一个有效的域名。" + msgid "Enter a valid URL." msgstr "输入一个有效的 URL。" @@ -396,14 +401,18 @@ msgid "" "hyphens." msgstr "输入由Unicode字母,数字,下划线或连字符号组成的有效“字段”。" -msgid "Enter a valid IPv4 address." -msgstr "输入一个有效的 IPv4 地址。" +#, python-format +msgid "Enter a valid %(protocol)s address." +msgstr "输入一个有效的 %(protocol)s 地址。" -msgid "Enter a valid IPv6 address." -msgstr "输入一个有效的 IPv6 地址。" +msgid "IPv4" +msgstr "IPv4" -msgid "Enter a valid IPv4 or IPv6 address." -msgstr "输入一个有效的 IPv4 或 IPv6 地址." +msgid "IPv6" +msgstr "IPv6" + +msgid "IPv4 or IPv6" +msgstr "IPv4 或 IPv6" msgid "Enter only digits separated by commas." msgstr "只能输入用逗号分隔的数字。" diff --git a/django/conf/project_template/project_name/settings.py-tpl b/django/conf/project_template/project_name/settings.py-tpl index 3b6caab333..5631ec9a31 100644 --- a/django/conf/project_template/project_name/settings.py-tpl +++ b/django/conf/project_template/project_name/settings.py-tpl @@ -58,7 +58,6 @@ TEMPLATES = [ 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ - 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', diff --git a/django/contrib/admin/locale/cs/LC_MESSAGES/django.mo b/django/contrib/admin/locale/cs/LC_MESSAGES/django.mo index d3bfdd7ea0..0dc5f9abc9 100644 Binary files a/django/contrib/admin/locale/cs/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/cs/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/cs/LC_MESSAGES/django.po b/django/contrib/admin/locale/cs/LC_MESSAGES/django.po index 65159b8166..39c3679caf 100644 --- a/django/contrib/admin/locale/cs/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/cs/LC_MESSAGES/django.po @@ -2,7 +2,9 @@ # # Translators: # Jannis Leidel , 2011 -# trendspotter , 2022 +# Jan Papež , 2024 +# Jiří Podhorecký , 2024 +# Jiří Podhorecký , 2022 # Jirka Vejrazka , 2011 # Tomáš Ehrlich , 2015 # Vláďa Macek , 2013-2014 @@ -12,10 +14,10 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-05-17 05:10-0500\n" -"PO-Revision-Date: 2022-07-25 07:05+0000\n" -"Last-Translator: trendspotter \n" -"Language-Team: Czech (http://www.transifex.com/django/django/language/cs/)\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-10-07 07:05+0000\n" +"Last-Translator: Jan Papež , 2024\n" +"Language-Team: Czech (http://app.transifex.com/django/django/language/cs/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -184,6 +186,9 @@ msgstr "" "Výběr více než jedné položky je možný přidržením klávesy \"Control\", na " "Macu \"Command\"." +msgid "Select this object for an action - {}" +msgstr "Vyberte tento objekt pro akci - {}" + #, python-brace-format msgid "The {name} “{obj}” was added successfully." msgstr "Položka typu {name} \"{obj}\" byla úspěšně přidána." @@ -205,12 +210,6 @@ msgstr "" "Položka typu {name} \"{obj}\" byla úspěšně změněna. Níže ji můžete dále " "upravovat." -#, python-brace-format -msgid "The {name} “{obj}” was added successfully. You may edit it again below." -msgstr "" -"Položka \"{obj}\" typu {name} byla úspěšně přidána. Níže ji můžete dále " -"upravovat." - #, python-brace-format msgid "" "The {name} “{obj}” was changed successfully. You may add another {name} " @@ -352,6 +351,9 @@ msgstr "Vybrat všechny položky typu %(module_name)s, celkem %(total_count)s." msgid "Clear selection" msgstr "Zrušit výběr" +msgid "Breadcrumbs" +msgstr "Drobečky" + #, python-format msgid "Models in the %(name)s application" msgstr "Modely v aplikaci %(name)s" @@ -378,16 +380,36 @@ msgstr "Zadejte uživatelské jméno a heslo." msgid "Change password" msgstr "Změnit heslo" -msgid "Please correct the error below." -msgstr "Opravte níže uvedenou chybu." +msgid "Set password" +msgstr "Nastavit heslo" -msgid "Please correct the errors below." -msgstr "Opravte níže uvedené chyby." +msgid "Please correct the error below." +msgid_plural "Please correct the errors below." +msgstr[0] "Opravte prosím níže uvedenou chybu." +msgstr[1] "Opravte prosím níže uvedené chyby." +msgstr[2] "Opravte prosím níže uvedené chyby." +msgstr[3] "Opravte prosím níže uvedené chyby." #, python-format msgid "Enter a new password for the user %(username)s." msgstr "Zadejte nové heslo pro uživatele %(username)s." +msgid "" +"This action will enable password-based authentication for " +"this user." +msgstr "" +"Tato akce povolí pro tohoto uživatele ověřování na základě " +"hesla." + +msgid "Disable password-based authentication" +msgstr "Zakázat ověřování pomocí hesla" + +msgid "Enable password-based authentication" +msgstr "Povolit ověřování pomocí hesla" + +msgid "Skip to main content" +msgstr "Přeskočit na hlavní obsah" + msgid "Welcome," msgstr "Vítejte, uživateli" @@ -413,6 +435,12 @@ msgstr "Zobrazení na webu" msgid "Filter" msgstr "Filtr" +msgid "Hide counts" +msgstr "Skrýt počty" + +msgid "Show counts" +msgstr "Zobrazit počty" + msgid "Clear all filters" msgstr "Zrušit všechny filtry" @@ -426,6 +454,15 @@ msgstr "Priorita řazení: %(priority_number)s" msgid "Toggle sorting" msgstr "Přehodit řazení" +msgid "Toggle theme (current theme: auto)" +msgstr "Přepnout motiv (aktuální motiv: auto)" + +msgid "Toggle theme (current theme: light)" +msgstr "Přepnout motiv (aktuální motiv: světlý)" + +msgid "Toggle theme (current theme: dark)" +msgstr "Přepnout motiv (aktuální motiv: tmavý)" + msgid "Delete" msgstr "Odstranit" @@ -512,6 +549,15 @@ msgstr "Moje akce" msgid "None available" msgstr "Nic" +msgid "Added:" +msgstr "Přidáno:" + +msgid "Changed:" +msgstr "Změněno:" + +msgid "Deleted:" +msgstr "Smazáno:" + msgid "Unknown content" msgstr "Neznámý obsah" @@ -538,6 +584,9 @@ msgstr "Zapomněli jste heslo nebo uživatelské jméno?" msgid "Toggle navigation" msgstr "Přehodit navigaci" +msgid "Sidebar" +msgstr "Boční panel" + msgid "Start typing to filter…" msgstr "Filtrovat začnete vepsáním textu..." @@ -554,10 +603,11 @@ msgid "Action" msgstr "Operace" msgid "entry" -msgstr "" - -msgid "entries" -msgstr "" +msgid_plural "entries" +msgstr[0] "položka" +msgstr[1] "položky" +msgstr[2] "položek" +msgstr[3] "položek" msgid "" "This object doesn’t have a change history. It probably wasn’t added via this " @@ -719,6 +769,9 @@ msgstr "E-mailová adresa:" msgid "Reset my password" msgstr "Obnovit heslo" +msgid "Select all objects on this page for an action" +msgstr "Vyberte všechny objekty na této stránce pro akci" + msgid "All dates" msgstr "Všechna data" diff --git a/django/contrib/admin/locale/cs/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/cs/LC_MESSAGES/djangojs.mo index 828f705ac7..db715bc9fc 100644 Binary files a/django/contrib/admin/locale/cs/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/cs/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/cs/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/cs/LC_MESSAGES/djangojs.po index f4b781881f..1e8fa73739 100644 --- a/django/contrib/admin/locale/cs/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/cs/LC_MESSAGES/djangojs.po @@ -2,7 +2,9 @@ # # Translators: # Jannis Leidel , 2011 -# trendspotter , 2022 +# Jan Papež , 2024 +# Jiří Podhorecký , 2024 +# Jiří Podhorecký , 2022 # Jirka Vejrazka , 2011 # Vláďa Macek , 2012,2014 # Vláďa Macek , 2015-2016,2020-2021 @@ -10,10 +12,10 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-05-17 05:26-0500\n" -"PO-Revision-Date: 2022-07-25 07:59+0000\n" -"Last-Translator: trendspotter \n" -"Language-Team: Czech (http://www.transifex.com/django/django/language/cs/)\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-10-07 07:59+0000\n" +"Last-Translator: Jan Papež , 2024\n" +"Language-Team: Czech (http://app.transifex.com/django/django/language/cs/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -67,6 +69,10 @@ msgstr "" "Seznam vybraných položek %s. Jednotlivě je lze odebrat tak, že na ně v " "rámečku klepnete a pak klepnete na šipku \"Odebrat mezi rámečky." +#, javascript-format +msgid "Type into this box to filter down the list of selected %s." +msgstr "Zadáním do tohoto pole vyfiltrujete seznam vybraných %s." + msgid "Remove all" msgstr "Odebrat vše" @@ -74,6 +80,14 @@ msgstr "Odebrat vše" msgid "Click to remove all chosen %s at once." msgstr "Chcete-li najednou odebrat všechny vybrané položky %s, klepněte sem." +#, javascript-format +msgid "%s selected option not visible" +msgid_plural "%s selected options not visible" +msgstr[0] "%s vybraná volba není viditelná" +msgstr[1] "%s vybrané volby nejsou viditelné" +msgstr[2] "%s vybrané volby nejsou viditelné" +msgstr[3] "%s vybrané volby nejsou viditelné" + msgid "%(sel)s of %(cnt)s selected" msgid_plural "%(sel)s of %(cnt)s selected" msgstr[0] "Vybrána je %(sel)s položka z celkem %(cnt)s." @@ -240,6 +254,55 @@ msgctxt "abbrev. month December" msgid "Dec" msgstr "Pro" +msgid "Sunday" +msgstr "Neděle" + +msgid "Monday" +msgstr "Pondělí" + +msgid "Tuesday" +msgstr "Úterý" + +msgid "Wednesday" +msgstr "Středa" + +msgid "Thursday" +msgstr "Čtvrtek" + +msgid "Friday" +msgstr "Pátek" + +msgid "Saturday" +msgstr "Sobota" + +msgctxt "abbrev. day Sunday" +msgid "Sun" +msgstr "Ned" + +msgctxt "abbrev. day Monday" +msgid "Mon" +msgstr "Pon" + +msgctxt "abbrev. day Tuesday" +msgid "Tue" +msgstr "Úte" + +msgctxt "abbrev. day Wednesday" +msgid "Wed" +msgstr "Stř" + +msgctxt "abbrev. day Thursday" +msgid "Thur" +msgstr "Čtv" + +msgctxt "abbrev. day Friday" +msgid "Fri" +msgstr "Pát" + +msgctxt "abbrev. day Saturday" +msgid "Sat" +msgstr "Sob" + msgctxt "one letter Sunday" msgid "S" msgstr "N" @@ -267,14 +330,3 @@ msgstr "P" msgctxt "one letter Saturday" msgid "S" msgstr "S" - -msgid "" -"You have already submitted this form. Are you sure you want to submit it " -"again?" -msgstr "Tento formulář jste již odeslali. Opravdu jej chcete odeslat znovu?" - -msgid "Show" -msgstr "Zobrazit" - -msgid "Hide" -msgstr "Skrýt" diff --git a/django/contrib/admin/locale/dsb/LC_MESSAGES/django.mo b/django/contrib/admin/locale/dsb/LC_MESSAGES/django.mo index db0f50af56..cc67569f54 100644 Binary files a/django/contrib/admin/locale/dsb/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/dsb/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/dsb/LC_MESSAGES/django.po b/django/contrib/admin/locale/dsb/LC_MESSAGES/django.po index 2c700c370f..31cbbba1e0 100644 --- a/django/contrib/admin/locale/dsb/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/dsb/LC_MESSAGES/django.po @@ -1,14 +1,14 @@ # This file is distributed under the same license as the Django package. # # Translators: -# Michael Wolf , 2016-2023 +# Michael Wolf , 2016-2024 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-09-18 11:41-0300\n" -"PO-Revision-Date: 2023-12-04 07:05+0000\n" -"Last-Translator: Michael Wolf , 2016-2023\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-10-07 07:05+0000\n" +"Last-Translator: Michael Wolf , 2016-2024\n" "Language-Team: Lower Sorbian (http://app.transifex.com/django/django/" "language/dsb/)\n" "MIME-Version: 1.0\n" @@ -199,11 +199,6 @@ msgid "" msgstr "" "{name} „{obj}“ jo se wuspěšnje změnił. Móžośo jen dołojce znowego wobźěłowaś." -#, python-brace-format -msgid "The {name} “{obj}” was added successfully. You may edit it again below." -msgstr "" -"{name} „{obj}“ jo se wuspěšnje pśidał. Móžośo jen dołojce znowego wobźěłowaś." - #, python-brace-format msgid "" "The {name} “{obj}” was changed successfully. You may add another {name} " @@ -373,6 +368,9 @@ msgstr "Zapódajśo wužywarske mě a gronidło." msgid "Change password" msgstr "Gronidło změniś" +msgid "Set password" +msgstr "Gronidło póstajiś" + msgid "Please correct the error below." msgid_plural "Please correct the errors below." msgstr[0] "Pšosym korigěrujśo slědujucu zmólku." @@ -384,6 +382,19 @@ msgstr[3] "Pšosym korigěrujśo slědujuce zmólki." msgid "Enter a new password for the user %(username)s." msgstr "Zapódajśo nowe gronidło za wužywarja %(username)s." +msgid "" +"This action will enable password-based authentication for " +"this user." +msgstr "" +"Toś ta akcija awtentifikaciju na zakłaźe gronidła za toś togo wužywarja " +" zmóžnijo ." + +msgid "Disable password-based authentication" +msgstr "Awtentifikaciju na zakłaźe gronidła znjemóžniś" + +msgid "Enable password-based authentication" +msgstr "Awtentifikaciju na zakłaźe gronidła zmóžniś" + msgid "Skip to main content" msgstr "Dalej ku głownemu wopśimjeśeju" diff --git a/django/contrib/admin/locale/es/LC_MESSAGES/django.mo b/django/contrib/admin/locale/es/LC_MESSAGES/django.mo index f1818ea1cd..379f20424b 100644 Binary files a/django/contrib/admin/locale/es/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/es/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/es/LC_MESSAGES/django.po b/django/contrib/admin/locale/es/LC_MESSAGES/django.po index 9872a80464..76348412b8 100644 --- a/django/contrib/admin/locale/es/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/es/LC_MESSAGES/django.po @@ -21,15 +21,15 @@ # Natalia, 2024 # Pablo, 2015 # Salomon Herrera, 2023 -# Uriel Medina , 2020-2023 +# Uriel Medina , 2020-2024 # Veronicabh , 2015 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-05-22 11:46-0300\n" -"PO-Revision-Date: 2024-08-07 07:05+0000\n" -"Last-Translator: Natalia, 2024\n" +"PO-Revision-Date: 2024-10-07 07:05+0000\n" +"Last-Translator: Uriel Medina , 2020-2024\n" "Language-Team: Spanish (http://app.transifex.com/django/django/language/" "es/)\n" "MIME-Version: 1.0\n" @@ -411,13 +411,13 @@ msgid "" "this user." msgstr "" "Esta acción habilitará la autenticación basada en " -"contraseñas para este usuario." +"contraseña para este usuario." msgid "Disable password-based authentication" -msgstr "Deshabilitar la autenticación basada en contraseñas" +msgstr "Deshabilitar la autenticación basada en contraseña" msgid "Enable password-based authentication" -msgstr "Habilitar la autenticación basada en contraseñas" +msgstr "Habilitar la autenticación basada en contraseña" msgid "Skip to main content" msgstr "Saltar al contenido principal" diff --git a/django/contrib/admin/locale/es_AR/LC_MESSAGES/django.mo b/django/contrib/admin/locale/es_AR/LC_MESSAGES/django.mo index 22f0ba35b2..6ffb9267c6 100644 Binary files a/django/contrib/admin/locale/es_AR/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/es_AR/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/es_AR/LC_MESSAGES/django.po b/django/contrib/admin/locale/es_AR/LC_MESSAGES/django.po index b340729d81..67a4918604 100644 --- a/django/contrib/admin/locale/es_AR/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/es_AR/LC_MESSAGES/django.po @@ -3,16 +3,16 @@ # Translators: # Jannis Leidel , 2011 # Leonardo José Guzmán , 2013 -# Natalia (Django Fellow), 2023 -# Natalia (Django Fellow), 2023 -# Ramiro Morales, 2013-2023 +# Natalia, 2023 +# Natalia, 2023 +# Ramiro Morales, 2013-2024 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-09-18 11:41-0300\n" -"PO-Revision-Date: 2023-12-04 07:05+0000\n" -"Last-Translator: Natalia (Django Fellow), 2023\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-10-07 07:05+0000\n" +"Last-Translator: Ramiro Morales, 2013-2024\n" "Language-Team: Spanish (Argentina) (http://app.transifex.com/django/django/" "language/es_AR/)\n" "MIME-Version: 1.0\n" @@ -205,10 +205,6 @@ msgid "" msgstr "" "Se modificó con éxito {name} \"{obj}”. Puede modificarlo/a nuevamente abajo." -#, python-brace-format -msgid "The {name} “{obj}” was added successfully. You may edit it again below." -msgstr "Se agregó con éxito {name} \"{obj}”. Puede modificarlo/a abajo." - #, python-brace-format msgid "" "The {name} “{obj}” was changed successfully. You may add another {name} " @@ -377,6 +373,9 @@ msgstr "Introduzca un nombre de usuario y una contraseña." msgid "Change password" msgstr "Cambiar contraseña" +msgid "Set password" +msgstr "Establecer contraseña" + msgid "Please correct the error below." msgid_plural "Please correct the errors below." msgstr[0] "Por favor, corrija el siguiente error." @@ -389,6 +388,19 @@ msgstr "" "Introduzca una nueva contraseña para el usuario %(username)s." +msgid "" +"This action will enable password-based authentication for " +"this user." +msgstr "" +"Esta acción activará la autenticación basada en contraseñas " +"para este usuario." + +msgid "Disable password-based authentication" +msgstr "Desactivar la autenticación basada en contraseñas" + +msgid "Enable password-based authentication" +msgstr "Activar la autenticación basada en contraseñas" + msgid "Skip to main content" msgstr "Ir al contenido principal" diff --git a/django/contrib/admin/locale/ga/LC_MESSAGES/django.mo b/django/contrib/admin/locale/ga/LC_MESSAGES/django.mo index 8c029af57b..4f4d286500 100644 Binary files a/django/contrib/admin/locale/ga/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/ga/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/ga/LC_MESSAGES/django.po b/django/contrib/admin/locale/ga/LC_MESSAGES/django.po index 252e50d065..bc55f33531 100644 --- a/django/contrib/admin/locale/ga/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/ga/LC_MESSAGES/django.po @@ -1,6 +1,7 @@ # This file is distributed under the same license as the Django package. # # Translators: +# Aindriú Mac Giolla Eoin, 2024 # Jannis Leidel , 2011 # Luke Blaney , 2019 # Michael Thornhill , 2011-2012,2015 @@ -8,10 +9,10 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-01-16 20:42+0100\n" -"PO-Revision-Date: 2019-06-22 21:17+0000\n" -"Last-Translator: Luke Blaney \n" -"Language-Team: Irish (http://www.transifex.com/django/django/language/ga/)\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-10-07 07:05+0000\n" +"Last-Translator: Aindriú Mac Giolla Eoin, 2024\n" +"Language-Team: Irish (http://app.transifex.com/django/django/language/ga/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -19,6 +20,10 @@ msgstr "" "Plural-Forms: nplurals=5; plural=(n==1 ? 0 : n==2 ? 1 : n<7 ? 2 : n<11 ? 3 : " "4);\n" +#, python-format +msgid "Delete selected %(verbose_name_plural)s" +msgstr "Scrios %(verbose_name_plural) roghnaithe" + #, python-format msgid "Successfully deleted %(count)d %(items)s." msgstr "D'éirigh le scriosadh %(count)d %(items)s." @@ -30,10 +35,6 @@ msgstr "Ní féidir scriosadh %(name)s " msgid "Are you sure?" msgstr "An bhfuil tú cinnte?" -#, python-format -msgid "Delete selected %(verbose_name_plural)s" -msgstr "Scrios %(verbose_name_plural) roghnaithe" - msgid "Administration" msgstr "Riarachán" @@ -70,6 +71,12 @@ msgstr "Gan dáta" msgid "Has date" msgstr "Le dáta" +msgid "Empty" +msgstr "Folamh" + +msgid "Not empty" +msgstr "Gan folamh" + #, python-format msgid "" "Please enter the correct %(username)s and password for a staff account. Note " @@ -89,7 +96,7 @@ msgid "Remove" msgstr "Tóg amach" msgid "Addition" -msgstr "" +msgstr "Suimiú" msgid "Change" msgstr "Athraigh" @@ -104,7 +111,7 @@ msgid "user" msgstr "úsáideoir" msgid "content type" -msgstr "" +msgstr "cineál ábhair" msgid "object id" msgstr "id oibiacht" @@ -127,23 +134,23 @@ msgid "log entries" msgstr "loga iontrálacha" #, python-format -msgid "Added \"%(object)s\"." -msgstr "\"%(object)s\" curtha isteach." +msgid "Added “%(object)s”." +msgstr "Curtha leis “%(object)s”." #, python-format -msgid "Changed \"%(object)s\" - %(changes)s" -msgstr "\"%(object)s\" - %(changes)s aithrithe" +msgid "Changed “%(object)s” — %(changes)s" +msgstr "Athraithe “%(object)s” — %(changes)s" #, python-format -msgid "Deleted \"%(object)s.\"" -msgstr "\"%(object)s.\" scrioste" +msgid "Deleted “%(object)s.”" +msgstr "Scriosta “%(object)s.”" msgid "LogEntry Object" msgstr "Oibiacht LogEntry" #, python-brace-format -msgid "Added {name} \"{object}\"." -msgstr "{name} curtha leis \"{object}\"." +msgid "Added {name} “{object}”." +msgstr "Cuireadh {name} “{object}”." msgid "Added." msgstr "Curtha leis." @@ -152,16 +159,16 @@ msgid "and" msgstr "agus" #, python-brace-format -msgid "Changed {fields} for {name} \"{object}\"." -msgstr "{fields} athrithe don {name} \"{object}\"." +msgid "Changed {fields} for {name} “{object}”." +msgstr "Athraíodh {fields} le haghaidh {name} “{object}”." #, python-brace-format msgid "Changed {fields}." msgstr "{fields} athrithe." #, python-brace-format -msgid "Deleted {name} \"{object}\"." -msgstr "{name} scrioste: \"{object}\"." +msgid "Deleted {name} “{object}”." +msgstr "Scriosadh {name} “{object}”." msgid "No fields changed." msgstr "Dada réimse aithraithe" @@ -169,48 +176,46 @@ msgstr "Dada réimse aithraithe" msgid "None" msgstr "Dada" -msgid "" -"Hold down \"Control\", or \"Command\" on a Mac, to select more than one." +msgid "Hold down “Control”, or “Command” on a Mac, to select more than one." msgstr "" -"Coinnigh síos \"Control\", nó \"Command\" ar Mac chun níos mó ná ceann " -"amháin a roghnú." +"Coinnigh síos “Rialú”, nó “Ordú” ar Mac, chun níos mó ná ceann amháin a " +"roghnú." + +msgid "Select this object for an action - {}" +msgstr "Roghnaigh an réad seo le haghaidh gnímh - {}" #, python-brace-format -msgid "The {name} \"{obj}\" was added successfully." -msgstr "Bhí {name} \"{obj}\" curtha leis go rathúil" +msgid "The {name} “{obj}” was added successfully." +msgstr "Cuireadh an {name} “{obj}” leis go rathúil." msgid "You may edit it again below." msgstr "Thig leat é a athrú arís faoi seo." #, python-brace-format msgid "" -"The {name} \"{obj}\" was added successfully. You may add another {name} " +"The {name} “{obj}” was added successfully. You may add another {name} below." +msgstr "" +"Cuireadh an {name} “{obj}” leis go rathúil. Is féidir leat {name} eile a " +"chur leis thíos." + +#, python-brace-format +msgid "" +"The {name} “{obj}” was changed successfully. You may edit it again below." +msgstr "" +"Athraíodh an {name} “{obj}” go rathúil. Is féidir leat é a chur in eagar " +"arís thíos." + +#, python-brace-format +msgid "" +"The {name} “{obj}” was changed successfully. You may add another {name} " "below." msgstr "" +"Athraíodh an {name} “{obj}” go rathúil. Is féidir leat {name} eile a chur " +"leis thíos." #, python-brace-format -msgid "" -"The {name} \"{obj}\" was changed successfully. You may edit it again below." -msgstr "" -"D'athraigh {name} \"{obj}\" go rathúil.\n" -"Thig leat é a athrú arís faoi seo." - -#, python-brace-format -msgid "" -"The {name} \"{obj}\" was added successfully. You may edit it again below." -msgstr "" - -#, python-brace-format -msgid "" -"The {name} \"{obj}\" was changed successfully. You may add another {name} " -"below." -msgstr "" -"D'athraigh {name} \"{obj}\" go rathúil.\n" -"Thig leat {name} eile a chuir leis." - -#, python-brace-format -msgid "The {name} \"{obj}\" was changed successfully." -msgstr "D'athraigh {name} \"{obj}\" go rathúil." +msgid "The {name} “{obj}” was changed successfully." +msgstr "Athraíodh an {name} “{obj}” go rathúil." msgid "" "Items must be selected in order to perform actions on them. No items have " @@ -223,12 +228,12 @@ msgid "No action selected." msgstr "Uimh gníomh roghnaithe." #, python-format -msgid "The %(name)s \"%(obj)s\" was deleted successfully." -msgstr "Bhí %(name)s \"%(obj)s\" scrioste go rathúil." +msgid "The %(name)s “%(obj)s” was deleted successfully." +msgstr "D'éirigh le scriosadh %(name)s \"%(obj)s\"." #, python-format -msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" -msgstr "Níl%(name)s ann le aitheantais \"%(key)s\". B'fhéidir gur scriosadh é?" +msgid "%(name)s with ID “%(key)s” doesn’t exist. Perhaps it was deleted?" +msgstr "Níl %(name)s le haitheantas “%(key)s” ann. B'fhéidir gur scriosadh é?" #, python-format msgid "Add %s" @@ -271,8 +276,9 @@ msgstr "0 as %(cnt)s roghnaithe." msgid "Change history: %s" msgstr "Athraigh stáir %s" -#. Translators: Model verbose name and instance representation, -#. suitable to be an item in a list. +#. Translators: Model verbose name and instance +#. representation, suitable to be an item in a +#. list. #, python-format msgid "%(class_name)s %(instance)s" msgstr "%(class_name)s %(instance)s" @@ -304,8 +310,8 @@ msgstr "%(app)s riaracháin" msgid "Page not found" msgstr "Ní bhfuarthas an leathanach" -msgid "We're sorry, but the requested page could not be found." -msgstr "Tá brón orainn, ach ní bhfuarthas an leathanach iarraite." +msgid "We’re sorry, but the requested page could not be found." +msgstr "Ár leithscéal, ach níorbh fhéidir an leathanach iarrtha a aimsiú." msgid "Home" msgstr "Baile" @@ -320,11 +326,11 @@ msgid "Server Error (500)" msgstr "Botún Freastalaí (500)" msgid "" -"There's been an error. It's been reported to the site administrators via " +"There’s been an error. It’s been reported to the site administrators via " "email and should be fixed shortly. Thanks for your patience." msgstr "" -"Tharla earráid. Tuairiscíodh don riarthóirí suíomh tríd an ríomhphost agus " -"ba chóir a shocrú go luath. Go raibh maith agat as do foighne." +"Tharla earráid. Tuairiscíodh do riarthóirí an tsuímh trí ríomhphost agus ba " +"cheart é a shocrú go luath. Go raibh maith agat as do foighne." msgid "Run the selected action" msgstr "Rith an gníomh roghnaithe" @@ -343,12 +349,26 @@ msgstr "Roghnaigh gach %(total_count)s %(module_name)s" msgid "Clear selection" msgstr "Scroiseadh modhnóir" +msgid "Breadcrumbs" +msgstr "Brioscáin aráin" + +#, python-format +msgid "Models in the %(name)s application" +msgstr "Samhlacha ins an %(name)s iarratais" + +msgid "Add" +msgstr "Cuir le" + +msgid "View" +msgstr "Amharc ar" + +msgid "You don’t have permission to view or edit anything." +msgstr "Níl cead agat aon rud a fheiceáil ná a chur in eagar." + msgid "" -"First, enter a username and password. Then, you'll be able to edit more user " +"First, enter a username and password. Then, you’ll be able to edit more user " "options." -msgstr "" -"Ar dtús, iontráil ainm úsaideoir agus focal faire. Ansin, beidh tú in ann " -"cuir in eagar níos mó roghaí úsaideoira." +msgstr "Níl cead agat aon rud a fheiceáil ná a chur in eagar." msgid "Enter a username and password." msgstr "Cuir isteach ainm úsáideora agus focal faire." @@ -356,11 +376,16 @@ msgstr "Cuir isteach ainm úsáideora agus focal faire." msgid "Change password" msgstr "Athraigh focal faire" -msgid "Please correct the error below." -msgstr "Ceartaigh an botún thíos le do thoil." +msgid "Set password" +msgstr "Socraigh pasfhocal" -msgid "Please correct the errors below." -msgstr "Le do thoil cheartú earráidí thíos." +msgid "Please correct the error below." +msgid_plural "Please correct the errors below." +msgstr[0] "Ceartaigh an earráid thíos le do thoil." +msgstr[1] "Ceartaigh na hearráidí thíos le do thoil." +msgstr[2] "Ceartaigh na hearráidí thíos le do thoil." +msgstr[3] "Ceartaigh na hearráidí thíos le do thoil." +msgstr[4] "Ceartaigh na hearráidí thíos le do thoil." #, python-format msgid "Enter a new password for the user %(username)s." @@ -368,6 +393,22 @@ msgstr "" "Iontráil focal faire nua le hadhaigh an úsaideor %(username)s." +msgid "" +"This action will enable password-based authentication for " +"this user." +msgstr "" +"Cumasóidh an gníomh seo fhíordheimhniú pasfhocal-bhunaithe " +"don úsáideoir seo." + +msgid "Disable password-based authentication" +msgstr "Díchumasaigh fíordheimhniú pasfhocal-bhunaithe" + +msgid "Enable password-based authentication" +msgstr "Cumasaigh fíordheimhniú pasfhocal-bhunaithe" + +msgid "Skip to main content" +msgstr "Téigh ar aghaidh chuig an bpríomhábhar" + msgid "Welcome," msgstr "Fáilte" @@ -393,6 +434,15 @@ msgstr "Breath ar suíomh" msgid "Filter" msgstr "Scagaire" +msgid "Hide counts" +msgstr "Folaigh comhaireamh" + +msgid "Show counts" +msgstr "Taispeáin comhaireamh" + +msgid "Clear all filters" +msgstr "Glan na scagairí go léir" + msgid "Remove from sorting" msgstr "Bain as sórtáil" @@ -403,6 +453,15 @@ msgstr "Sórtáil tosaíocht: %(priority_number)s" msgid "Toggle sorting" msgstr "Toggle sórtáil" +msgid "Toggle theme (current theme: auto)" +msgstr "Scoránaigh an téama (téama reatha: uathoibríoch)" + +msgid "Toggle theme (current theme: light)" +msgstr "Scoránaigh an téama (téama reatha: solas)" + +msgid "Toggle theme (current theme: dark)" +msgstr "Scoránaigh an téama (téama reatha: dorcha)" + msgid "Delete" msgstr "Cealaigh" @@ -434,8 +493,8 @@ msgstr "" msgid "Objects" msgstr "Oibiachtaí" -msgid "Yes, I'm sure" -msgstr "Táim cinnte" +msgid "Yes, I’m sure" +msgstr "Sea, táim cinnte" msgid "No, take me back" msgstr "Ní hea, tóg ar ais mé" @@ -469,9 +528,6 @@ msgstr "" "An bhfuil tú cinnte gur mian leat a scriosadh %(objects_name)s roghnaithe? " "Beidh gach ceann de na nithe seo a leanas agus a n-ítimí gaolta scroiste:" -msgid "View" -msgstr "Amharc ar" - msgid "Delete?" msgstr "Cealaigh?" @@ -482,46 +538,59 @@ msgstr " Trí %(filter_title)s " msgid "Summary" msgstr "Achoimre" -#, python-format -msgid "Models in the %(name)s application" -msgstr "Samhlacha ins an %(name)s iarratais" - -msgid "Add" -msgstr "Cuir le" - -msgid "You don't have permission to view or edit anything." -msgstr "" - msgid "Recent actions" -msgstr "" +msgstr "Gníomhartha le déanaí" msgid "My actions" -msgstr "" +msgstr "Mo ghníomhartha" msgid "None available" msgstr "Dada ar fáil" +msgid "Added:" +msgstr "Curtha leis:" + +msgid "Changed:" +msgstr "Athraithe:" + +msgid "Deleted:" +msgstr "Scriosta:" + msgid "Unknown content" msgstr "Inneachair anaithnid" msgid "" -"Something's wrong with your database installation. Make sure the appropriate " +"Something’s wrong with your database installation. Make sure the appropriate " "database tables have been created, and make sure the database is readable by " "the appropriate user." msgstr "" -"Tá rud éigin mícheart le suitéail do bunachar sonraí. Déan cinnte go bhfuil " -"boird an bunachar sonraI cruthaithe cheana, agus déan cinnte go bhfuil do " -"úsaideoir in ann an bunacchar sonraí a léamh." +"Tá rud éigin cearr le suiteáil do bhunachar sonraí. Cinntigh go bhfuil na " +"táblaí bunachar sonraí cuí cruthaithe, agus cinntigh go bhfuil an bunachar " +"sonraí inléite ag an úsáideoir cuí." #, python-format msgid "" "You are authenticated as %(username)s, but are not authorized to access this " "page. Would you like to login to a different account?" msgstr "" +"Tá tú fíordheimhnithe mar %(username)s, ach níl cead agat an leathanach seo " +"a rochtain. Ar mhaith leat logáil isteach i gcuntas eile?" msgid "Forgotten your password or username?" msgstr "Dearmad déanta ar do focal faire nó ainm úsaideora" +msgid "Toggle navigation" +msgstr "Scoránaigh an nascleanúint" + +msgid "Sidebar" +msgstr "Barra Taoibh" + +msgid "Start typing to filter…" +msgstr "Tosaigh ag clóscríobh chun an scagaire…" + +msgid "Filter navigation items" +msgstr "Scag míreanna nascleanúna" + msgid "Date/time" msgstr "Dáta/am" @@ -531,12 +600,20 @@ msgstr "Úsaideoir" msgid "Action" msgstr "Aicsean" +msgid "entry" +msgid_plural "entries" +msgstr[0] "iontráil" +msgstr[1] "iontrálacha" +msgstr[2] "iontrálacha" +msgstr[3] "iontrálacha" +msgstr[4] "iontrálacha" + msgid "" -"This object doesn't have a change history. It probably wasn't added via this " +"This object doesn’t have a change history. It probably wasn’t added via this " "admin site." msgstr "" -"Níl stáir aitraithe ag an oibiacht seo agús is dócha ná cuir le tríd an an " -"suíomh riarachán." +"Níl stair athruithe ag an réad seo. Is dócha nár cuireadh leis tríd an " +"suíomh riaracháin seo." msgid "Show all" msgstr "Taispéan gach rud" @@ -545,7 +622,7 @@ msgid "Save" msgstr "Sábháil" msgid "Popup closing…" -msgstr "" +msgstr "Preabfhuinneog ag dúnadh…" msgid "Search" msgstr "Cuardach" @@ -590,8 +667,14 @@ msgstr "Cuir le %(model)s" msgid "Delete selected %(model)s" msgstr "Scrios roghnaithe %(model)s" -msgid "Thanks for spending some quality time with the Web site today." -msgstr "Go raibh maith agat le hadhaigh do cuairt ar an suíomh idirlínn inniú." +#, python-format +msgid "View selected %(model)s" +msgstr "Féach ar %(model)s roghnaithe" + +msgid "Thanks for spending some quality time with the web site today." +msgstr "" +"Go raibh maith agat as roinnt ama ardchaighdeáin a chaitheamh leis an suíomh " +"Gréasáin inniu." msgid "Log in again" msgstr "Logáil isteacj arís" @@ -603,12 +686,12 @@ msgid "Your password was changed." msgstr "Bhí do focal faire aithraithe." msgid "" -"Please enter your old password, for security's sake, and then enter your new " +"Please enter your old password, for security’s sake, and then enter your new " "password twice so we can verify you typed it in correctly." msgstr "" -"Le do thoil, iontráil do sean-focal faire, ar son slándáil, agus ansin " -"iontráil do focal faire dhá uaire cé go mbeimid in ann a seiceal go bhfuil " -"sé scríobhte isteach i gceart." +"Cuir isteach do sheanphasfhocal, ar mhaithe le slándáil, agus ansin cuir " +"isteach do phasfhocal nua faoi dhó ionas gur féidir linn a fhíorú gur " +"chlóscríobh tú i gceart é." msgid "Change my password" msgstr "Athraigh mo focal faire" @@ -643,28 +726,35 @@ msgstr "" "úsaidte cheana. Le do thoil, iarr ar athsocraigh focal faire nua." msgid "" -"We've emailed you instructions for setting your password, if an account " +"We’ve emailed you instructions for setting your password, if an account " "exists with the email you entered. You should receive them shortly." msgstr "" +"Chuireamar ríomhphost chugat treoracha maidir le do phasfhocal a shocrú, má " +"tá cuntas ann leis an ríomhphost a chuir tú isteach. Ba cheart duit iad a " +"fháil go luath." msgid "" -"If you don't receive an email, please make sure you've entered the address " +"If you don’t receive an email, please make sure you’ve entered the address " "you registered with, and check your spam folder." msgstr "" +"Mura bhfaigheann tú ríomhphost, cinntigh le do thoil gur chuir tú isteach an " +"seoladh ar chláraigh tú leis, agus seiceáil d’fhillteán turscair." #, python-format msgid "" "You're receiving this email because you requested a password reset for your " "user account at %(site_name)s." msgstr "" +"Tá an ríomhphost seo á fháil agat toisc gur iarr tú athshocrú pasfhocail do " +"do chuntas úsáideora ag %(site_name)s." msgid "Please go to the following page and choose a new password:" msgstr "" "Le do thoil té go dtí an leathanach a leanúint agus roghmaigh focal faire " "nua:" -msgid "Your username, in case you've forgotten:" -msgstr "Do ainm úsaideoir, má tá dearmad déanta agat." +msgid "Your username, in case you’ve forgotten:" +msgstr "D’ainm úsáideora, ar eagla go bhfuil dearmad déanta agat ar:" msgid "Thanks for using our site!" msgstr "Go raibh maith agat le hadhaigh do cuairt!" @@ -674,9 +764,11 @@ msgid "The %(site_name)s team" msgstr "Foireann an %(site_name)s" msgid "" -"Forgotten your password? Enter your email address below, and we'll email " +"Forgotten your password? Enter your email address below, and we’ll email " "instructions for setting a new one." msgstr "" +"Dearmad déanta agat ar do phasfhocal? Cuir isteach do sheoladh ríomhphoist " +"thíos, agus seolfaimid treoracha ríomhphoist chun ceann nua a shocrú." msgid "Email address:" msgstr "Seoladh ríomhphoist:" @@ -684,6 +776,9 @@ msgstr "Seoladh ríomhphoist:" msgid "Reset my password" msgstr "Athsocraigh mo focal faire" +msgid "Select all objects on this page for an action" +msgstr "Roghnaigh gach oibiacht ar an leathanach seo le haghaidh gnímh" + msgid "All dates" msgstr "Gach dáta" @@ -697,7 +792,7 @@ msgstr "Roghnaigh %s a athrú" #, python-format msgid "Select %s to view" -msgstr "" +msgstr "Roghnaigh %s le féachaint" msgid "Date:" msgstr "Dáta:" diff --git a/django/contrib/admin/locale/ga/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/ga/LC_MESSAGES/djangojs.mo index ee000e278f..e46bd504c6 100644 Binary files a/django/contrib/admin/locale/ga/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/ga/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/ga/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/ga/LC_MESSAGES/djangojs.po index ce0a412d10..6f6e50dcc2 100644 --- a/django/contrib/admin/locale/ga/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/ga/LC_MESSAGES/djangojs.po @@ -1,6 +1,7 @@ # This file is distributed under the same license as the Django package. # # Translators: +# Aindriú Mac Giolla Eoin, 2024 # Jannis Leidel , 2011 # Luke Blaney , 2019 # Michael Thornhill , 2011-2012,2015 @@ -8,10 +9,10 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-05-17 11:50+0200\n" -"PO-Revision-Date: 2019-06-22 21:36+0000\n" -"Last-Translator: Luke Blaney \n" -"Language-Team: Irish (http://www.transifex.com/django/django/language/ga/)\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-10-07 07:59+0000\n" +"Last-Translator: Aindriú Mac Giolla Eoin, 2024\n" +"Language-Team: Irish (http://app.transifex.com/django/django/language/ga/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -66,6 +67,11 @@ msgstr "" "roghnaionn tú cuid acu sa bhosca thíos agus ansin cliceáil ar an saighead " "\"Bain\" idir an dá boscaí." +#, javascript-format +msgid "Type into this box to filter down the list of selected %s." +msgstr "" +"Clóscríobh isteach sa bhosca seo chun liosta na %s roghnaithe a scagadh." + msgid "Remove all" msgstr "Scrois gach ceann" @@ -73,6 +79,15 @@ msgstr "Scrois gach ceann" msgid "Click to remove all chosen %s at once." msgstr "Cliceáil anseo chun %s go léir roghnaithe a scroiseadh." +#, javascript-format +msgid "%s selected option not visible" +msgid_plural "%s selected options not visible" +msgstr[0] "Níl an rogha roghnaithe %s le feiceáil" +msgstr[1] "Níl %s roghanna roghnaithe le feiceáil" +msgstr[2] "Níl %s roghanna roghnaithe le feiceáil" +msgstr[3] "Níl %s roghanna roghnaithe le feiceáil" +msgstr[4] "Níl %s roghanna roghnaithe le feiceáil" + msgid "%(sel)s of %(cnt)s selected" msgid_plural "%(sel)s of %(cnt)s selected" msgstr[0] "%(sel)s de %(cnt)s roghnaithe" @@ -89,20 +104,21 @@ msgstr "" "gníomh, caillfidh tú do chuid aithrithe." msgid "" -"You have selected an action, but you haven't saved your changes to " -"individual fields yet. Please click OK to save. You'll need to re-run the " +"You have selected an action, but you haven’t saved your changes to " +"individual fields yet. Please click OK to save. You’ll need to re-run the " "action." msgstr "" -"Tá gníomh roghnaithe agat, ach níl do aithrithe sabhailte ar cuid de na " -"réímse. Clic OK chun iad a sábháil. Caithfidh tú an gníomh a rith arís." +"Tá gníomh roghnaithe agat, ach níor shábháil tú d'athruithe ar réimsí aonair " +"fós. Cliceáil OK le do thoil a shábháil. Beidh ort an t-aicsean a rith arís." msgid "" -"You have selected an action, and you haven't made any changes on individual " -"fields. You're probably looking for the Go button rather than the Save " +"You have selected an action, and you haven’t made any changes on individual " +"fields. You’re probably looking for the Go button rather than the Save " "button." msgstr "" -"Tá gníomh roghnaithe agat, ach níl do aithrithe sabhailte ar cuid de na " -"réímse. Is dócha go bhfuil tú ag iarraidh an cnaipe Té ná an cnaipe Sábháil." +"Tá gníomh roghnaithe agat, agus níl aon athruithe déanta agat ar réimsí " +"aonair. Is dócha go bhfuil an cnaipe Téigh á lorg agat seachas an cnaipe " +"Sábháil." msgid "Now" msgstr "Anois" @@ -199,6 +215,103 @@ msgstr "Samhain" msgid "December" msgstr "Nollaig" +msgctxt "abbrev. month January" +msgid "Jan" +msgstr "Ean" + +msgctxt "abbrev. month February" +msgid "Feb" +msgstr "Feabh" + +msgctxt "abbrev. month March" +msgid "Mar" +msgstr "Már" + +msgctxt "abbrev. month April" +msgid "Apr" +msgstr "Aib" + +msgctxt "abbrev. month May" +msgid "May" +msgstr "Beal" + +msgctxt "abbrev. month June" +msgid "Jun" +msgstr "Meith" + +msgctxt "abbrev. month July" +msgid "Jul" +msgstr "Lúil" + +msgctxt "abbrev. month August" +msgid "Aug" +msgstr "Lún" + +msgctxt "abbrev. month September" +msgid "Sep" +msgstr "Meán Fóm" + +msgctxt "abbrev. month October" +msgid "Oct" +msgstr "Deireadh Fóm" + +msgctxt "abbrev. month November" +msgid "Nov" +msgstr "Sam" + +msgctxt "abbrev. month December" +msgid "Dec" +msgstr "Noll" + +msgid "Sunday" +msgstr "Domhnach" + +msgid "Monday" +msgstr "Dé Luain" + +msgid "Tuesday" +msgstr "Dé Máirt" + +msgid "Wednesday" +msgstr "Dé Céadaoin" + +msgid "Thursday" +msgstr "Déardaoin" + +msgid "Friday" +msgstr "Dé hAoine" + +msgid "Saturday" +msgstr "Dé Sathairn" + +msgctxt "abbrev. day Sunday" +msgid "Sun" +msgstr "Dom" + +msgctxt "abbrev. day Monday" +msgid "Mon" +msgstr "Lua" + +msgctxt "abbrev. day Tuesday" +msgid "Tue" +msgstr "Mái" + +msgctxt "abbrev. day Wednesday" +msgid "Wed" +msgstr "Céa" + +msgctxt "abbrev. day Thursday" +msgid "Thur" +msgstr "Déa" + +msgctxt "abbrev. day Friday" +msgid "Fri" +msgstr "Aoi" + +msgctxt "abbrev. day Saturday" +msgid "Sat" +msgstr "Sat" + msgctxt "one letter Sunday" msgid "S" msgstr "D" @@ -226,9 +339,3 @@ msgstr "A" msgctxt "one letter Saturday" msgid "S" msgstr "S" - -msgid "Show" -msgstr "Taispeán" - -msgid "Hide" -msgstr "Folaigh" diff --git a/django/contrib/admin/locale/hsb/LC_MESSAGES/django.mo b/django/contrib/admin/locale/hsb/LC_MESSAGES/django.mo index e0e9215544..8d8267184e 100644 Binary files a/django/contrib/admin/locale/hsb/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/hsb/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/hsb/LC_MESSAGES/django.po b/django/contrib/admin/locale/hsb/LC_MESSAGES/django.po index 88d434108f..eca6bc4cf2 100644 --- a/django/contrib/admin/locale/hsb/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/hsb/LC_MESSAGES/django.po @@ -1,14 +1,14 @@ # This file is distributed under the same license as the Django package. # # Translators: -# Michael Wolf , 2016-2023 +# Michael Wolf , 2016-2024 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-09-18 11:41-0300\n" -"PO-Revision-Date: 2023-12-04 07:05+0000\n" -"Last-Translator: Michael Wolf , 2016-2023\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-10-07 07:05+0000\n" +"Last-Translator: Michael Wolf , 2016-2024\n" "Language-Team: Upper Sorbian (http://app.transifex.com/django/django/" "language/hsb/)\n" "MIME-Version: 1.0\n" @@ -199,10 +199,6 @@ msgid "" "The {name} “{obj}” was changed successfully. You may edit it again below." msgstr "{name} „{obj}“ je so wuspěšnje změnił. Móžeće jón deleka wobdźěłować." -#, python-brace-format -msgid "The {name} “{obj}” was added successfully. You may edit it again below." -msgstr "{name} „{obj}“ je so wuspěšnje přidał. Móžeće jón deleka wobdźěłować." - #, python-brace-format msgid "" "The {name} “{obj}” was changed successfully. You may add another {name} " @@ -372,6 +368,9 @@ msgstr "Zapodajće wužiwarske mjeno a hesło." msgid "Change password" msgstr "Hesło změnić" +msgid "Set password" +msgstr "Hesło postajić" + msgid "Please correct the error below." msgid_plural "Please correct the errors below." msgstr[0] "Prošu porjedźće slědowacy zmylk." @@ -383,6 +382,19 @@ msgstr[3] "Prošu porjedźće slědowace zmylki." msgid "Enter a new password for the user %(username)s." msgstr "Zapodajće nowe hesło za %(username)s." +msgid "" +"This action will enable password-based authentication for " +"this user." +msgstr "" +"Tuta akcija awtentifikacija na zakładźe hesła za tutoho wužiwarja " +"zmóžni ." + +msgid "Disable password-based authentication" +msgstr "Awtentifikaciju na zakładźe hesła znjemóžnić" + +msgid "Enable password-based authentication" +msgstr "Awtentifikaciju na zakładźe hesła zmóžnić" + msgid "Skip to main content" msgstr "Dale k hłownemu wobsahej" diff --git a/django/contrib/admin/locale/id/LC_MESSAGES/django.mo b/django/contrib/admin/locale/id/LC_MESSAGES/django.mo index 8dbc5b5eb6..dbba65c99a 100644 Binary files a/django/contrib/admin/locale/id/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/id/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/id/LC_MESSAGES/django.po b/django/contrib/admin/locale/id/LC_MESSAGES/django.po index a49fd66792..791412ed6a 100644 --- a/django/contrib/admin/locale/id/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/id/LC_MESSAGES/django.po @@ -3,7 +3,7 @@ # Translators: # Bayu Satiyo , 2024 # Claude Paroz , 2014 -# Fery Setiawan , 2015-2019,2021-2023 +# Fery Setiawan , 2015-2019,2021-2024 # Jannis Leidel , 2011 # M Asep Indrayana , 2015 # oon arfiandwi (OonID) , 2016,2020 @@ -16,8 +16,8 @@ msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-05-22 11:46-0300\n" -"PO-Revision-Date: 2013-04-25 07:05+0000\n" -"Last-Translator: Bayu Satiyo , 2024\n" +"PO-Revision-Date: 2024-10-07 07:05+0000\n" +"Last-Translator: Fery Setiawan , 2015-2019,2021-2024\n" "Language-Team: Indonesian (http://app.transifex.com/django/django/language/" "id/)\n" "MIME-Version: 1.0\n" @@ -372,7 +372,7 @@ msgid "Change password" msgstr "Ganti sandi" msgid "Set password" -msgstr "" +msgstr "Setel sandi" msgid "Please correct the error below." msgid_plural "Please correct the errors below." @@ -386,12 +386,14 @@ msgid "" "This action will enable password-based authentication for " "this user." msgstr "" +"Tindakan ini akan enable autentifikasi brerdasarkan-sandi " +"untuk pengguna ini." msgid "Disable password-based authentication" -msgstr "" +msgstr "Tiadakan autentifikasu berdasarkan-sandi" msgid "Enable password-based authentication" -msgstr "" +msgstr "Adakan autentifikasu berdasarkan-sandi" msgid "Skip to main content" msgstr "Lewati ke isi utama" diff --git a/django/contrib/admin/locale/ko/LC_MESSAGES/django.mo b/django/contrib/admin/locale/ko/LC_MESSAGES/django.mo index f8c12f3c52..2d1eb80a3d 100644 Binary files a/django/contrib/admin/locale/ko/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/ko/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/ko/LC_MESSAGES/django.po b/django/contrib/admin/locale/ko/LC_MESSAGES/django.po index d91718ce63..717f2d5995 100644 --- a/django/contrib/admin/locale/ko/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/ko/LC_MESSAGES/django.po @@ -22,13 +22,14 @@ # 정훈 이, 2021 # 박태진, 2021 # Yang Chan Woo , 2019 +# Youngkwang Yang, 2024 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-05-22 11:46-0300\n" -"PO-Revision-Date: 2024-08-07 07:05+0000\n" -"Last-Translator: Juyoung Lim, 2024\n" +"PO-Revision-Date: 2024-10-07 07:05+0000\n" +"Last-Translator: Youngkwang Yang, 2024\n" "Language-Team: Korean (http://app.transifex.com/django/django/language/ko/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -392,7 +393,7 @@ msgstr "비밀번호 설정" msgid "Please correct the error below." msgid_plural "Please correct the errors below." -msgstr[0] "아래 오류를 수정하기 바랍니다. " +msgstr[0] "아래 오류들을 수정하기 바랍니다. " #, python-format msgid "Enter a new password for the user %(username)s." @@ -438,10 +439,10 @@ msgid "Filter" msgstr "필터" msgid "Hide counts" -msgstr "" +msgstr "개수 숨기기" msgid "Show counts" -msgstr "" +msgstr "개수 표시" msgid "Clear all filters" msgstr "모든 필터 삭제" diff --git a/django/contrib/admin/locale/sk/LC_MESSAGES/django.mo b/django/contrib/admin/locale/sk/LC_MESSAGES/django.mo index 597165e2cf..dc548e5b07 100644 Binary files a/django/contrib/admin/locale/sk/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/sk/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/sk/LC_MESSAGES/django.po b/django/contrib/admin/locale/sk/LC_MESSAGES/django.po index f0ae985eb6..7e18ff8e65 100644 --- a/django/contrib/admin/locale/sk/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/sk/LC_MESSAGES/django.po @@ -1,7 +1,7 @@ # This file is distributed under the same license as the Django package. # # Translators: -# Adam Zahradník, 2023 +# Adam Zahradník, 2023-2024 # Jannis Leidel , 2011 # 18f25ad6fa9930fc67cb11aca9d16a27, 2012-2013 # Marian Andre , 2013-2015,2017 @@ -15,9 +15,9 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-09-18 11:41-0300\n" -"PO-Revision-Date: 2023-12-04 07:05+0000\n" -"Last-Translator: Martin Tóth , 2017,2023\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-10-07 07:05+0000\n" +"Last-Translator: Adam Zahradník, 2023-2024\n" "Language-Team: Slovak (http://app.transifex.com/django/django/language/sk/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -209,11 +209,6 @@ msgid "" msgstr "" "Objekt {name} „{obj}“ bol úspešne zmenený. Ďalšie zmeny môžete urobiť nižšie." -#, python-brace-format -msgid "The {name} “{obj}” was added successfully. You may edit it again below." -msgstr "" -"Objekt {name} „{obj}“ bol úspešne pridaný. Ďalšie zmeny môžete urobiť nižšie." - #, python-brace-format msgid "" "The {name} “{obj}” was changed successfully. You may add another {name} " @@ -383,6 +378,9 @@ msgstr "Zadajte používateľské meno a heslo." msgid "Change password" msgstr "Zmeniť heslo" +msgid "Set password" +msgstr "Nastaviť heslo" + msgid "Please correct the error below." msgid_plural "Please correct the errors below." msgstr[0] "Prosím, opravte chybu uvedenú nižšie." @@ -394,6 +392,19 @@ msgstr[3] "Prosím, opravte chyby uvedené nižšie." msgid "Enter a new password for the user %(username)s." msgstr "Zadajte nové heslo pre používateľa %(username)s." +msgid "" +"This action will enable password-based authentication for " +"this user." +msgstr "" +"Týmto povolíte tomuto používateľovi prihlasovanie pomocou " +"hesla." + +msgid "Disable password-based authentication" +msgstr "Vypnúť prihlasovanie pomocou hesla" + +msgid "Enable password-based authentication" +msgstr "Povoliť prihlasovanie pomocou hesla" + msgid "Skip to main content" msgstr "Preskočiť na hlavný obsah" diff --git a/django/contrib/admin/locale/ug/LC_MESSAGES/django.mo b/django/contrib/admin/locale/ug/LC_MESSAGES/django.mo index c517da6563..13b8d2c144 100644 Binary files a/django/contrib/admin/locale/ug/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/ug/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/ug/LC_MESSAGES/django.po b/django/contrib/admin/locale/ug/LC_MESSAGES/django.po index 5af1e41f54..bdac66a88d 100644 --- a/django/contrib/admin/locale/ug/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/ug/LC_MESSAGES/django.po @@ -3,18 +3,18 @@ # Translators: # abdl erkin <84247764@qq.com>, 2018 # ABDULLA , 2014 -# Abduqadir Abliz , 2023 +# Abduqadir Abliz , 2023-2024 # Azat, 2023 # Murat Orhun , 2023 -# Natalia (Django Fellow), 2023 +# Natalia, 2023 # Serpidin Uyghur, 2023 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-09-18 11:41-0300\n" -"PO-Revision-Date: 2023-12-04 07:05+0000\n" -"Last-Translator: Natalia (Django Fellow), 2023\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-10-07 07:05+0000\n" +"Last-Translator: Abduqadir Abliz , 2023-2024\n" "Language-Team: Uyghur (http://app.transifex.com/django/django/language/ug/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -204,12 +204,6 @@ msgstr "" "{name} “{obj}” مۇۋەپپەقىيەتلىك ئۆزگەرتىلدى. تۆۋەندە ئۇنى قايتا تەھرىرلىسىڭىز " "بولىدۇ." -#, python-brace-format -msgid "The {name} “{obj}” was added successfully. You may edit it again below." -msgstr "" -"{name} “{obj}” مۇۋەپپەقىيەتلىك قوشۇلدى. تۆۋەندە ئۇنى قايتا تەھرىرلىسىڭىز " -"بولىدۇ." - #, python-brace-format msgid "" "The {name} “{obj}” was changed successfully. You may add another {name} " @@ -376,6 +370,9 @@ msgstr "ئىشلەتكۈچى ئاتى ۋە پارول كىرگۈزۈڭ." msgid "Change password" msgstr "پارولنى ئۆزگەرتىش" +msgid "Set password" +msgstr "پارول تەڭشەك" + msgid "Please correct the error below." msgid_plural "Please correct the errors below." msgstr[0] "تۆۋەندىكى خاتالىقلارنى توغرىلاڭ." @@ -385,6 +382,19 @@ msgstr[1] "تۆۋەندىكى خاتالىقلارنى توغرىلاڭ." msgid "Enter a new password for the user %(username)s." msgstr "ئىشلەتكۈچى %(username)s ئۈچۈن يېڭى پارول كىرگۈزۈڭ." +msgid "" +"This action will enable password-based authentication for " +"this user." +msgstr "" +"بۇ مەشغۇلات مەزكۇر ئىشلەتكۈچىنىڭ ئىم ئاساسىدىكى دەلىللىشىنى " +"قوزغىتىدۇ" + +msgid "Disable password-based authentication" +msgstr "ئىم ئاساسىدىكى دەلىللەشنى چەكلەيدۇ" + +msgid "Enable password-based authentication" +msgstr "ئىم ئاساسىدىكى دەلىللەشنى قوزغىتىدۇ" + msgid "Skip to main content" msgstr "ئاساسلىق مەزمۇنغا ئاتلا" diff --git a/django/contrib/admin/static/admin/css/forms.css b/django/contrib/admin/static/admin/css/forms.css index 03914f2d59..faf196eddd 100644 --- a/django/contrib/admin/static/admin/css/forms.css +++ b/django/contrib/admin/static/admin/css/forms.css @@ -164,6 +164,10 @@ form .aligned div.help:last-child { padding-bottom: 0; } +form .aligned select option:checked { + background-color: var(--selected-row); +} + form .aligned ul li { list-style: none; } @@ -438,17 +442,6 @@ body.popup .submit-row { _width: 700px; } -.inline-group ul.tools { - padding: 0; - margin: 0; - list-style: none; -} - -.inline-group ul.tools li { - display: inline; - padding: 0 5px; -} - .inline-group div.add-row, .inline-group .tabular tr.add-row td { color: var(--body-quiet-color); @@ -462,11 +455,8 @@ body.popup .submit-row { border-bottom: 1px solid var(--hairline-color); } -.inline-group ul.tools a.add, .inline-group div.add-row a, .inline-group .tabular tr.add-row td a { - background: url(../img/icon-addlink.svg) 0 1px no-repeat; - padding-left: 16px; font-size: 0.75rem; } diff --git a/django/contrib/admin/static/admin/css/responsive_rtl.css b/django/contrib/admin/static/admin/css/responsive_rtl.css index 33b5784842..f7c90adb23 100644 --- a/django/contrib/admin/static/admin/css/responsive_rtl.css +++ b/django/contrib/admin/static/admin/css/responsive_rtl.css @@ -28,7 +28,6 @@ margin-left: 0; } - [dir="rtl"] .inline-group ul.tools a.add, [dir="rtl"] .inline-group div.add-row a, [dir="rtl"] .inline-group .tabular tr.add-row td a { padding: 8px 26px 8px 10px; diff --git a/django/contrib/admin/static/admin/js/inlines.js b/django/contrib/admin/static/admin/js/inlines.js index e9a1dfe122..a4246d6e12 100644 --- a/django/contrib/admin/static/admin/js/inlines.js +++ b/django/contrib/admin/static/admin/js/inlines.js @@ -50,11 +50,11 @@ // If forms are laid out as table rows, insert the // "add" button in a new table row: const numCols = $this.eq(-1).children().length; - $parent.append('' + options.addText + ""); + $parent.append('' + options.addText + ""); addButton = $parent.find("tr:last a"); } else { // Otherwise, insert it immediately after the last form: - $this.filter(":last").after('"); + $this.filter(":last").after('"); addButton = $this.filter(":last").next().find("a"); } } diff --git a/django/contrib/admin/templates/admin/auth/user/add_form.html b/django/contrib/admin/templates/admin/auth/user/add_form.html index 48406f11a2..a13e75e89a 100644 --- a/django/contrib/admin/templates/admin/auth/user/add_form.html +++ b/django/contrib/admin/templates/admin/auth/user/add_form.html @@ -3,9 +3,7 @@ {% block form_top %} {% if not is_popup %} -

{% translate 'First, enter a username and password. Then, you’ll be able to edit more user options.' %}

- {% else %} -

{% translate "Enter a username and password." %}

+

{% translate "After you've created a user, you’ll be able to edit more user options." %}

{% endif %} {% endblock %} {% block extrahead %} diff --git a/django/contrib/admin/templates/admin/base.html b/django/contrib/admin/templates/admin/base.html index 18e3a2a9fc..f4df6b30af 100644 --- a/django/contrib/admin/templates/admin/base.html +++ b/django/contrib/admin/templates/admin/base.html @@ -10,7 +10,6 @@ {% endblock %} {% if not is_popup and is_nav_sidebar_enabled %} - {% endif %} {% block extrastyle %}{% endblock %} {% if LANGUAGE_BIDI %}{% endif %} diff --git a/django/contrib/admin/templates/admin/login.html b/django/contrib/admin/templates/admin/login.html index 3999dd3703..fa0dcbc01d 100644 --- a/django/contrib/admin/templates/admin/login.html +++ b/django/contrib/admin/templates/admin/login.html @@ -57,7 +57,7 @@ {% url 'admin_password_reset' as password_reset_url %} {% if password_reset_url %} {% endif %}
diff --git a/django/contrib/admin/templates/admin/nav_sidebar.html b/django/contrib/admin/templates/admin/nav_sidebar.html index a413e23754..37cdac2184 100644 --- a/django/contrib/admin/templates/admin/nav_sidebar.html +++ b/django/contrib/admin/templates/admin/nav_sidebar.html @@ -1,4 +1,4 @@ -{% load i18n %} +{% load i18n static %} + diff --git a/django/contrib/admin/templates/registration/password_reset_email.html b/django/contrib/admin/templates/registration/password_reset_email.html index 64822091d1..2a428c48ce 100644 --- a/django/contrib/admin/templates/registration/password_reset_email.html +++ b/django/contrib/admin/templates/registration/password_reset_email.html @@ -5,7 +5,7 @@ {% block reset_link %} {{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %} {% endblock %} -{% translate 'Your username, in case you’ve forgotten:' %} {{ user.get_username }} +{% translate 'In case you’ve forgotten, you are:' %} {{ user.get_username }} {% translate "Thanks for using our site!" %} diff --git a/django/contrib/admindocs/locale/ga/LC_MESSAGES/django.mo b/django/contrib/admindocs/locale/ga/LC_MESSAGES/django.mo index ebd4321e4b..201694dd51 100644 Binary files a/django/contrib/admindocs/locale/ga/LC_MESSAGES/django.mo and b/django/contrib/admindocs/locale/ga/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admindocs/locale/ga/LC_MESSAGES/django.po b/django/contrib/admindocs/locale/ga/LC_MESSAGES/django.po index d786e704f6..5fde076b13 100644 --- a/django/contrib/admindocs/locale/ga/LC_MESSAGES/django.po +++ b/django/contrib/admindocs/locale/ga/LC_MESSAGES/django.po @@ -1,6 +1,7 @@ # This file is distributed under the same license as the Django package. # # Translators: +# Aindriú Mac Giolla Eoin, 2024 # Jannis Leidel , 2011 # Luke Blaney , 2019 # Michael Thornhill , 2012 @@ -8,10 +9,10 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-01-19 16:49+0100\n" -"PO-Revision-Date: 2019-06-22 21:37+0000\n" -"Last-Translator: Luke Blaney \n" -"Language-Team: Irish (http://www.transifex.com/django/django/language/ga/)\n" +"POT-Creation-Date: 2021-01-15 09:00+0100\n" +"PO-Revision-Date: 2024-10-07 20:19+0000\n" +"Last-Translator: Aindriú Mac Giolla Eoin, 2024\n" +"Language-Team: Irish (http://app.transifex.com/django/django/language/ga/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -20,7 +21,7 @@ msgstr "" "4);\n" msgid "Administrative Documentation" -msgstr "" +msgstr "Doiciméadúchán Riaracháin" msgid "Home" msgstr "Baile" @@ -39,6 +40,10 @@ msgid "" "click the link and add it to your bookmarks. Now you can select the " "bookmarklet from any page in the site." msgstr "" +"Chun leabharmharcanna a shuiteáil, tarraing an nasc chuig do bharra uirlisí " +"leabharmharcanna, nó deaschliceáil ar an nasc agus cuir le do " +"leabharmharcanna é. Anois is féidir leat an leabharmharc a roghnú ó aon " +"leathanach ar an suíomh." msgid "Documentation for this page" msgstr "Doiciméadúchán le hadhaigh an leathanach seo" @@ -54,7 +59,7 @@ msgid "Tags" msgstr "Clibeanna" msgid "List of all the template tags and their functions." -msgstr "" +msgstr "Liosta de na clibeanna teimpléid go léir agus a bhfeidhmeanna." msgid "Filters" msgstr "Scagairí" @@ -63,6 +68,8 @@ msgid "" "Filters are actions which can be applied to variables in a template to alter " "the output." msgstr "" +"Is gníomhartha iad na scagairí is féidir a chur i bhfeidhm ar athróga i " +"dteimpléad chun an t-aschur a athrú." msgid "Models" msgstr "Samhla" @@ -72,6 +79,9 @@ msgid "" "associated fields. Each model has a list of fields which can be accessed as " "template variables" msgstr "" +"Is éard atá i múnlaí ná cur síos ar na réada go léir sa chóras agus ar na " +"réimsí a bhaineann leo. Tá liosta réimsí ag gach samhail ar féidir rochtain " +"a fháil orthu mar athróga teimpléid" msgid "Views" msgstr "Radharcanna" @@ -81,138 +91,149 @@ msgid "" "template is used to generate the page and which objects are available to " "that template." msgstr "" +"Gintear gach leathanach ar an suíomh poiblí trí radharc. Sainmhíníonn an t-" +"amharc cén teimpléad a úsáidtear chun an leathanach a ghiniúint agus na " +"rudaí atá ar fáil don teimpléad sin." msgid "Tools for your browser to quickly access admin functionality." msgstr "" +"Uirlisí do do bhrabhsálaí chun rochtain tapa a fháil ar fheidhmiúlacht " +"riaracháin." msgid "Please install docutils" -msgstr "" +msgstr "Suiteáil docutils le do thoil" #, python-format msgid "" -"The admin documentation system requires Python's docutils library." +"The admin documentation system requires Python’s docutils library." msgstr "" +"Teastaíonn leabharlann Python docutils ón gcóras " +"doiciméadúcháin riaracháin." #, python-format msgid "" "Please ask your administrators to install docutils." -msgstr "" +msgstr "Iarr ar do riarthóirí docutils a shuiteáil." #, python-format msgid "Model: %(name)s" -msgstr "" +msgstr "Múnla: %(name)s" msgid "Fields" -msgstr "" +msgstr "Réimsí" msgid "Field" -msgstr "" +msgstr "Réimse" msgid "Type" msgstr "Cineál" msgid "Description" -msgstr "" +msgstr "Cur síos" msgid "Methods with arguments" -msgstr "" +msgstr "Modhanna le hargóintí" msgid "Method" -msgstr "" +msgstr "Modh" msgid "Arguments" -msgstr "" +msgstr "Argóintí" msgid "Back to Model documentation" -msgstr "" +msgstr "Ar ais chuig an doiciméadú Múnla" msgid "Model documentation" -msgstr "" +msgstr "Doiciméadú múnla" msgid "Model groups" -msgstr "" +msgstr "Grúpaí samhlacha" msgid "Templates" msgstr "Teimpléid" #, python-format msgid "Template: %(name)s" -msgstr "" +msgstr "Teimpléad: %(name)s" #, python-format -msgid "Template: \"%(name)s\"" -msgstr "" +msgid "Template: %(name)s" +msgstr "Teimpléad: %(name)s" #. Translators: Search is not a verb here, it qualifies path (a search path) #, python-format -msgid "Search path for template \"%(name)s\":" -msgstr "" +msgid "Search path for template %(name)s:" +msgstr "Teimpléad cuardaigh cosán %(name)s:" msgid "(does not exist)" -msgstr "" +msgstr "(níl ann)" msgid "Back to Documentation" -msgstr "" +msgstr "Ar ais go Doiciméadúchán" msgid "Template filters" -msgstr "" +msgstr "Ar ais go Doiciméadúchán" msgid "Template filter documentation" -msgstr "" +msgstr "Doiciméadú scagaire teimpléid" msgid "Built-in filters" -msgstr "" +msgstr "scagairí ionsuite" #, python-format msgid "" "To use these filters, put %(code)s in your template before " "using the filter." msgstr "" +"Chun na scagairí seo a úsáid, cuir%(code)s i do theimpléad sula " +"n-úsáideann tú an scagaire." msgid "Template tags" -msgstr "" +msgstr "Clibeanna teimpléid" msgid "Template tag documentation" -msgstr "" +msgstr "Doiciméadú clib teimpléad" msgid "Built-in tags" -msgstr "" +msgstr "Insuite i clibeanna" #, python-format msgid "" "To use these tags, put %(code)s in your template before using " "the tag." msgstr "" +"Chun na clibeanna seo a úsáid, cuir %(code)s i do theimpléad " +"sula n-úsáideann tú an chlib." #, python-format msgid "View: %(name)s" -msgstr "" +msgstr "Amharc: %(name)s" msgid "Context:" -msgstr "" +msgstr "Comhthéacs:" msgid "Templates:" -msgstr "" +msgstr "Teimpléid:" msgid "Back to View documentation" -msgstr "" +msgstr "Ar ais go dtí Féach ar na doiciméid" msgid "View documentation" -msgstr "" +msgstr "Féach ar cháipéisíocht" msgid "Jump to namespace" -msgstr "" +msgstr "Léim go spás ainm" msgid "Empty namespace" -msgstr "" +msgstr "Ainmspás folamh" #, python-format msgid "Views by namespace %(name)s" -msgstr "" +msgstr "Radhairc de réir ainmspáis %(name)s" msgid "Views by empty namespace" -msgstr "" +msgstr "Radhairc de réir ainmspás folamh" #, python-format msgid "" @@ -220,6 +241,9 @@ msgid "" " View function: %(full_name)s. Name: %(url_name)s.\n" msgstr "" +"\n" +" Féach ar fheidhm: %(full_name)s. Ainm: %(url_name)s.\n" msgid "tag:" msgstr "clib:" @@ -232,7 +256,7 @@ msgstr "radharc:" #, python-format msgid "App %(app_label)r not found" -msgstr "" +msgstr "Aip %(app_label)r gan aimsiú" #, python-format msgid "Model %(model_name)r not found in app %(app_label)r" diff --git a/django/contrib/auth/__init__.py b/django/contrib/auth/__init__.py index 3db1445d9e..689567ca6c 100644 --- a/django/contrib/auth/__init__.py +++ b/django/contrib/auth/__init__.py @@ -1,8 +1,6 @@ import inspect import re -from asgiref.sync import sync_to_async - from django.apps import apps as django_apps from django.conf import settings from django.core.exceptions import ImproperlyConfigured, PermissionDenied @@ -40,6 +38,39 @@ def get_backends(): return _get_backends(return_tuples=False) +def _get_compatible_backends(request, **credentials): + for backend, backend_path in _get_backends(return_tuples=True): + backend_signature = inspect.signature(backend.authenticate) + try: + backend_signature.bind(request, **credentials) + except TypeError: + # This backend doesn't accept these credentials as arguments. Try + # the next one. + continue + yield backend, backend_path + + +def _get_backend_from_user(user, backend=None): + try: + backend = backend or user.backend + except AttributeError: + backends = _get_backends(return_tuples=True) + if len(backends) == 1: + _, backend = backends[0] + else: + raise ValueError( + "You have multiple authentication backends configured and " + "therefore must provide the `backend` argument or set the " + "`backend` attribute on the user." + ) + else: + if not isinstance(backend, str): + raise TypeError( + "backend must be a dotted import path string (got %r)." % backend + ) + return backend + + @sensitive_variables("credentials") def _clean_credentials(credentials): """ @@ -62,19 +93,21 @@ def _get_user_session_key(request): return get_user_model()._meta.pk.to_python(request.session[SESSION_KEY]) +async def _aget_user_session_key(request): + # This value in the session is always serialized to a string, so we need + # to convert it back to Python whenever we access it. + session_key = await request.session.aget(SESSION_KEY) + if session_key is None: + raise KeyError() + return get_user_model()._meta.pk.to_python(session_key) + + @sensitive_variables("credentials") def authenticate(request=None, **credentials): """ If the given credentials are valid, return a User object. """ - for backend, backend_path in _get_backends(return_tuples=True): - backend_signature = inspect.signature(backend.authenticate) - try: - backend_signature.bind(request, **credentials) - except TypeError: - # This backend doesn't accept these credentials as arguments. Try - # the next one. - continue + for backend, backend_path in _get_compatible_backends(request, **credentials): try: user = backend.authenticate(request, **credentials) except PermissionDenied: @@ -96,7 +129,23 @@ def authenticate(request=None, **credentials): @sensitive_variables("credentials") async def aauthenticate(request=None, **credentials): """See authenticate().""" - return await sync_to_async(authenticate)(request, **credentials) + for backend, backend_path in _get_compatible_backends(request, **credentials): + try: + user = await backend.aauthenticate(request, **credentials) + except PermissionDenied: + # This backend says to stop in our tracks - this user should not be + # allowed in at all. + break + if user is None: + continue + # Annotate the user object with the path of the backend. + user.backend = backend_path + return user + + # The credentials supplied are invalid to all backends, fire signal. + await user_login_failed.asend( + sender=__name__, credentials=_clean_credentials(credentials), request=request + ) def login(request, user, backend=None): @@ -125,23 +174,7 @@ def login(request, user, backend=None): else: request.session.cycle_key() - try: - backend = backend or user.backend - except AttributeError: - backends = _get_backends(return_tuples=True) - if len(backends) == 1: - _, backend = backends[0] - else: - raise ValueError( - "You have multiple authentication backends configured and " - "therefore must provide the `backend` argument or set the " - "`backend` attribute on the user." - ) - else: - if not isinstance(backend, str): - raise TypeError( - "backend must be a dotted import path string (got %r)." % backend - ) + backend = _get_backend_from_user(user=user, backend=backend) request.session[SESSION_KEY] = user._meta.pk.value_to_string(user) request.session[BACKEND_SESSION_KEY] = backend @@ -154,7 +187,36 @@ def login(request, user, backend=None): async def alogin(request, user, backend=None): """See login().""" - return await sync_to_async(login)(request, user, backend) + session_auth_hash = "" + if user is None: + user = await request.auser() + if hasattr(user, "get_session_auth_hash"): + session_auth_hash = user.get_session_auth_hash() + + if await request.session.ahas_key(SESSION_KEY): + if await _aget_user_session_key(request) != user.pk or ( + session_auth_hash + and not constant_time_compare( + await request.session.aget(HASH_SESSION_KEY, ""), + session_auth_hash, + ) + ): + # To avoid reusing another user's session, create a new, empty + # session if the existing session corresponds to a different + # authenticated user. + await request.session.aflush() + else: + await request.session.acycle_key() + + backend = _get_backend_from_user(user=user, backend=backend) + + await request.session.aset(SESSION_KEY, user._meta.pk.value_to_string(user)) + await request.session.aset(BACKEND_SESSION_KEY, backend) + await request.session.aset(HASH_SESSION_KEY, session_auth_hash) + if hasattr(request, "user"): + request.user = user + rotate_token(request) + await user_logged_in.asend(sender=user.__class__, request=request, user=user) def logout(request): @@ -177,7 +239,19 @@ def logout(request): async def alogout(request): """See logout().""" - return await sync_to_async(logout)(request) + # Dispatch the signal before the user is logged out so the receivers have a + # chance to find out *who* logged out. + user = getattr(request, "auser", None) + if user is not None: + user = await user() + if not getattr(user, "is_authenticated", True): + user = None + await user_logged_out.asend(sender=user.__class__, request=request, user=user) + await request.session.aflush() + if hasattr(request, "user"): + from django.contrib.auth.models import AnonymousUser + + request.user = AnonymousUser() def get_user_model(): @@ -243,7 +317,43 @@ def get_user(request): async def aget_user(request): """See get_user().""" - return await sync_to_async(get_user)(request) + from .models import AnonymousUser + + user = None + try: + user_id = await _aget_user_session_key(request) + backend_path = await request.session.aget(BACKEND_SESSION_KEY) + except KeyError: + pass + else: + if backend_path in settings.AUTHENTICATION_BACKENDS: + backend = load_backend(backend_path) + user = await backend.aget_user(user_id) + # Verify the session + if hasattr(user, "get_session_auth_hash"): + session_hash = await request.session.aget(HASH_SESSION_KEY) + if not session_hash: + session_hash_verified = False + else: + session_auth_hash = user.get_session_auth_hash() + session_hash_verified = session_hash and constant_time_compare( + session_hash, user.get_session_auth_hash() + ) + if not session_hash_verified: + # If the current secret does not verify the session, try + # with the fallback secrets and stop when a matching one is + # found. + if session_hash and any( + constant_time_compare(session_hash, fallback_auth_hash) + for fallback_auth_hash in user.get_session_auth_fallback_hash() + ): + await request.session.acycle_key() + await request.session.aset(HASH_SESSION_KEY, session_auth_hash) + else: + await request.session.aflush() + user = None + + return user or AnonymousUser() def get_permission_codename(action, opts): diff --git a/django/contrib/auth/backends.py b/django/contrib/auth/backends.py index dd3c2e527b..8352530665 100644 --- a/django/contrib/auth/backends.py +++ b/django/contrib/auth/backends.py @@ -1,3 +1,5 @@ +from asgiref.sync import sync_to_async + from django.contrib.auth import get_user_model from django.contrib.auth.models import Permission from django.db.models import Exists, OuterRef, Q @@ -9,24 +11,45 @@ class BaseBackend: def authenticate(self, request, **kwargs): return None + async def aauthenticate(self, request, **kwargs): + return await sync_to_async(self.authenticate)(request, **kwargs) + def get_user(self, user_id): return None + async def aget_user(self, user_id): + return await sync_to_async(self.get_user)(user_id) + def get_user_permissions(self, user_obj, obj=None): return set() + async def aget_user_permissions(self, user_obj, obj=None): + return await sync_to_async(self.get_user_permissions)(user_obj, obj) + def get_group_permissions(self, user_obj, obj=None): return set() + async def aget_group_permissions(self, user_obj, obj=None): + return await sync_to_async(self.get_group_permissions)(user_obj, obj) + def get_all_permissions(self, user_obj, obj=None): return { *self.get_user_permissions(user_obj, obj=obj), *self.get_group_permissions(user_obj, obj=obj), } + async def aget_all_permissions(self, user_obj, obj=None): + return { + *await self.aget_user_permissions(user_obj, obj=obj), + *await self.aget_group_permissions(user_obj, obj=obj), + } + def has_perm(self, user_obj, perm, obj=None): return perm in self.get_all_permissions(user_obj, obj=obj) + async def ahas_perm(self, user_obj, perm, obj=None): + return perm in await self.aget_all_permissions(user_obj, obj) + class ModelBackend(BaseBackend): """ @@ -48,6 +71,23 @@ class ModelBackend(BaseBackend): if user.check_password(password) and self.user_can_authenticate(user): return user + async def aauthenticate(self, request, username=None, password=None, **kwargs): + if username is None: + username = kwargs.get(UserModel.USERNAME_FIELD) + if username is None or password is None: + return + try: + user = await UserModel._default_manager.aget_by_natural_key(username) + except UserModel.DoesNotExist: + # Run the default password hasher once to reduce the timing + # difference between an existing and a nonexistent user (#20760). + UserModel().set_password(password) + else: + if await user.acheck_password(password) and self.user_can_authenticate( + user + ): + return user + def user_can_authenticate(self, user): """ Reject users with is_active=False. Custom user models that don't have @@ -59,9 +99,7 @@ class ModelBackend(BaseBackend): return user_obj.user_permissions.all() def _get_group_permissions(self, user_obj): - user_groups_field = get_user_model()._meta.get_field("groups") - user_groups_query = "group__%s" % user_groups_field.related_query_name() - return Permission.objects.filter(**{user_groups_query: user_obj}) + return Permission.objects.filter(group__in=user_obj.groups.all()) def _get_permissions(self, user_obj, obj, from_name): """ @@ -84,6 +122,25 @@ class ModelBackend(BaseBackend): ) return getattr(user_obj, perm_cache_name) + async def _aget_permissions(self, user_obj, obj, from_name): + """See _get_permissions().""" + if not user_obj.is_active or user_obj.is_anonymous or obj is not None: + return set() + + perm_cache_name = "_%s_perm_cache" % from_name + if not hasattr(user_obj, perm_cache_name): + if user_obj.is_superuser: + perms = Permission.objects.all() + else: + perms = getattr(self, "_get_%s_permissions" % from_name)(user_obj) + perms = perms.values_list("content_type__app_label", "codename").order_by() + setattr( + user_obj, + perm_cache_name, + {"%s.%s" % (ct, name) async for ct, name in perms}, + ) + return getattr(user_obj, perm_cache_name) + def get_user_permissions(self, user_obj, obj=None): """ Return a set of permission strings the user `user_obj` has from their @@ -91,6 +148,10 @@ class ModelBackend(BaseBackend): """ return self._get_permissions(user_obj, obj, "user") + async def aget_user_permissions(self, user_obj, obj=None): + """See get_user_permissions().""" + return await self._aget_permissions(user_obj, obj, "user") + def get_group_permissions(self, user_obj, obj=None): """ Return a set of permission strings the user `user_obj` has from the @@ -98,6 +159,10 @@ class ModelBackend(BaseBackend): """ return self._get_permissions(user_obj, obj, "group") + async def aget_group_permissions(self, user_obj, obj=None): + """See get_group_permissions().""" + return await self._aget_permissions(user_obj, obj, "group") + def get_all_permissions(self, user_obj, obj=None): if not user_obj.is_active or user_obj.is_anonymous or obj is not None: return set() @@ -108,6 +173,9 @@ class ModelBackend(BaseBackend): def has_perm(self, user_obj, perm, obj=None): return user_obj.is_active and super().has_perm(user_obj, perm, obj=obj) + async def ahas_perm(self, user_obj, perm, obj=None): + return user_obj.is_active and await super().ahas_perm(user_obj, perm, obj=obj) + def has_module_perms(self, user_obj, app_label): """ Return True if user_obj has any permissions in the given app_label. @@ -117,6 +185,13 @@ class ModelBackend(BaseBackend): for perm in self.get_all_permissions(user_obj) ) + async def ahas_module_perms(self, user_obj, app_label): + """See has_module_perms()""" + return user_obj.is_active and any( + perm[: perm.index(".")] == app_label + for perm in await self.aget_all_permissions(user_obj) + ) + def with_perm(self, perm, is_active=True, include_superusers=True, obj=None): """ Return users that have permission "perm". By default, filter out @@ -159,6 +234,13 @@ class ModelBackend(BaseBackend): return None return user if self.user_can_authenticate(user) else None + async def aget_user(self, user_id): + try: + user = await UserModel._default_manager.aget(pk=user_id) + except UserModel.DoesNotExist: + return None + return user if self.user_can_authenticate(user) else None + class AllowAllUsersModelBackend(ModelBackend): def user_can_authenticate(self, user): @@ -210,6 +292,29 @@ class RemoteUserBackend(ModelBackend): user = self.configure_user(request, user, created=created) return user if self.user_can_authenticate(user) else None + async def aauthenticate(self, request, remote_user): + """See authenticate().""" + if not remote_user: + return + created = False + user = None + username = self.clean_username(remote_user) + + # Note that this could be accomplished in one try-except clause, but + # instead we use get_or_create when creating unknown users since it has + # built-in safeguards for multiple threads. + if self.create_unknown_user: + user, created = await UserModel._default_manager.aget_or_create( + **{UserModel.USERNAME_FIELD: username} + ) + else: + try: + user = await UserModel._default_manager.aget_by_natural_key(username) + except UserModel.DoesNotExist: + pass + user = await self.aconfigure_user(request, user, created=created) + return user if self.user_can_authenticate(user) else None + def clean_username(self, username): """ Perform any cleaning on the "username" prior to using it to get or @@ -227,6 +332,10 @@ class RemoteUserBackend(ModelBackend): """ return user + async def aconfigure_user(self, request, user, created=True): + """See configure_user()""" + return await sync_to_async(self.configure_user)(request, user, created) + class AllowAllUsersRemoteUserBackend(RemoteUserBackend): def user_can_authenticate(self, user): diff --git a/django/contrib/auth/base_user.py b/django/contrib/auth/base_user.py index 0c9538d69d..5bb88ac4dd 100644 --- a/django/contrib/auth/base_user.py +++ b/django/contrib/auth/base_user.py @@ -36,6 +36,9 @@ class BaseUserManager(models.Manager): def get_by_natural_key(self, username): return self.get(**{self.model.USERNAME_FIELD: username}) + async def aget_by_natural_key(self, username): + return await self.aget(**{self.model.USERNAME_FIELD: username}) + class AbstractBaseUser(models.Model): password = models.CharField(_("password"), max_length=128) diff --git a/django/contrib/auth/decorators.py b/django/contrib/auth/decorators.py index 78e76a9ae9..77fbc79855 100644 --- a/django/contrib/auth/decorators.py +++ b/django/contrib/auth/decorators.py @@ -111,7 +111,7 @@ def permission_required(perm, login_url=None, raise_exception=False): async def check_perms(user): # First check if the user has the permission (even anon users). - if await sync_to_async(user.has_perms)(perms): + if await user.ahas_perms(perms): return True # In case the 403 handler should be called raise the exception. if raise_exception: diff --git a/django/contrib/auth/locale/cs/LC_MESSAGES/django.mo b/django/contrib/auth/locale/cs/LC_MESSAGES/django.mo index 71403aec13..0bdcc7b633 100644 Binary files a/django/contrib/auth/locale/cs/LC_MESSAGES/django.mo and b/django/contrib/auth/locale/cs/LC_MESSAGES/django.mo differ diff --git a/django/contrib/auth/locale/cs/LC_MESSAGES/django.po b/django/contrib/auth/locale/cs/LC_MESSAGES/django.po index 78b6fc49e2..6b5cf6b2d4 100644 --- a/django/contrib/auth/locale/cs/LC_MESSAGES/django.po +++ b/django/contrib/auth/locale/cs/LC_MESSAGES/django.po @@ -3,6 +3,7 @@ # Translators: # Jan Munclinger , 2013 # Jannis Leidel , 2011 +# Jiří Podhorecký , 2024 # Tomáš Ehrlich , 2015 # Vláďa Macek , 2013-2014 # Vláďa Macek , 2015-2017,2019,2021-2022 @@ -10,10 +11,10 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-09-21 10:22+0200\n" -"PO-Revision-Date: 2022-01-04 18:49+0000\n" -"Last-Translator: Vláďa Macek \n" -"Language-Team: Czech (http://www.transifex.com/django/django/language/cs/)\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-10-07 08:09+0000\n" +"Last-Translator: Jiří Podhorecký , 2024\n" +"Language-Team: Czech (http://app.transifex.com/django/django/language/cs/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -34,13 +35,23 @@ msgstr "Důležitá data" msgid "%(name)s object with primary key %(key)r does not exist." msgstr "Položka \"%(name)s\" s primárním klíčem \"%(key)r\" neexistuje." +msgid "Conflicting form data submitted. Please try again." +msgstr "Odeslání protichůdných údajů z formuláře. Zkuste to prosím znovu." + msgid "Password changed successfully." msgstr "Změna hesla byla úspěšná." +msgid "Password-based authentication was disabled." +msgstr "Ověřování pomocí hesla bylo zakázáno." + #, python-format msgid "Change password: %s" msgstr "Heslo pro uživatele %s: změnit" +#, python-format +msgid "Set password: %s" +msgstr "Nastavit heslo: %s" + msgid "Authentication and Authorization" msgstr "Autentizace a autorizace" @@ -56,9 +67,24 @@ msgstr "Heslo nenastaveno." msgid "Invalid password format or unknown hashing algorithm." msgstr "Neplatný formát hesla nebo neplatný hashovací algoritmus." +msgid "Reset password" +msgstr "Resetovat heslo" + +msgid "Set password" +msgstr "Nastavit heslo" + msgid "The two password fields didn’t match." msgstr "Hesla se neshodují." +msgid "" +"Whether the user will be able to authenticate using a password or not. If " +"disabled, they may still be able to authenticate using other backends, such " +"as Single Sign-On or LDAP." +msgstr "" +"Zda se uživatel bude moci ověřit pomocí hesla, nebo ne. Pokud je zakázáno, " +"může být stále schopen ověřit se pomocí jiných backendů, například Single " +"Sign-On nebo LDAP." + msgid "Password" msgstr "Heslo" @@ -68,12 +94,24 @@ msgstr "Potvrzení hesla" msgid "Enter the same password as before, for verification." msgstr "Zadejte pro ověření stejné heslo jako předtím." +msgid "Password-based authentication" +msgstr "Ověřování pomocí hesla" + +msgid "Enabled" +msgstr "Zapnuto" + +msgid "Disabled" +msgstr "Vypnuto" + msgid "" -"Raw passwords are not stored, so there is no way to see this user’s " -"password, but you can change the password using this form." +"Raw passwords are not stored, so there is no way to see the user’s password." msgstr "" -"Hesla se neukládají přímo a tak je nelze zobrazit. Je ale možné je změnit " -"pomocí tohoto formuláře." +"Neupravená hesla se neukládají, takže není možné zjistit heslo uživatele." + +msgid "" +"Enable password-based authentication for this user by setting a password." +msgstr "" +"Povolit ověřování na základě hesla pro tohoto uživatele nastavením hesla." #, python-format msgid "" @@ -101,9 +139,6 @@ msgstr "Vaše současné heslo nebylo zadáno správně. Zkuste to znovu." msgid "Old password" msgstr "Současné heslo" -msgid "Password (again)" -msgstr "Heslo (znovu)" - msgid "algorithm" msgstr "algoritmus" @@ -271,11 +306,11 @@ msgid "Password reset on %(site_name)s" msgstr "Obnovení hesla na webu %(site_name)s" msgid "" -"Enter a valid username. This value may contain only English letters, " -"numbers, and @/./+/-/_ characters." +"Enter a valid username. This value may contain only unaccented lowercase a-z " +"and uppercase A-Z letters, numbers, and @/./+/-/_ characters." msgstr "" -"Zadejte platné uživatelské jméno. Hodnota může obsahovat pouze písmena bez " -"diakritiky, tj. háčků a čárek, číslice a znaky @/./+/-/_." +"Zadejte platné uživatelské jméno. Tato hodnota může obsahovat pouze malá " +"písmena bez diakritiky a-z a velká písmena A-Z, číslice a znaky @/./+/-/_." msgid "" "Enter a valid username. This value may contain only letters, numbers, and " diff --git a/django/contrib/auth/locale/dsb/LC_MESSAGES/django.mo b/django/contrib/auth/locale/dsb/LC_MESSAGES/django.mo index 473025927e..0f26ac6d56 100644 Binary files a/django/contrib/auth/locale/dsb/LC_MESSAGES/django.mo and b/django/contrib/auth/locale/dsb/LC_MESSAGES/django.mo differ diff --git a/django/contrib/auth/locale/dsb/LC_MESSAGES/django.po b/django/contrib/auth/locale/dsb/LC_MESSAGES/django.po index a2cf9e981e..2d37b97fca 100644 --- a/django/contrib/auth/locale/dsb/LC_MESSAGES/django.po +++ b/django/contrib/auth/locale/dsb/LC_MESSAGES/django.po @@ -1,16 +1,16 @@ # This file is distributed under the same license as the Django package. # # Translators: -# Michael Wolf , 2016-2017,2020-2021,2023 +# Michael Wolf , 2016-2017,2020-2021,2023-2024 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-03-17 03:19-0500\n" -"PO-Revision-Date: 2023-04-25 08:09+0000\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-10-07 08:09+0000\n" "Last-Translator: Michael Wolf , " -"2016-2017,2020-2021,2023\n" -"Language-Team: Lower Sorbian (http://www.transifex.com/django/django/" +"2016-2017,2020-2021,2023-2024\n" +"Language-Team: Lower Sorbian (http://app.transifex.com/django/django/" "language/dsb/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -32,13 +32,23 @@ msgstr "Wažne daty" msgid "%(name)s object with primary key %(key)r does not exist." msgstr "Objekt %(name)s z primarnym klucom %(key)r njeeksistěrujo." +msgid "Conflicting form data submitted. Please try again." +msgstr "Pśeśiwne formularowe daty zapódane. Pšosym wopytajśo hyšći raz." + msgid "Password changed successfully." msgstr "Gronidło jo se změniło." +msgid "Password-based authentication was disabled." +msgstr "Awtentifikacija na zakłaźe gronidła jo se znjemóžniła." + #, python-format msgid "Change password: %s" msgstr "Gronidło změniś: %s" +#, python-format +msgid "Set password: %s" +msgstr "Nastajone gronidło: %s" + msgid "Authentication and Authorization" msgstr "Awtentifikacija a awtorizacija" @@ -54,9 +64,24 @@ msgstr "Žedno gronidło nastajone." msgid "Invalid password format or unknown hashing algorithm." msgstr "Njepłaśiwy gronidłowy format abo njeznaty kontrolny algoritmus." +msgid "Reset password" +msgstr "Gronidło slědk stajiś" + +msgid "Set password" +msgstr "Gronidło póstajis" + msgid "The two password fields didn’t match." msgstr "Dwě gronidlowej póli njejstej jadnakej." +msgid "" +"Whether the user will be able to authenticate using a password or not. If " +"disabled, they may still be able to authenticate using other backends, such " +"as Single Sign-On or LDAP." +msgstr "" +"Lěc wužywaŕ móžo z gronidłom awtentificěrowaś abo nic. Jolic to jo " +"znjemóžnjone, jo hyšći móžno, z pomocu drugich backendami awtentificěrowaś, " +"na pśikład Single Sign-On abo LDAP." + msgid "Password" msgstr "Gronidło" @@ -66,13 +91,26 @@ msgstr "Gronidłowe wobkšuśenje" msgid "Enter the same password as before, for verification." msgstr "Zapódajśo to samske gronidło, za pśespytanje." +msgid "Password-based authentication" +msgstr "Awtentifikacija na zakłaźe gronidła" + +msgid "Enabled" +msgstr "Źmóžnjony" + +msgid "Disabled" +msgstr "Znjemóžnjony" + msgid "" -"Raw passwords are not stored, so there is no way to see this user’s " -"password, but you can change the password using this form." +"Raw passwords are not stored, so there is no way to see the user’s password." msgstr "" -"Grube gronidła se njeskładuju, togodla njejo móžno, gronidło wužywarja " -"wiźeś, ale móžośo gronidło z pomocu toś togo formulara " -"změniś." +"Gropne gronidła se njeskładuju, njedajo pótakem žednu móžnosć, gronidło " +"wužywarja wiźeś." + +msgid "" +"Enable password-based authentication for this user by setting a password." +msgstr "" +"Póstajśo gronidło, aby awtentifkaciju na zakłaźe gronidła za toś togo " +"wužywarja zmóžnił." #, python-format msgid "" @@ -101,9 +139,6 @@ msgstr "" msgid "Old password" msgstr "Stare gronidło" -msgid "Password (again)" -msgstr "Gronidło (znowego)" - msgid "algorithm" msgstr "algoritmus" diff --git a/django/contrib/auth/locale/es/LC_MESSAGES/django.mo b/django/contrib/auth/locale/es/LC_MESSAGES/django.mo index 9af417c804..9323380d97 100644 Binary files a/django/contrib/auth/locale/es/LC_MESSAGES/django.mo and b/django/contrib/auth/locale/es/LC_MESSAGES/django.mo differ diff --git a/django/contrib/auth/locale/es/LC_MESSAGES/django.po b/django/contrib/auth/locale/es/LC_MESSAGES/django.po index 1644c996d0..060af415c6 100644 --- a/django/contrib/auth/locale/es/LC_MESSAGES/django.po +++ b/django/contrib/auth/locale/es/LC_MESSAGES/django.po @@ -13,15 +13,15 @@ # Josue Naaman Nistal Guerra , 2014 # Leonardo J. Caballero G. , 2011 # Natalia, 2024 -# Uriel Medina , 2020-2021,2023 +# Uriel Medina , 2020-2021,2023-2024 # Veronicabh , 2015 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-05-22 11:46-0300\n" -"PO-Revision-Date: 2024-08-07 08:09+0000\n" -"Last-Translator: Natalia, 2024\n" +"PO-Revision-Date: 2024-10-07 08:09+0000\n" +"Last-Translator: Uriel Medina , 2020-2021,2023-2024\n" "Language-Team: Spanish (http://app.transifex.com/django/django/language/" "es/)\n" "MIME-Version: 1.0\n" @@ -44,13 +44,15 @@ msgid "%(name)s object with primary key %(key)r does not exist." msgstr "el objeto %(name)s con clave primaria %(key)r no existe." msgid "Conflicting form data submitted. Please try again." -msgstr "Los datos enviados se contradicen. Por favor intente de nuevo." +msgstr "" +"Se enviaron datos contradictorios en el formulario. Por favor inténtalo de " +"nuevo." msgid "Password changed successfully." msgstr "La contraseña se ha cambiado con éxito." msgid "Password-based authentication was disabled." -msgstr "La autenticación basada en contraseñas fue deshabilitada." +msgstr "La autenticación basada en contraseña fue deshabilitada." #, python-format msgid "Change password: %s" @@ -103,7 +105,7 @@ msgid "Enter the same password as before, for verification." msgstr "Para verificar, introduzca la misma contraseña anterior." msgid "Password-based authentication" -msgstr "Autenticación basada en contraseñas" +msgstr "Autenticación basada en contraseña" msgid "Enabled" msgstr "Habilitado" diff --git a/django/contrib/auth/locale/es_AR/LC_MESSAGES/django.mo b/django/contrib/auth/locale/es_AR/LC_MESSAGES/django.mo index d985402b26..80cd087426 100644 Binary files a/django/contrib/auth/locale/es_AR/LC_MESSAGES/django.mo and b/django/contrib/auth/locale/es_AR/LC_MESSAGES/django.mo differ diff --git a/django/contrib/auth/locale/es_AR/LC_MESSAGES/django.po b/django/contrib/auth/locale/es_AR/LC_MESSAGES/django.po index bd89b28f39..c5f59b6971 100644 --- a/django/contrib/auth/locale/es_AR/LC_MESSAGES/django.po +++ b/django/contrib/auth/locale/es_AR/LC_MESSAGES/django.po @@ -2,14 +2,14 @@ # # Translators: # Jannis Leidel , 2011 -# Ramiro Morales, 2013-2017,2019,2021,2023 +# Ramiro Morales, 2013-2017,2019,2021,2023-2024 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-03-17 03:19-0500\n" -"PO-Revision-Date: 2023-12-04 08:09+0000\n" -"Last-Translator: Ramiro Morales, 2013-2017,2019,2021,2023\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-10-07 08:09+0000\n" +"Last-Translator: Ramiro Morales, 2013-2017,2019,2021,2023-2024\n" "Language-Team: Spanish (Argentina) (http://app.transifex.com/django/django/" "language/es_AR/)\n" "MIME-Version: 1.0\n" @@ -31,13 +31,23 @@ msgstr "Fechas importantes" msgid "%(name)s object with primary key %(key)r does not exist." msgstr "No existe un objeto %(name)s con una clave primaria %(key)r." +msgid "Conflicting form data submitted. Please try again." +msgstr "Los datos enviados se contradicen. Por favor intente de nuevo." + msgid "Password changed successfully." msgstr "Cambio de contraseña exitoso" +msgid "Password-based authentication was disabled." +msgstr "La autenticación basada en contraseñas fue desactivada." + #, python-format msgid "Change password: %s" msgstr "Cambiar contraseña: %s" +#, python-format +msgid "Set password: %s" +msgstr "Establecer contraseña: %s" + msgid "Authentication and Authorization" msgstr "Autenticación y Autorización" @@ -53,9 +63,24 @@ msgstr "No se ha establecido una contraseña." msgid "Invalid password format or unknown hashing algorithm." msgstr "Formato de contraseña inválido o algoritmo de hashing desconocido." +msgid "Reset password" +msgstr "Restablecer contraseña" + +msgid "Set password" +msgstr "Establecer contraseña" + msgid "The two password fields didn’t match." msgstr "Los dos campos de contraseñas no coinciden entre si." +msgid "" +"Whether the user will be able to authenticate using a password or not. If " +"disabled, they may still be able to authenticate using other backends, such " +"as Single Sign-On or LDAP." +msgstr "" +"Determina si el usuario podrá autenticarse usando una contraseña. Si está " +"desactivado, el usuario aún podría autenticarse mediante otros métodos, como " +"Single Sign-On o LDAP." + msgid "Password" msgstr "Contraseña" @@ -66,13 +91,26 @@ msgid "Enter the same password as before, for verification." msgstr "" "Introduzca la misma contraseña nuevamente, para poder verificar la misma." +msgid "Password-based authentication" +msgstr "Autenticación basada en contraseñas" + +msgid "Enabled" +msgstr "Activo" + +msgid "Disabled" +msgstr "Inactivo" + msgid "" -"Raw passwords are not stored, so there is no way to see this user’s " -"password, but you can change the password using this form." +"Raw passwords are not stored, so there is no way to see the user’s password." msgstr "" -"El sistema no almacena las contraseñas originales por lo cual no es posible " -"visualizar la contraseña de este usuario, pero puede modificarla usando este formulario." +"Las contraseñas en texto plano no se almacenan, por lo que no se puede ver " +"la contraseña del usuario." + +msgid "" +"Enable password-based authentication for this user by setting a password." +msgstr "" +"Active la autenticación por contraseña para este usuario estableciendo una " +"contraseña." #, python-format msgid "" @@ -102,9 +140,6 @@ msgstr "" msgid "Old password" msgstr "Contraseña antigua" -msgid "Password (again)" -msgstr "Contraseña (de nuevo)" - msgid "algorithm" msgstr "algoritmo" diff --git a/django/contrib/auth/locale/ga/LC_MESSAGES/django.mo b/django/contrib/auth/locale/ga/LC_MESSAGES/django.mo index 3221b186d7..84d1451989 100644 Binary files a/django/contrib/auth/locale/ga/LC_MESSAGES/django.mo and b/django/contrib/auth/locale/ga/LC_MESSAGES/django.mo differ diff --git a/django/contrib/auth/locale/ga/LC_MESSAGES/django.po b/django/contrib/auth/locale/ga/LC_MESSAGES/django.po index a91375aa8f..2d51e6dd28 100644 --- a/django/contrib/auth/locale/ga/LC_MESSAGES/django.po +++ b/django/contrib/auth/locale/ga/LC_MESSAGES/django.po @@ -1,16 +1,17 @@ # This file is distributed under the same license as the Django package. # # Translators: +# Aindriú Mac Giolla Eoin, 2024 # Jannis Leidel , 2011 # Michael Thornhill , 2012,2015 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-09-24 13:46+0200\n" -"PO-Revision-Date: 2017-09-24 14:24+0000\n" -"Last-Translator: Jannis Leidel \n" -"Language-Team: Irish (http://www.transifex.com/django/django/language/ga/)\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-10-07 08:09+0000\n" +"Last-Translator: Aindriú Mac Giolla Eoin, 2024\n" +"Language-Team: Irish (http://app.transifex.com/django/django/language/ga/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -29,17 +30,28 @@ msgstr "Dáta tábhactach" #, python-format msgid "%(name)s object with primary key %(key)r does not exist." +msgstr "Níl %(name)s réad le príomheochair %(key)r ann." + +msgid "Conflicting form data submitted. Please try again." msgstr "" +"Sonraí foirmeacha contrártha curtha isteach. Déan iarracht eile le do thoil." msgid "Password changed successfully." msgstr "Focal faire aithraithe rathúil" +msgid "Password-based authentication was disabled." +msgstr "Díchumasaíodh fíordheimhniú pasfhocal-bhunaithe." + #, python-format msgid "Change password: %s" msgstr "Athraigh focal faire: %s" +#, python-format +msgid "Set password: %s" +msgstr "Socraigh pasfhocal: %s" + msgid "Authentication and Authorization" -msgstr "" +msgstr "Fíordheimhniú agus Údarú" msgid "password" msgstr "focal faire" @@ -48,13 +60,29 @@ msgid "last login" msgstr "logáil deirneach" msgid "No password set." -msgstr "" +msgstr "Uimh pasfhocal socraithe." msgid "Invalid password format or unknown hashing algorithm." -msgstr "" +msgstr "Formáid pasfhocail neamhbhailí nó algartam hashing anaithnid." -msgid "The two password fields didn't match." -msgstr "Níl an dá focla faire comhoiriúnigh" +msgid "Reset password" +msgstr "Athshocraigh pasfhocal" + +msgid "Set password" +msgstr "Socraigh pasfhocal" + +msgid "The two password fields didn’t match." +msgstr "Níorbh ionann an dá réimse pasfhocal." + +msgid "" +"Whether the user will be able to authenticate using a password or not. If " +"disabled, they may still be able to authenticate using other backends, such " +"as Single Sign-On or LDAP." +msgstr "" +"Cé acu an mbeidh an t-úsáideoir in ann a fhíordheimhniú ag baint úsáide as " +"pasfhocal nó nach bhfuil. Má tá sé díchumasaithe, seans go mbeidh siad fós " +"in ann fíordheimhniú a dhéanamh trí úsáid a bhaint as hinnill eile, mar " +"shampla Sign-On Aonair nó LDAP." msgid "Password" msgstr "Focal faire" @@ -64,17 +92,37 @@ msgstr "Focal faire deimhniú" msgid "Enter the same password as before, for verification." msgstr "" +"Cuir isteach an pasfhocal céanna agus a rinneadh roimhe seo, le haghaidh " +"fíorú." + +msgid "Password-based authentication" +msgstr "Fíordheimhniú pasfhocal-bhunaithe" + +msgid "Enabled" +msgstr "Cumasaithe" + +msgid "Disabled" +msgstr "Faoi ​​mhíchumas" msgid "" -"Raw passwords are not stored, so there is no way to see this user's " -"password, but you can change the password using this form." +"Raw passwords are not stored, so there is no way to see the user’s password." msgstr "" +"Ní stóráiltear pasfhocail amh, mar sin níl aon bhealach ann pasfhocal an " +"úsáideora a fheiceáil." + +msgid "" +"Enable password-based authentication for this user by setting a password." +msgstr "" +"Cumasaigh fíordheimhniú pasfhocal-bhunaithe don úsáideoir seo trí phasfhocal " +"a shocrú." #, python-format msgid "" "Please enter a correct %(username)s and password. Note that both fields may " "be case-sensitive." msgstr "" +"Cuir isteach %(username)sagus pasfhocal ceart. Tabhair faoi deara go " +"bhféadfadh an dá réimse a bheith cás-íogair." msgid "This account is inactive." msgstr "Tá an cuntas seo neamhghníomhach." @@ -95,9 +143,6 @@ msgstr "" msgid "Old password" msgstr "Sean-focal faire " -msgid "Password (again)" -msgstr "Focal faire (arís)" - msgid "algorithm" msgstr "algartam" @@ -111,19 +156,19 @@ msgid "hash" msgstr "haiseáil" msgid "variety" -msgstr "" +msgstr "éagsúlacht" msgid "version" -msgstr "" +msgstr "leagan" msgid "memory cost" -msgstr "" +msgstr "costas cuimhne" msgid "time cost" -msgstr "" +msgstr "costas ama" msgid "parallelism" -msgstr "" +msgstr "comhthreomhaireacht" msgid "work factor" msgstr "fachtóir oibre" @@ -131,11 +176,14 @@ msgstr "fachtóir oibre" msgid "checksum" msgstr "suim sheiceála" +msgid "block size" +msgstr "méid bloc" + msgid "name" msgstr "ainm" msgid "content type" -msgstr "" +msgstr "cineál ábhair" msgid "codename" msgstr "Ainm cód" @@ -166,18 +214,22 @@ msgid "" "The groups this user belongs to. A user will get all permissions granted to " "each of their groups." msgstr "" +"Na grúpaí ar leis an úsáideoir seo iad. Gheobhaidh úsáideoir gach cead a " +"thugtar do gach ceann dá ngrúpa." msgid "user permissions" msgstr "ceada úsáideoira" msgid "Specific permissions for this user." -msgstr "" +msgstr "Ceadanna sonracha don úsáideoir seo." msgid "username" msgstr "Ainm úsáideoir" msgid "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only." msgstr "" +"Ag teastáil. 150 carachtar nó níos lú. Litreacha, digití agus @/./+/-/_ " +"amháin." msgid "A user with that username already exists." msgstr "In ann do úsáideoir leis an ainm úsáideora." @@ -226,52 +278,73 @@ msgid_plural "" "This password is too short. It must contain at least %(min_length)d " "characters." msgstr[0] "" +"Tá an pasfhocal seo ró-ghearr. Caithfidh %(min_length)d carachtar ar a " +"laghad a bheith ann." msgstr[1] "" +"Tá an pasfhocal seo ró-ghearr. Caithfidh %(min_length)d carachtair ar a " +"laghad a bheith ann." msgstr[2] "" +"Tá an pasfhocal seo ró-ghearr. Caithfidh %(min_length)d carachtair ar a " +"laghad a bheith ann." msgstr[3] "" +"Tá an pasfhocal seo ró-ghearr. Caithfidh %(min_length)d carachtair ar a " +"laghad a bheith ann." msgstr[4] "" +"Tá an pasfhocal seo ró-ghearr. Caithfidh %(min_length)d carachtair ar a " +"laghad a bheith ann." #, python-format msgid "Your password must contain at least %(min_length)d character." msgid_plural "Your password must contain at least %(min_length)d characters." msgstr[0] "" +"Caithfidh %(min_length)d carachtar ar a laghad a bheith i do phasfhocal." msgstr[1] "" +"Caithfidh %(min_length)d carachtair ar a laghad a bheith i do phasfhocal." msgstr[2] "" +"Caithfidh %(min_length)d carachtair ar a laghad a bheith i do phasfhocal." msgstr[3] "" +"Caithfidh %(min_length)d carachtair ar a laghad a bheith i do phasfhocal." msgstr[4] "" +"Caithfidh %(min_length)d carachtair ar a laghad a bheith i do phasfhocal." #, python-format msgid "The password is too similar to the %(verbose_name)s." -msgstr "" +msgstr "Tá an focal faire róchosúil le %(verbose_name)s." -msgid "Your password can't be too similar to your other personal information." +msgid "Your password can’t be too similar to your other personal information." msgstr "" +"Ní féidir le do phasfhocal a bheith róchosúil le d'fhaisnéis phearsanta eile." msgid "This password is too common." -msgstr "" +msgstr "Tá an pasfhocal seo ró-choitianta." -msgid "Your password can't be a commonly used password." +msgid "Your password can’t be a commonly used password." msgstr "" +"Ní féidir le do phasfhocal a bheith ina phasfhocal a úsáidtear go coitianta." msgid "This password is entirely numeric." -msgstr "" +msgstr "Tá an pasfhocal seo go hiomlán uimhriúil." -msgid "Your password can't be entirely numeric." -msgstr "" +msgid "Your password can’t be entirely numeric." +msgstr "Ní féidir le do phasfhocal a bheith uimhriúil go hiomlán." #, python-format msgid "Password reset on %(site_name)s" msgstr "Athshocraigh focal faire ar %(site_name)s" msgid "" -"Enter a valid username. This value may contain only English letters, " -"numbers, and @/./+/-/_ characters." +"Enter a valid username. This value may contain only unaccented lowercase a-z " +"and uppercase A-Z letters, numbers, and @/./+/-/_ characters." msgstr "" +"Cuir isteach ainm úsáideora bailí. Ní fhéadfaidh an luach seo ach litreacha, " +"uimhreacha, agus @/./+//_ A bheith i gceist leis an luach seo." msgid "" "Enter a valid username. This value may contain only letters, numbers, and " "@/./+/-/_ characters." msgstr "" +"Cuir isteach ainm úsáideora bailí. Seans nach bhfuil sa luach seo ach " +"litreacha, uimhreacha, agus carachtair @/./+/-/_." msgid "Logged out" msgstr "Logáilte amach" @@ -283,16 +356,16 @@ msgid "Password reset sent" msgstr "Pasfhocal athshocrú sheoladh" msgid "Enter new password" -msgstr "" +msgstr "Cuir isteach pasfhocal nua" msgid "Password reset unsuccessful" -msgstr "" +msgstr "Níor éirigh le hathshocrú pasfhocail" msgid "Password reset complete" -msgstr "" +msgstr "Athshocrú pasfhocail críochnaithe" msgid "Password change" -msgstr "" +msgstr "Athrú pasfhocal" msgid "Password change successful" -msgstr "" +msgstr "D'éirigh le hathrú pasfhocal" diff --git a/django/contrib/auth/locale/hsb/LC_MESSAGES/django.mo b/django/contrib/auth/locale/hsb/LC_MESSAGES/django.mo index 5b76e7b43f..ecc53cdd46 100644 Binary files a/django/contrib/auth/locale/hsb/LC_MESSAGES/django.mo and b/django/contrib/auth/locale/hsb/LC_MESSAGES/django.mo differ diff --git a/django/contrib/auth/locale/hsb/LC_MESSAGES/django.po b/django/contrib/auth/locale/hsb/LC_MESSAGES/django.po index 1293c03bb5..a6ddea5cec 100644 --- a/django/contrib/auth/locale/hsb/LC_MESSAGES/django.po +++ b/django/contrib/auth/locale/hsb/LC_MESSAGES/django.po @@ -1,16 +1,16 @@ # This file is distributed under the same license as the Django package. # # Translators: -# Michael Wolf , 2016-2017,2019,2021,2023 +# Michael Wolf , 2016-2017,2019,2021,2023-2024 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-03-17 03:19-0500\n" -"PO-Revision-Date: 2023-04-25 08:09+0000\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-10-07 08:09+0000\n" "Last-Translator: Michael Wolf , " -"2016-2017,2019,2021,2023\n" -"Language-Team: Upper Sorbian (http://www.transifex.com/django/django/" +"2016-2017,2019,2021,2023-2024\n" +"Language-Team: Upper Sorbian (http://app.transifex.com/django/django/" "language/hsb/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -32,13 +32,23 @@ msgstr "Wažne daty" msgid "%(name)s object with primary key %(key)r does not exist." msgstr "Objekt %(name)s z primarnym klučom %(key)r njeeksistuje." +msgid "Conflicting form data submitted. Please try again." +msgstr "Přećiwne formularowe daty zapodate. Prošu spytajće hišće raz." + msgid "Password changed successfully." msgstr "Hesło je so wuspěšnje změniło." +msgid "Password-based authentication was disabled." +msgstr "Awtentifikacija na zakładźe hesła je so znjemóžniła." + #, python-format msgid "Change password: %s" msgstr "Hesło změnić: %s" +#, python-format +msgid "Set password: %s" +msgstr "Nastajene hesło: %s" + msgid "Authentication and Authorization" msgstr "Awtentifikacija a awtorizacija" @@ -54,9 +64,24 @@ msgstr "Žane hesło nastajene." msgid "Invalid password format or unknown hashing algorithm." msgstr "Njepłaćiwy hesłowy format abo njeznaty kontrolny algoritmus." +msgid "Reset password" +msgstr "Hesło wróćo stajić" + +msgid "Set password" +msgstr "Hesło postajić" + msgid "The two password fields didn’t match." msgstr "Dwě heslowej poli sej njewotpowědujetej." +msgid "" +"Whether the user will be able to authenticate using a password or not. If " +"disabled, they may still be able to authenticate using other backends, such " +"as Single Sign-On or LDAP." +msgstr "" +"Hač wužiwar móže z hesłom awtentifikować abo nic. Jeli to je znjemóžnjene, " +"je hišće móžno, z druhimi backendami awtentifikować, na přikład Single Sign-" +"On abo LDAP." + msgid "Password" msgstr "Hesło" @@ -66,12 +91,25 @@ msgstr "Hesłowe wobkrućenje" msgid "Enter the same password as before, for verification." msgstr "Zapodajće samsne hesło kaž do toho, za přepruwowanje." +msgid "Password-based authentication" +msgstr "Awtentifikacija na zakładźe hesła" + +msgid "Enabled" +msgstr "Zmóžnjeny" + +msgid "Disabled" +msgstr "Znjemóžnjeny" + msgid "" -"Raw passwords are not stored, so there is no way to see this user’s " -"password, but you can change the password using this form." +"Raw passwords are not stored, so there is no way to see the user’s password." msgstr "" -"Hrube hesła so njeskładuja, tohodla njeda so hesło tutoho wužwarja widźeć, " -"ale móžeće hesło z pomocu tutoho formulara změnić. " +"Hrube hesła so njeskładuja, tohodla móžnosć njeje, hesło wužiwarja widźeć." + +msgid "" +"Enable password-based authentication for this user by setting a password." +msgstr "" +"Postajće hesło, zo byšće awtentifikaciju na zakładźe hesło za tutoho " +"wužiwarja zmóžnił." #, python-format msgid "" @@ -99,9 +137,6 @@ msgstr "Waše stare hesło je so wopak zapodało. Prošu zapodajće jo hišće r msgid "Old password" msgstr "Stare hesło" -msgid "Password (again)" -msgstr "Hesło (znowa)" - msgid "algorithm" msgstr "algoritmus" diff --git a/django/contrib/auth/locale/sk/LC_MESSAGES/django.mo b/django/contrib/auth/locale/sk/LC_MESSAGES/django.mo index fa6e2e03a0..c6df60e4e9 100644 Binary files a/django/contrib/auth/locale/sk/LC_MESSAGES/django.mo and b/django/contrib/auth/locale/sk/LC_MESSAGES/django.mo differ diff --git a/django/contrib/auth/locale/sk/LC_MESSAGES/django.po b/django/contrib/auth/locale/sk/LC_MESSAGES/django.po index 63483a7ac1..dfa64b7b25 100644 --- a/django/contrib/auth/locale/sk/LC_MESSAGES/django.po +++ b/django/contrib/auth/locale/sk/LC_MESSAGES/django.po @@ -1,7 +1,7 @@ # This file is distributed under the same license as the Django package. # # Translators: -# Adam Zahradník, 2023 +# Adam Zahradník, 2023-2024 # Jannis Leidel , 2011 # 18f25ad6fa9930fc67cb11aca9d16a27, 2012-2014 # Marian Andre , 2015,2017 @@ -12,9 +12,9 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-03-17 03:19-0500\n" -"PO-Revision-Date: 2023-12-04 08:09+0000\n" -"Last-Translator: Martin Tóth , 2017-2018,2023\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-10-07 08:09+0000\n" +"Last-Translator: Adam Zahradník, 2023-2024\n" "Language-Team: Slovak (http://app.transifex.com/django/django/language/sk/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -36,13 +36,23 @@ msgstr "Dôležité dátumy" msgid "%(name)s object with primary key %(key)r does not exist." msgstr "Objekt %(name)s s primárnym kľúčom %(key)r neexistuje." +msgid "Conflicting form data submitted. Please try again." +msgstr "Neplatná požiadavka. Skúste to prosím znova." + msgid "Password changed successfully." msgstr "Heslo úspešne zmenené." +msgid "Password-based authentication was disabled." +msgstr "Prihlasovanie pomocou hesla bolo vypnuté." + #, python-format msgid "Change password: %s" msgstr "Zmeniť heslo: %s" +#, python-format +msgid "Set password: %s" +msgstr "Nastaviť heslo: %s" + msgid "Authentication and Authorization" msgstr "Autentifikácia a autorizácia" @@ -58,9 +68,24 @@ msgstr "Žiadne heslo." msgid "Invalid password format or unknown hashing algorithm." msgstr "Neplatný formát hesla alebo neznámy hašovací algoritmus." +msgid "Reset password" +msgstr "Obnoviť heslo" + +msgid "Set password" +msgstr "Nastaviť heslo" + msgid "The two password fields didn’t match." msgstr "Heslo a jeho potvrdenie sa nezhodujú." +msgid "" +"Whether the user will be able to authenticate using a password or not. If " +"disabled, they may still be able to authenticate using other backends, such " +"as Single Sign-On or LDAP." +msgstr "" +"Určuje, či sa bude používateľ môcť prihlásiť pomocou hesla. Ak je " +"prihlasovanie pomocou hesla vypnuté, stále sa bude môcť vedieť prihlásiť " +"pomocou iných metód, ako napríklad Single Sign-On alebo LDAP." + msgid "Password" msgstr "Heslo" @@ -70,13 +95,25 @@ msgstr "Potvrdenie hesla" msgid "Enter the same password as before, for verification." msgstr "Kvôli overeniu, znovu zadajte rovnaké heslo." +msgid "Password-based authentication" +msgstr "Prihlasovanie pomocou hesla" + +msgid "Enabled" +msgstr "Povolené" + +msgid "Disabled" +msgstr "Vypnuté" + msgid "" -"Raw passwords are not stored, so there is no way to see this user’s " -"password, but you can change the password using this form." +"Raw passwords are not stored, so there is no way to see the user’s password." msgstr "" -"Heslá v pôvodnom tvare nie sú ukladané, takže neexistuje spôsob zobraziť " -"heslo užívateľa. Môžete ho však zmeniť pomocou tohoto " -"formulára." +"Nie je možné zobraziť si používateľovo heslo, nakoľko sa samotné heslá " +"neukladajú." + +msgid "" +"Enable password-based authentication for this user by setting a password." +msgstr "" +"Nastavením hesla povolíte používateľovi prihlasovanie sa pomocou hesla." #, python-format msgid "" @@ -104,9 +141,6 @@ msgstr "Nezadali ste správne svoje staré heslo. Napíšte ho znovu, prosím." msgid "Old password" msgstr "Staré heslo" -msgid "Password (again)" -msgstr "Heslo (znova)" - msgid "algorithm" msgstr "algoritmus" diff --git a/django/contrib/auth/locale/ug/LC_MESSAGES/django.mo b/django/contrib/auth/locale/ug/LC_MESSAGES/django.mo index 2228f92e68..481d10ada0 100644 Binary files a/django/contrib/auth/locale/ug/LC_MESSAGES/django.mo and b/django/contrib/auth/locale/ug/LC_MESSAGES/django.mo differ diff --git a/django/contrib/auth/locale/ug/LC_MESSAGES/django.po b/django/contrib/auth/locale/ug/LC_MESSAGES/django.po index ef15de3fd3..e231cc2c3f 100644 --- a/django/contrib/auth/locale/ug/LC_MESSAGES/django.po +++ b/django/contrib/auth/locale/ug/LC_MESSAGES/django.po @@ -1,16 +1,16 @@ # This file is distributed under the same license as the Django package. # # Translators: -# Abduqadir Abliz , 2023 +# Abduqadir Abliz , 2023-2024 # Azat, 2023 # Murat Orhun , 2023 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-03-17 03:19-0500\n" -"PO-Revision-Date: 2023-12-04 08:09+0000\n" -"Last-Translator: Azat, 2023\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-10-07 08:09+0000\n" +"Last-Translator: Abduqadir Abliz , 2023-2024\n" "Language-Team: Uyghur (http://app.transifex.com/django/django/language/ug/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -31,13 +31,23 @@ msgstr "مۇھىم چېسلا" msgid "%(name)s object with primary key %(key)r does not exist." msgstr "%(key)r ئاساسىي ئاچقۇچى بار %(name)s ئوبيېكت مەۋجۇت ئەمەس." +msgid "Conflicting form data submitted. Please try again." +msgstr "تاپشۇرغان جەدۋەل سانلىق مەلۇماتىدا توقۇنۇش بار. قايتا سىناڭ." + msgid "Password changed successfully." msgstr "پارول مۇۋەپپەقىيەتلىك ئۆزگەرتىلدى." +msgid "Password-based authentication was disabled." +msgstr "ئىم ئاساسىدىكى دەلىللەش چەكلەنگەن." + #, python-format msgid "Change password: %s" msgstr "پارول ئۆزگەرتىش: %s" +#, python-format +msgid "Set password: %s" +msgstr "ئىم تەڭشەك %s" + msgid "Authentication and Authorization" msgstr "دەلىللەش ۋە ھوقۇق بېرىش" @@ -54,9 +64,24 @@ msgid "Invalid password format or unknown hashing algorithm." msgstr "" "ئىناۋەتسىز پارول پىچىمى ياكى يوچۇن مۇكەممەللىكىنى تەكشۈرۈش ھېسابلاش ئۇسۇلى." +msgid "Reset password" +msgstr "ئىم ئەسلىگە قايتۇر" + +msgid "Set password" +msgstr "ئىم تەڭشەك" + msgid "The two password fields didn’t match." msgstr "ئىككى پارول بۆلىكى ماس كەلمىدى." +msgid "" +"Whether the user will be able to authenticate using a password or not. If " +"disabled, they may still be able to authenticate using other backends, such " +"as Single Sign-On or LDAP." +msgstr "" +"ئىشلەتكۈچى ئىم ئارقىلىق دەلىللەشنى ئىشلىتەلەمدۇ يوق. ئەگەر چەكلەنسە، ئۇلار " +"يەنىلا باشقا ئارقا ئۇچ پىروگراممىسى يەنى يەككە تىزىمغا كىرىش ياكى LDAP " +"ئارقىلىق دەلىللىيەلەيدۇ." + msgid "Password" msgstr "پارول" @@ -66,13 +91,23 @@ msgstr "پارول جەزملەش" msgid "Enter the same password as before, for verification." msgstr "دەلىللەش ئۈچۈن، ئىلگىرىكى ئوخشاش ئىمنى قايتا كىرگۈزۈڭ." +msgid "Password-based authentication" +msgstr "ئىم ئاساسىدىكى دەلىللەش" + +msgid "Enabled" +msgstr "قوزغىتىلدى" + +msgid "Disabled" +msgstr "چەكلەندى" + msgid "" -"Raw passwords are not stored, so there is no way to see this user’s " -"password, but you can change the password using this form." +"Raw passwords are not stored, so there is no way to see the user’s password." +msgstr "ئەسلى ئىم ساقلانمىدى، شۇڭلاشقا ئىشلەتكۈچى ئىمنى كۆرسىتەلمەيدۇ." + +msgid "" +"Enable password-based authentication for this user by setting a password." msgstr "" -"ئەسلى پارول سىستېمىغا ساقلانمايدۇ، شۇڭلاشقا بۇ ئىشلەتكۈچى ئىشلەتكەن پارولىنى " -"كۆرگىلى بولمايدۇ، ئەمما بۇ جەدۋەل نى ئىشلىتىپ پارولنى " -"ئۆزگەرتەلەيسىز." +"بۇ ئىشلەتكۈچىگە ئىم تەڭشەش ئارقىلىق ئىم ئاساسىدىكى دەلىللەشنى قوزغىتىدۇ." #, python-format msgid "" @@ -100,9 +135,6 @@ msgstr "كونا پارولنى توغرا كىرگۈزمىدىڭىز. قايت msgid "Old password" msgstr "كونا پارول" -msgid "Password (again)" -msgstr "پارول (قايتا)" - msgid "algorithm" msgstr "ئالگورىزىم" diff --git a/django/contrib/auth/locale/zh_Hans/LC_MESSAGES/django.mo b/django/contrib/auth/locale/zh_Hans/LC_MESSAGES/django.mo index d4d2485b3a..5622927c44 100644 Binary files a/django/contrib/auth/locale/zh_Hans/LC_MESSAGES/django.mo and b/django/contrib/auth/locale/zh_Hans/LC_MESSAGES/django.mo differ diff --git a/django/contrib/auth/locale/zh_Hans/LC_MESSAGES/django.po b/django/contrib/auth/locale/zh_Hans/LC_MESSAGES/django.po index 000822e86a..1b2f624f12 100644 --- a/django/contrib/auth/locale/zh_Hans/LC_MESSAGES/django.po +++ b/django/contrib/auth/locale/zh_Hans/LC_MESSAGES/django.po @@ -11,6 +11,7 @@ # Kevin Sze , 2012 # Lele Long , 2011,2015 # Liping Wang , 2016-2017 +# L., 2024 # mozillazg , 2016 # Lemon Li , 2012-2013 # Wentao Han , 2020 @@ -22,9 +23,9 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-03-17 03:19-0500\n" -"PO-Revision-Date: 2023-12-04 08:09+0000\n" -"Last-Translator: Kaiqi Zhu, 2023\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-10-07 08:09+0000\n" +"Last-Translator: L., 2024\n" "Language-Team: Chinese (China) (http://app.transifex.com/django/django/" "language/zh_CN/)\n" "MIME-Version: 1.0\n" @@ -46,13 +47,23 @@ msgstr "重要日期" msgid "%(name)s object with primary key %(key)r does not exist." msgstr "具有主键 %(key)r 的对象 %(name)s 不存在。" +msgid "Conflicting form data submitted. Please try again." +msgstr "提交的表单数据存在冲突。请再试一次。" + msgid "Password changed successfully." msgstr "密码修改成功。" +msgid "Password-based authentication was disabled." +msgstr "基于密码的验证已禁用。" + #, python-format msgid "Change password: %s" msgstr "修改密码:%s" +#, python-format +msgid "Set password: %s" +msgstr "设置密码: %s" + msgid "Authentication and Authorization" msgstr "认证和授权" @@ -68,9 +79,23 @@ msgstr "密码未设置。" msgid "Invalid password format or unknown hashing algorithm." msgstr "不可用的密码格式或未知的哈希算法。" +msgid "Reset password" +msgstr "重置密码" + +msgid "Set password" +msgstr "设置密码" + msgid "The two password fields didn’t match." msgstr "输入的两个密码不一致。" +msgid "" +"Whether the user will be able to authenticate using a password or not. If " +"disabled, they may still be able to authenticate using other backends, such " +"as Single Sign-On or LDAP." +msgstr "" +"用户是否能够使用密码进行认证。如果禁用,他们仍可能能够使用其他后端进行认证," +"例如单一登入或LDAP。" + msgid "Password" msgstr "密码" @@ -80,12 +105,22 @@ msgstr "密码确认" msgid "Enter the same password as before, for verification." msgstr "为了校验,请输入与上面相同的密码。" +msgid "Password-based authentication" +msgstr "基于密码的验证" + +msgid "Enabled" +msgstr "启用" + +msgid "Disabled" +msgstr "禁用" + msgid "" -"Raw passwords are not stored, so there is no way to see this user’s " -"password, but you can change the password using this form." -msgstr "" -"密码原文未存储在系统中,因此无法看到该用户的密码。然而你可以通过这个表单来修改密码。" +"Raw passwords are not stored, so there is no way to see the user’s password." +msgstr "原始密码不会被储存,因此无法查看用户的密码。" + +msgid "" +"Enable password-based authentication for this user by setting a password." +msgstr "通过设定密码为此用户启用基于密码的认证。" #, python-format msgid "" @@ -111,9 +146,6 @@ msgstr "你的旧密码不正确。请重新输入。" msgid "Old password" msgstr "旧密码" -msgid "Password (again)" -msgstr "密码(重复)" - msgid "algorithm" msgstr "算法" diff --git a/django/contrib/auth/middleware.py b/django/contrib/auth/middleware.py index cb409ee778..85f58ec9a5 100644 --- a/django/contrib/auth/middleware.py +++ b/django/contrib/auth/middleware.py @@ -1,6 +1,8 @@ from functools import partial from urllib.parse import urlsplit +from asgiref.sync import iscoroutinefunction, markcoroutinefunction + from django.conf import settings from django.contrib import auth from django.contrib.auth import REDIRECT_FIELD_NAME, load_backend @@ -88,7 +90,7 @@ class LoginRequiredMiddleware(MiddlewareMixin): ) -class RemoteUserMiddleware(MiddlewareMixin): +class RemoteUserMiddleware: """ Middleware for utilizing web-server-provided authentication. @@ -102,13 +104,27 @@ class RemoteUserMiddleware(MiddlewareMixin): different header. """ + sync_capable = True + async_capable = True + + def __init__(self, get_response): + if get_response is None: + raise ValueError("get_response must be provided.") + self.get_response = get_response + self.is_async = iscoroutinefunction(get_response) + if self.is_async: + markcoroutinefunction(self) + super().__init__() + # Name of request header to grab username from. This will be the key as # used in the request.META dictionary, i.e. the normalization of headers to # all uppercase and the addition of "HTTP_" prefix apply. header = "REMOTE_USER" force_logout_if_no_header = True - def process_request(self, request): + def __call__(self, request): + if self.is_async: + return self.__acall__(request) # AuthenticationMiddleware is required so that request.user exists. if not hasattr(request, "user"): raise ImproperlyConfigured( @@ -126,13 +142,13 @@ class RemoteUserMiddleware(MiddlewareMixin): # AnonymousUser by the AuthenticationMiddleware). if self.force_logout_if_no_header and request.user.is_authenticated: self._remove_invalid_user(request) - return + return self.get_response(request) # If the user is already authenticated and that user is the user we are # getting passed in the headers, then the correct user is already # persisted in the session and we don't need to continue. if request.user.is_authenticated: if request.user.get_username() == self.clean_username(username, request): - return + return self.get_response(request) else: # An authenticated user is associated with the request, but # it does not match the authorized user in the header. @@ -146,6 +162,51 @@ class RemoteUserMiddleware(MiddlewareMixin): # by logging the user in. request.user = user auth.login(request, user) + return self.get_response(request) + + async def __acall__(self, request): + # AuthenticationMiddleware is required so that request.user exists. + if not hasattr(request, "user"): + raise ImproperlyConfigured( + "The Django remote user auth middleware requires the" + " authentication middleware to be installed. Edit your" + " MIDDLEWARE setting to insert" + " 'django.contrib.auth.middleware.AuthenticationMiddleware'" + " before the RemoteUserMiddleware class." + ) + try: + username = request.META["HTTP_" + self.header] + except KeyError: + # If specified header doesn't exist then remove any existing + # authenticated remote-user, or return (leaving request.user set to + # AnonymousUser by the AuthenticationMiddleware). + if self.force_logout_if_no_header: + user = await request.auser() + if user.is_authenticated: + await self._aremove_invalid_user(request) + return await self.get_response(request) + user = await request.auser() + # If the user is already authenticated and that user is the user we are + # getting passed in the headers, then the correct user is already + # persisted in the session and we don't need to continue. + if user.is_authenticated: + if user.get_username() == self.clean_username(username, request): + return await self.get_response(request) + else: + # An authenticated user is associated with the request, but + # it does not match the authorized user in the header. + await self._aremove_invalid_user(request) + + # We are seeing this user for the first time in this session, attempt + # to authenticate the user. + user = await auth.aauthenticate(request, remote_user=username) + if user: + # User is valid. Set request.user and persist user in the session + # by logging the user in. + request.user = user + await auth.alogin(request, user) + + return await self.get_response(request) def clean_username(self, username, request): """ @@ -176,6 +237,22 @@ class RemoteUserMiddleware(MiddlewareMixin): if isinstance(stored_backend, RemoteUserBackend): auth.logout(request) + async def _aremove_invalid_user(self, request): + """ + Remove the current authenticated user in the request which is invalid + but only if the user is authenticated via the RemoteUserBackend. + """ + try: + stored_backend = load_backend( + await request.session.aget(auth.BACKEND_SESSION_KEY, "") + ) + except ImportError: + # Backend failed to load. + await auth.alogout(request) + else: + if isinstance(stored_backend, RemoteUserBackend): + await auth.alogout(request) + class PersistentRemoteUserMiddleware(RemoteUserMiddleware): """ diff --git a/django/contrib/auth/models.py b/django/contrib/auth/models.py index e5ef1bb523..d4a8dd902b 100644 --- a/django/contrib/auth/models.py +++ b/django/contrib/auth/models.py @@ -95,6 +95,9 @@ class GroupManager(models.Manager): def get_by_natural_key(self, name): return self.get(name=name) + async def aget_by_natural_key(self, name): + return await self.aget(name=name) + class Group(models.Model): """ @@ -137,10 +140,7 @@ class Group(models.Model): class UserManager(BaseUserManager): use_in_migrations = True - def _create_user(self, username, email, password, **extra_fields): - """ - Create and save a user with the given username, email, and password. - """ + def _create_user_object(self, username, email, password, **extra_fields): if not username: raise ValueError("The given username must be set") email = self.normalize_email(email) @@ -153,14 +153,32 @@ class UserManager(BaseUserManager): username = GlobalUserModel.normalize_username(username) user = self.model(username=username, email=email, **extra_fields) user.password = make_password(password) + return user + + def _create_user(self, username, email, password, **extra_fields): + """ + Create and save a user with the given username, email, and password. + """ + user = self._create_user_object(username, email, password, **extra_fields) user.save(using=self._db) return user + async def _acreate_user(self, username, email, password, **extra_fields): + """See _create_user()""" + user = self._create_user_object(username, email, password, **extra_fields) + await user.asave(using=self._db) + return user + def create_user(self, username, email=None, password=None, **extra_fields): extra_fields.setdefault("is_staff", False) extra_fields.setdefault("is_superuser", False) return self._create_user(username, email, password, **extra_fields) + async def acreate_user(self, username, email=None, password=None, **extra_fields): + extra_fields.setdefault("is_staff", False) + extra_fields.setdefault("is_superuser", False) + return await self._acreate_user(username, email, password, **extra_fields) + def create_superuser(self, username, email=None, password=None, **extra_fields): extra_fields.setdefault("is_staff", True) extra_fields.setdefault("is_superuser", True) @@ -172,6 +190,19 @@ class UserManager(BaseUserManager): return self._create_user(username, email, password, **extra_fields) + async def acreate_superuser( + self, username, email=None, password=None, **extra_fields + ): + extra_fields.setdefault("is_staff", True) + extra_fields.setdefault("is_superuser", True) + + if extra_fields.get("is_staff") is not True: + raise ValueError("Superuser must have is_staff=True.") + if extra_fields.get("is_superuser") is not True: + raise ValueError("Superuser must have is_superuser=True.") + + return await self._acreate_user(username, email, password, **extra_fields) + def with_perm( self, perm, is_active=True, include_superusers=True, backend=None, obj=None ): @@ -210,6 +241,15 @@ def _user_get_permissions(user, obj, from_name): return permissions +async def _auser_get_permissions(user, obj, from_name): + permissions = set() + name = "aget_%s_permissions" % from_name + for backend in auth.get_backends(): + if hasattr(backend, name): + permissions.update(await getattr(backend, name)(user, obj)) + return permissions + + def _user_has_perm(user, perm, obj): """ A backend can raise `PermissionDenied` to short-circuit permission checking. @@ -225,6 +265,19 @@ def _user_has_perm(user, perm, obj): return False +async def _auser_has_perm(user, perm, obj): + """See _user_has_perm()""" + for backend in auth.get_backends(): + if not hasattr(backend, "ahas_perm"): + continue + try: + if await backend.ahas_perm(user, perm, obj): + return True + except PermissionDenied: + return False + return False + + def _user_has_module_perms(user, app_label): """ A backend can raise `PermissionDenied` to short-circuit permission checking. @@ -240,6 +293,19 @@ def _user_has_module_perms(user, app_label): return False +async def _auser_has_module_perms(user, app_label): + """See _user_has_module_perms()""" + for backend in auth.get_backends(): + if not hasattr(backend, "ahas_module_perms"): + continue + try: + if await backend.ahas_module_perms(user, app_label): + return True + except PermissionDenied: + return False + return False + + class PermissionsMixin(models.Model): """ Add the fields and methods necessary to support the Group and Permission @@ -285,6 +351,10 @@ class PermissionsMixin(models.Model): """ return _user_get_permissions(self, obj, "user") + async def aget_user_permissions(self, obj=None): + """See get_user_permissions()""" + return await _auser_get_permissions(self, obj, "user") + def get_group_permissions(self, obj=None): """ Return a list of permission strings that this user has through their @@ -293,9 +363,16 @@ class PermissionsMixin(models.Model): """ return _user_get_permissions(self, obj, "group") + async def aget_group_permissions(self, obj=None): + """See get_group_permissions()""" + return await _auser_get_permissions(self, obj, "group") + def get_all_permissions(self, obj=None): return _user_get_permissions(self, obj, "all") + async def aget_all_permissions(self, obj=None): + return await _auser_get_permissions(self, obj, "all") + def has_perm(self, perm, obj=None): """ Return True if the user has the specified permission. Query all @@ -311,6 +388,15 @@ class PermissionsMixin(models.Model): # Otherwise we need to check the backends. return _user_has_perm(self, perm, obj) + async def ahas_perm(self, perm, obj=None): + """See has_perm()""" + # Active superusers have all permissions. + if self.is_active and self.is_superuser: + return True + + # Otherwise we need to check the backends. + return await _auser_has_perm(self, perm, obj) + def has_perms(self, perm_list, obj=None): """ Return True if the user has each of the specified permissions. If @@ -320,6 +406,15 @@ class PermissionsMixin(models.Model): raise ValueError("perm_list must be an iterable of permissions.") return all(self.has_perm(perm, obj) for perm in perm_list) + async def ahas_perms(self, perm_list, obj=None): + """See has_perms()""" + if not isinstance(perm_list, Iterable) or isinstance(perm_list, str): + raise ValueError("perm_list must be an iterable of permissions.") + for perm in perm_list: + if not await self.ahas_perm(perm, obj): + return False + return True + def has_module_perms(self, app_label): """ Return True if the user has any permissions in the given app label. @@ -331,6 +426,14 @@ class PermissionsMixin(models.Model): return _user_has_module_perms(self, app_label) + async def ahas_module_perms(self, app_label): + """See has_module_perms()""" + # Active superusers have all permissions. + if self.is_active and self.is_superuser: + return True + + return await _auser_has_module_perms(self, app_label) + class AbstractUser(AbstractBaseUser, PermissionsMixin): """ @@ -471,23 +574,46 @@ class AnonymousUser: def get_user_permissions(self, obj=None): return _user_get_permissions(self, obj, "user") + async def aget_user_permissions(self, obj=None): + return await _auser_get_permissions(self, obj, "user") + def get_group_permissions(self, obj=None): return set() + async def aget_group_permissions(self, obj=None): + return self.get_group_permissions(obj) + def get_all_permissions(self, obj=None): return _user_get_permissions(self, obj, "all") + async def aget_all_permissions(self, obj=None): + return await _auser_get_permissions(self, obj, "all") + def has_perm(self, perm, obj=None): return _user_has_perm(self, perm, obj=obj) + async def ahas_perm(self, perm, obj=None): + return await _auser_has_perm(self, perm, obj=obj) + def has_perms(self, perm_list, obj=None): if not isinstance(perm_list, Iterable) or isinstance(perm_list, str): raise ValueError("perm_list must be an iterable of permissions.") return all(self.has_perm(perm, obj) for perm in perm_list) + async def ahas_perms(self, perm_list, obj=None): + if not isinstance(perm_list, Iterable) or isinstance(perm_list, str): + raise ValueError("perm_list must be an iterable of permissions.") + for perm in perm_list: + if not await self.ahas_perm(perm, obj): + return False + return True + def has_module_perms(self, module): return _user_has_module_perms(self, module) + async def ahas_module_perms(self, module): + return await _auser_has_module_perms(self, module) + @property def is_anonymous(self): return True diff --git a/django/contrib/auth/password_validation.py b/django/contrib/auth/password_validation.py index 06f8fcc4e8..d24e69e0ce 100644 --- a/django/contrib/auth/password_validation.py +++ b/django/contrib/auth/password_validation.py @@ -106,17 +106,16 @@ class MinimumLengthValidator: def validate(self, password, user=None): if len(password) < self.min_length: - raise ValidationError( - ngettext( - "This password is too short. It must contain at least " - "%(min_length)d character.", - "This password is too short. It must contain at least " - "%(min_length)d characters.", - self.min_length, - ), - code="password_too_short", - params={"min_length": self.min_length}, - ) + raise ValidationError(self.get_error_message(), code="password_too_short") + + def get_error_message(self): + return ngettext( + "This password is too short. It must contain at least %d character." + % self.min_length, + "This password is too short. It must contain at least %d characters." + % self.min_length, + self.min_length, + ) def get_help_text(self): return ngettext( @@ -203,11 +202,14 @@ class UserAttributeSimilarityValidator: except FieldDoesNotExist: verbose_name = attribute_name raise ValidationError( - _("The password is too similar to the %(verbose_name)s."), + self.get_error_message(), code="password_too_similar", params={"verbose_name": verbose_name}, ) + def get_error_message(self): + return _("The password is too similar to the %(verbose_name)s.") + def get_help_text(self): return _( "Your password can’t be too similar to your other personal information." @@ -242,10 +244,13 @@ class CommonPasswordValidator: def validate(self, password, user=None): if password.lower().strip() in self.passwords: raise ValidationError( - _("This password is too common."), + self.get_error_message(), code="password_too_common", ) + def get_error_message(self): + return _("This password is too common.") + def get_help_text(self): return _("Your password can’t be a commonly used password.") @@ -258,9 +263,12 @@ class NumericPasswordValidator: def validate(self, password, user=None): if password.isdigit(): raise ValidationError( - _("This password is entirely numeric."), + self.get_error_message(), code="password_entirely_numeric", ) + def get_error_message(self): + return _("This password is entirely numeric.") + def get_help_text(self): return _("Your password can’t be entirely numeric.") diff --git a/django/contrib/contenttypes/locale/ga/LC_MESSAGES/django.mo b/django/contrib/contenttypes/locale/ga/LC_MESSAGES/django.mo index 6b0363a868..4b96fe58d0 100644 Binary files a/django/contrib/contenttypes/locale/ga/LC_MESSAGES/django.mo and b/django/contrib/contenttypes/locale/ga/LC_MESSAGES/django.mo differ diff --git a/django/contrib/contenttypes/locale/ga/LC_MESSAGES/django.po b/django/contrib/contenttypes/locale/ga/LC_MESSAGES/django.po index 864119019b..40229fc763 100644 --- a/django/contrib/contenttypes/locale/ga/LC_MESSAGES/django.po +++ b/django/contrib/contenttypes/locale/ga/LC_MESSAGES/django.po @@ -1,6 +1,7 @@ # This file is distributed under the same license as the Django package. # # Translators: +# Aindriú Mac Giolla Eoin, 2024 # Jannis Leidel , 2011 # Luke Blaney , 2019 # Michael Thornhill , 2012 @@ -8,10 +9,10 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-01-17 11:07+0100\n" -"PO-Revision-Date: 2019-06-22 21:48+0000\n" -"Last-Translator: Luke Blaney \n" -"Language-Team: Irish (http://www.transifex.com/django/django/language/ga/)\n" +"POT-Creation-Date: 2019-09-08 17:27+0200\n" +"PO-Revision-Date: 2024-10-07 19:22+0000\n" +"Last-Translator: Aindriú Mac Giolla Eoin, 2024\n" +"Language-Team: Irish (http://app.transifex.com/django/django/language/ga/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -36,9 +37,9 @@ msgid "Content type %(ct_id)s object has no associated model" msgstr "Ní bhaineann samhail leis an cineál inneachar %(ct_id)s" #, python-format -msgid "Content type %(ct_id)s object %(obj_id)s doesn't exist" -msgstr "Níl cineál inneachar %(ct_id)s oibiacht %(obj_id)s ann" +msgid "Content type %(ct_id)s object %(obj_id)s doesn’t exist" +msgstr "Níl cineál ábhair %(ct_id)s réad %(obj_id)s ann" #, python-format -msgid "%(ct_name)s objects don't have a get_absolute_url() method" -msgstr "Níl modh get_absolute_url() ag %(ct_name)s oibiachtaí" +msgid "%(ct_name)s objects don’t have a get_absolute_url() method" +msgstr "Níl modh get_absolute_url() ag réada %(ct_name)s" diff --git a/django/contrib/flatpages/locale/ga/LC_MESSAGES/django.mo b/django/contrib/flatpages/locale/ga/LC_MESSAGES/django.mo index 8e80c1071e..fdb167784d 100644 Binary files a/django/contrib/flatpages/locale/ga/LC_MESSAGES/django.mo and b/django/contrib/flatpages/locale/ga/LC_MESSAGES/django.mo differ diff --git a/django/contrib/flatpages/locale/ga/LC_MESSAGES/django.po b/django/contrib/flatpages/locale/ga/LC_MESSAGES/django.po index 3f0b097586..5db5dba346 100644 --- a/django/contrib/flatpages/locale/ga/LC_MESSAGES/django.po +++ b/django/contrib/flatpages/locale/ga/LC_MESSAGES/django.po @@ -1,16 +1,17 @@ # This file is distributed under the same license as the Django package. # # Translators: +# Aindriú Mac Giolla Eoin, 2024 # Jannis Leidel , 2011 # Michael Thornhill , 2011-2012,2015 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-10-09 17:42+0200\n" -"PO-Revision-Date: 2017-09-23 18:54+0000\n" -"Last-Translator: Jannis Leidel \n" -"Language-Team: Irish (http://www.transifex.com/django/django/language/ga/)\n" +"POT-Creation-Date: 2019-09-08 17:27+0200\n" +"PO-Revision-Date: 2024-10-07 19:03+0000\n" +"Last-Translator: Aindriú Mac Giolla Eoin, 2024\n" +"Language-Team: Irish (http://app.transifex.com/django/django/language/ga/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -28,9 +29,9 @@ msgid "URL" msgstr "URL" msgid "" -"Example: '/about/contact/'. Make sure to have leading and trailing slashes." +"Example: “/about/contact/”. Make sure to have leading and trailing slashes." msgstr "" -"Sampla '/about/contact/' Déan cinnte go bhfuil príomhslaid agus cúlslais " +"Sampla: “/ faoi/teagmháil/”. Bí cinnte go bhfuil slais tosaigh agus slaise " "agat." msgid "" @@ -40,6 +41,9 @@ msgstr "" "Ní mór an luach a bhfuil ach litreacha, uimhreacha, poncanna, béim, dashes, " "slaiseanna nó thilde." +msgid "Example: “/about/contact”. Make sure to have a leading slash." +msgstr "Sampla: “/ faoi/teagmháil”. Bí cinnte go bhfuil slais tosaigh." + msgid "URL is missing a leading slash." msgstr "Tá slais tosaigh in easnamh ag an URL." @@ -63,11 +67,11 @@ msgid "template name" msgstr "ainm an teimpléid" msgid "" -"Example: 'flatpages/contact_page.html'. If this isn't provided, the system " -"will use 'flatpages/default.html'." +"Example: “flatpages/contact_page.html”. If this isn’t provided, the system " +"will use “flatpages/default.html”." msgstr "" -"Sampla: 'flatpages/contact_page.html'. Muna bhfuil sé ar soláthair, bainfidh " -"an córás úsáid as 'flatpages/default.html'." +"Sampla: “flatpages/contact_page.html”. Mura gcuirtear é seo ar fáil, " +"úsáidfidh an córas “flatpages/default.html”." msgid "registration required" msgstr "clárúchán riachtanach" @@ -78,7 +82,7 @@ msgstr "" "leathanach seo a fheiceail" msgid "sites" -msgstr "" +msgstr "láithreáin" msgid "flat page" msgstr "leacleathanach" diff --git a/django/contrib/gis/locale/ga/LC_MESSAGES/django.mo b/django/contrib/gis/locale/ga/LC_MESSAGES/django.mo index 846f0d559c..ea86255550 100644 Binary files a/django/contrib/gis/locale/ga/LC_MESSAGES/django.mo and b/django/contrib/gis/locale/ga/LC_MESSAGES/django.mo differ diff --git a/django/contrib/gis/locale/ga/LC_MESSAGES/django.po b/django/contrib/gis/locale/ga/LC_MESSAGES/django.po index e8de66a38b..482e384cff 100644 --- a/django/contrib/gis/locale/ga/LC_MESSAGES/django.po +++ b/django/contrib/gis/locale/ga/LC_MESSAGES/django.po @@ -1,16 +1,17 @@ # This file is distributed under the same license as the Django package. # # Translators: +# Aindriú Mac Giolla Eoin, 2024 # Jannis Leidel , 2011 # Michael Thornhill , 2012 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-01-19 16:49+0100\n" -"PO-Revision-Date: 2017-09-23 18:54+0000\n" -"Last-Translator: Jannis Leidel \n" -"Language-Team: Irish (http://www.transifex.com/django/django/language/ga/)\n" +"POT-Creation-Date: 2023-09-18 11:41-0300\n" +"PO-Revision-Date: 2024-10-07 18:45+0000\n" +"Last-Translator: Aindriú Mac Giolla Eoin, 2024\n" +"Language-Team: Irish (http://app.transifex.com/django/django/language/ga/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -19,14 +20,16 @@ msgstr "" "4);\n" msgid "GIS" -msgstr "" +msgstr "GIS" msgid "The base GIS field." -msgstr "" +msgstr "Réimse bonn GIS." msgid "" -"The base Geometry field -- maps to the OpenGIS Specification Geometry type." +"The base Geometry field — maps to the OpenGIS Specification Geometry type." msgstr "" +"An bonnréimse Céimseata — léarscáileanna chuig an gcineál Céimseata " +"Sonraíochta OpenGIS." msgid "Point" msgstr "Pointe" @@ -50,10 +53,10 @@ msgid "Geometry collection" msgstr "Céimseata bhailiú" msgid "Extent Aggregate Field" -msgstr "" +msgstr "Méid Réimse Comhiomlán" msgid "Raster Field" -msgstr "" +msgstr "Réimse Raster" msgid "No geometry value provided." msgstr "Ní soláthair méid geoiméadracht" @@ -72,17 +75,14 @@ msgstr "" "geoiméadracht." msgid "Delete all Features" -msgstr "" - -msgid "WKT debugging window:" -msgstr "" +msgstr "Scrios na Gnéithe go léir" msgid "Debugging window (serialized value)" -msgstr "" +msgstr "Fuinneog dífhabhtaithe (luach sraitheach)" msgid "No feeds are registered." msgstr "Níl fothaí cláraithe." #, python-format -msgid "Slug %r isn't registered." -msgstr "Níl slug %r cláraithe." +msgid "Slug %r isn’t registered." +msgstr "Níl seilide %r cláraithe." diff --git a/django/contrib/humanize/locale/ga/LC_MESSAGES/django.mo b/django/contrib/humanize/locale/ga/LC_MESSAGES/django.mo index 1fb51b9f43..abbfd57980 100644 Binary files a/django/contrib/humanize/locale/ga/LC_MESSAGES/django.mo and b/django/contrib/humanize/locale/ga/LC_MESSAGES/django.mo differ diff --git a/django/contrib/humanize/locale/ga/LC_MESSAGES/django.po b/django/contrib/humanize/locale/ga/LC_MESSAGES/django.po index 5108ded756..0e99520b73 100644 --- a/django/contrib/humanize/locale/ga/LC_MESSAGES/django.po +++ b/django/contrib/humanize/locale/ga/LC_MESSAGES/django.po @@ -1,6 +1,7 @@ # This file is distributed under the same license as the Django package. # # Translators: +# Aindriú Mac Giolla Eoin, 2024 # Jannis Leidel , 2011 # Luke Blaney , 2019 # Michael Thornhill , 2011-2012 @@ -8,10 +9,10 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-01-16 20:42+0100\n" -"PO-Revision-Date: 2019-06-22 21:44+0000\n" -"Last-Translator: Luke Blaney \n" -"Language-Team: Irish (http://www.transifex.com/django/django/language/ga/)\n" +"POT-Creation-Date: 2023-09-18 11:41-0300\n" +"PO-Revision-Date: 2024-10-07 18:40+0000\n" +"Last-Translator: Aindriú Mac Giolla Eoin, 2024\n" +"Language-Team: Irish (http://app.transifex.com/django/django/language/ga/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -20,7 +21,7 @@ msgstr "" "4);\n" msgid "Humanize" -msgstr "" +msgstr "Déan daonnachadh" #. Translators: Ordinal format for 11 (11th), 12 (12th), and 13 (13th). msgctxt "ordinal 11, 12, 13" @@ -42,7 +43,7 @@ msgctxt "ordinal 2" msgid "{}nd" msgstr "{}ú" -#. Translators: Ordinal format when value ends with 3, e.g. 83th, except 13. +#. Translators: Ordinal format when value ends with 3, e.g. 83rd, except 13. msgctxt "ordinal 3" msgid "{}rd" msgstr "{}ú" @@ -77,15 +78,6 @@ msgctxt "ordinal 9" msgid "{}th" msgstr "{}ú" -#, python-format -msgid "%(value).1f million" -msgid_plural "%(value).1f million" -msgstr[0] "%(value).1f milliún" -msgstr[1] "%(value).1f milliún" -msgstr[2] "%(value).1f milliún" -msgstr[3] "%(value).1f milliún" -msgstr[4] "%(value).1f milliún" - #, python-format msgid "%(value)s million" msgid_plural "%(value)s million" @@ -95,15 +87,6 @@ msgstr[2] " %(value)s milliún" msgstr[3] " %(value)s milliún" msgstr[4] " %(value)s milliún" -#, python-format -msgid "%(value).1f billion" -msgid_plural "%(value).1f billion" -msgstr[0] "%(value).1f billiún" -msgstr[1] "%(value).1f billiún" -msgstr[2] "%(value).1f billiún" -msgstr[3] "%(value).1f billiún" -msgstr[4] "%(value).1f billiún" - #, python-format msgid "%(value)s billion" msgid_plural "%(value)s billion" @@ -113,15 +96,6 @@ msgstr[2] " %(value)s billiún" msgstr[3] " %(value)s billiún" msgstr[4] " %(value)s billiún" -#, python-format -msgid "%(value).1f trillion" -msgid_plural "%(value).1f trillion" -msgstr[0] "%(value).1f trilliún" -msgstr[1] "%(value).1f trilliún" -msgstr[2] "%(value).1f trilliún" -msgstr[3] "%(value).1f trilliún" -msgstr[4] "%(value).1f trilliún" - #, python-format msgid "%(value)s trillion" msgid_plural "%(value)s trillion" @@ -131,15 +105,6 @@ msgstr[2] " %(value)s trilliún" msgstr[3] " %(value)s trilliún" msgstr[4] " %(value)s trilliún" -#, python-format -msgid "%(value).1f quadrillion" -msgid_plural "%(value).1f quadrillion" -msgstr[0] "%(value).1f quadrilliún" -msgstr[1] "%(value).1f quadrilliún" -msgstr[2] "%(value).1f quadrilliún" -msgstr[3] "%(value).1f quadrilliún" -msgstr[4] "%(value).1f quadrilliún" - #, python-format msgid "%(value)s quadrillion" msgid_plural "%(value)s quadrillion" @@ -149,15 +114,6 @@ msgstr[2] "%(value)s quadrilliún" msgstr[3] "%(value)s quadrilliún" msgstr[4] "%(value)s quadrilliún" -#, python-format -msgid "%(value).1f quintillion" -msgid_plural "%(value).1f quintillion" -msgstr[0] "%(value).1f quintillion" -msgstr[1] "%(value).1f quintillion" -msgstr[2] "%(value).1f quintillion" -msgstr[3] "%(value).1f quintillion" -msgstr[4] "%(value).1f quintillion" - #, python-format msgid "%(value)s quintillion" msgid_plural "%(value)s quintillion" @@ -167,15 +123,6 @@ msgstr[2] "%(value)s quintillion" msgstr[3] "%(value)s quintillion" msgstr[4] "%(value)s quintillion" -#, python-format -msgid "%(value).1f sextillion" -msgid_plural "%(value).1f sextillion" -msgstr[0] "%(value).1f sextillion" -msgstr[1] "%(value).1f sextillion" -msgstr[2] "%(value).1f sextillion" -msgstr[3] "%(value).1f sextillion" -msgstr[4] "%(value).1f sextillion" - #, python-format msgid "%(value)s sextillion" msgid_plural "%(value)s sextillion" @@ -185,15 +132,6 @@ msgstr[2] "%(value)s sextillion" msgstr[3] "%(value)s sextillion" msgstr[4] "%(value)s sextillion" -#, python-format -msgid "%(value).1f septillion" -msgid_plural "%(value).1f septillion" -msgstr[0] "%(value).1f septillion" -msgstr[1] "%(value).1f septillion" -msgstr[2] "%(value).1f septillion" -msgstr[3] "%(value).1f septillion" -msgstr[4] "%(value).1f septillion" - #, python-format msgid "%(value)s septillion" msgid_plural "%(value)s septillion" @@ -203,15 +141,6 @@ msgstr[2] "%(value)s septillion" msgstr[3] "%(value)s septillion" msgstr[4] "%(value)s septillion" -#, python-format -msgid "%(value).1f octillion" -msgid_plural "%(value).1f octillion" -msgstr[0] "%(value).1f octillion" -msgstr[1] "%(value).1f octillion" -msgstr[2] "%(value).1f octillion" -msgstr[3] "%(value).1f octillion" -msgstr[4] "%(value).1f octillion" - #, python-format msgid "%(value)s octillion" msgid_plural "%(value)s octillion" @@ -221,15 +150,6 @@ msgstr[2] "%(value)s octillion" msgstr[3] "%(value)s octillion" msgstr[4] "%(value)s octillion" -#, python-format -msgid "%(value).1f nonillion" -msgid_plural "%(value).1f nonillion" -msgstr[0] "%(value).1f nonillion" -msgstr[1] "%(value).1f nonillion" -msgstr[2] "%(value).1f nonillion" -msgstr[3] "%(value).1f nonillion" -msgstr[4] "%(value).1f nonillion" - #, python-format msgid "%(value)s nonillion" msgid_plural "%(value)s nonillion" @@ -239,15 +159,6 @@ msgstr[2] "%(value)s nonillion" msgstr[3] "%(value)s nonillion" msgstr[4] "%(value)s nonillion" -#, python-format -msgid "%(value).1f decillion" -msgid_plural "%(value).1f decillion" -msgstr[0] "%(value).1f decillion" -msgstr[1] "%(value).1f decillion" -msgstr[2] "%(value).1f decillion" -msgstr[3] "%(value).1f decillion" -msgstr[4] "%(value).1f decillion" - #, python-format msgid "%(value)s decillion" msgid_plural "%(value)s decillion" @@ -257,15 +168,6 @@ msgstr[2] "%(value)s decillion" msgstr[3] "%(value)s decillion" msgstr[4] "%(value)s decillion" -#, python-format -msgid "%(value).1f googol" -msgid_plural "%(value).1f googol" -msgstr[0] "%(value).1f googol" -msgstr[1] "%(value).1f googol" -msgstr[2] "%(value).1f googol" -msgstr[3] "%(value).1f googol" -msgstr[4] "%(value).1f googol" - #, python-format msgid "%(value)s googol" msgid_plural "%(value)s googol" @@ -315,40 +217,40 @@ msgstr "inné" #. weeks' #, python-format msgid "%(delta)s ago" -msgstr "" +msgstr "%(delta)s ó shin" #. Translators: please keep a non-breaking space (U+00A0) between count #. and time unit. #, python-format msgid "an hour ago" msgid_plural "%(count)s hours ago" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" -msgstr[3] "" -msgstr[4] "" +msgstr[0] "uair an chloig ó shin" +msgstr[1] "%(count)s uair an chloig ó shin" +msgstr[2] "%(count)s uair an chloig ó shin" +msgstr[3] "%(count)s uair an chloig ó shin" +msgstr[4] "%(count)s uair an chloig ó shin" #. Translators: please keep a non-breaking space (U+00A0) between count #. and time unit. #, python-format msgid "a minute ago" msgid_plural "%(count)s minutes ago" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" -msgstr[3] "" -msgstr[4] "" +msgstr[0] "nóiméad ó shin" +msgstr[1] "%(count)s nóiméad ó shin" +msgstr[2] "%(count)s nóiméad ó shin" +msgstr[3] "%(count)s nóiméad ó shin" +msgstr[4] "%(count)s nóiméad ó shin" #. Translators: please keep a non-breaking space (U+00A0) between count #. and time unit. #, python-format msgid "a second ago" msgid_plural "%(count)s seconds ago" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" -msgstr[3] "" -msgstr[4] "" +msgstr[0] "soicind ó shin" +msgstr[1] "%(count)s soicindí ó shin" +msgstr[2] "%(count)s soicindí ó shin" +msgstr[3] "%(count)s soicindí ó shin" +msgstr[4] "%(count)s soicindí ó shin" msgid "now" msgstr "anois" @@ -358,159 +260,159 @@ msgstr "anois" #, python-format msgid "a second from now" msgid_plural "%(count)s seconds from now" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" -msgstr[3] "" -msgstr[4] "" +msgstr[0] "soicind as seo amach" +msgstr[1] "%(count)s soicind as seo amach" +msgstr[2] "%(count)s soicind as seo amach" +msgstr[3] "%(count)s soicind as seo amach" +msgstr[4] "%(count)s soicind as seo amach" #. Translators: please keep a non-breaking space (U+00A0) between count #. and time unit. #, python-format msgid "a minute from now" msgid_plural "%(count)s minutes from now" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" -msgstr[3] "" -msgstr[4] "" +msgstr[0] "nóiméad ó anois" +msgstr[1] "%(count)s nóiméad as seo" +msgstr[2] "%(count)s nóiméad as seo" +msgstr[3] "%(count)s nóiméad as seo" +msgstr[4] "%(count)s nóiméad as seo" #. Translators: please keep a non-breaking space (U+00A0) between count #. and time unit. #, python-format msgid "an hour from now" msgid_plural "%(count)s hours from now" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" -msgstr[3] "" -msgstr[4] "" +msgstr[0] "uair an chloig ó anois" +msgstr[1] "%(count)s uair an chloig as seo amach" +msgstr[2] "%(count)s uair an chloig as seo amach" +msgstr[3] "%(count)s uair an chloig as seo amach" +msgstr[4] "%(count)s uair an chloig as seo amach" #. Translators: delta will contain a string like '2 months' or '1 month, 2 #. weeks' #, python-format msgid "%(delta)s from now" -msgstr "" +msgstr "%(delta)s as seo amach" #. Translators: 'naturaltime-past' strings will be included in '%(delta)s ago' #, python-format msgctxt "naturaltime-past" -msgid "%d year" -msgid_plural "%d years" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" -msgstr[3] "" -msgstr[4] "" +msgid "%(num)d year" +msgid_plural "%(num)d years" +msgstr[0] "%(num)d bhliain" +msgstr[1] "%(num)d bliain" +msgstr[2] "%(num)d bliain" +msgstr[3] "%(num)d bliain" +msgstr[4] "%(num)d bliain" #, python-format msgctxt "naturaltime-past" -msgid "%d month" -msgid_plural "%d months" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" -msgstr[3] "" -msgstr[4] "" +msgid "%(num)d month" +msgid_plural "%(num)d months" +msgstr[0] "%(num)d mí" +msgstr[1] "%(num)d mí" +msgstr[2] "%(num)d mí" +msgstr[3] "%(num)d mí" +msgstr[4] "%(num)d mí" #, python-format msgctxt "naturaltime-past" -msgid "%d week" -msgid_plural "%d weeks" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" -msgstr[3] "" -msgstr[4] "" +msgid "%(num)d week" +msgid_plural "%(num)d weeks" +msgstr[0] "%(num)d seachtain" +msgstr[1] "%(num)d seachtain" +msgstr[2] "%(num)d seachtain" +msgstr[3] "%(num)d seachtain" +msgstr[4] "%(num)d seachtain" #, python-format msgctxt "naturaltime-past" -msgid "%d day" -msgid_plural "%d days" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" -msgstr[3] "" -msgstr[4] "" +msgid "%(num)d day" +msgid_plural "%(num)d days" +msgstr[0] "%(num)d lá" +msgstr[1] "%(num)d lá" +msgstr[2] "%(num)d lá" +msgstr[3] "%(num)d lá" +msgstr[4] "%(num)d lá" #, python-format msgctxt "naturaltime-past" -msgid "%d hour" -msgid_plural "%d hours" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" -msgstr[3] "" -msgstr[4] "" +msgid "%(num)d hour" +msgid_plural "%(num)d hours" +msgstr[0] "%(num)d uair" +msgstr[1] "%(num)d uair an chloig" +msgstr[2] "%(num)d uair an chloig" +msgstr[3] "%(num)d uair an chloig" +msgstr[4] "%(num)d uair an chloig" #, python-format msgctxt "naturaltime-past" -msgid "%d minute" -msgid_plural "%d minutes" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" -msgstr[3] "" -msgstr[4] "" +msgid "%(num)d minute" +msgid_plural "%(num)d minutes" +msgstr[0] "%(num)d nóiméad" +msgstr[1] "%(num)d nóiméad" +msgstr[2] "%(num)d nóiméad" +msgstr[3] "%(num)d nóiméad" +msgstr[4] "%(num)d nóiméad" #. Translators: 'naturaltime-future' strings will be included in '%(delta)s #. from now' #, python-format msgctxt "naturaltime-future" -msgid "%d year" -msgid_plural "%d years" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" -msgstr[3] "" -msgstr[4] "" +msgid "%(num)d year" +msgid_plural "%(num)d years" +msgstr[0] "%(num)d bhliain" +msgstr[1] "%(num)d bliain" +msgstr[2] "%(num)d bliain" +msgstr[3] "%(num)d bliain" +msgstr[4] "%(num)d bliain" #, python-format msgctxt "naturaltime-future" -msgid "%d month" -msgid_plural "%d months" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" -msgstr[3] "" -msgstr[4] "" +msgid "%(num)d month" +msgid_plural "%(num)d months" +msgstr[0] "%(num)d mí" +msgstr[1] "%(num)d mí" +msgstr[2] "%(num)d mí" +msgstr[3] "%(num)d mí" +msgstr[4] "%(num)d mí" #, python-format msgctxt "naturaltime-future" -msgid "%d week" -msgid_plural "%d weeks" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" -msgstr[3] "" -msgstr[4] "" +msgid "%(num)d week" +msgid_plural "%(num)d weeks" +msgstr[0] "%(num)d seachtain" +msgstr[1] "%(num)d seachtain" +msgstr[2] "%(num)d seachtain" +msgstr[3] "%(num)d seachtain" +msgstr[4] "%(num)d seachtain" #, python-format msgctxt "naturaltime-future" -msgid "%d day" -msgid_plural "%d days" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" -msgstr[3] "" -msgstr[4] "" +msgid "%(num)d day" +msgid_plural "%(num)d days" +msgstr[0] "%(num)d lá" +msgstr[1] "%(num)d lá" +msgstr[2] "%(num)d lá" +msgstr[3] "%(num)d lá" +msgstr[4] "%(num)d lá" #, python-format msgctxt "naturaltime-future" -msgid "%d hour" -msgid_plural "%d hours" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" -msgstr[3] "" -msgstr[4] "" +msgid "%(num)d hour" +msgid_plural "%(num)d hours" +msgstr[0] "%(num)d uair" +msgstr[1] "%(num)d uair an chloig" +msgstr[2] "%(num)d uair an chloig" +msgstr[3] "%(num)d uair an chloig" +msgstr[4] "%(num)d uair an chloig" #, python-format msgctxt "naturaltime-future" -msgid "%d minute" -msgid_plural "%d minutes" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" -msgstr[3] "" -msgstr[4] "" +msgid "%(num)d minute" +msgid_plural "%(num)d minutes" +msgstr[0] "%(num)d nóiméad" +msgstr[1] "%(num)d nóiméad" +msgstr[2] "%(num)d nóiméad" +msgstr[3] "%(num)d nóiméad" +msgstr[4] "%(num)d nóiméad" diff --git a/django/contrib/postgres/forms/array.py b/django/contrib/postgres/forms/array.py index ddb022afc3..fd5cd219f8 100644 --- a/django/contrib/postgres/forms/array.py +++ b/django/contrib/postgres/forms/array.py @@ -228,7 +228,7 @@ class SplitArrayField(forms.Field): params={"nth": index + 1}, ) ) - cleaned_data.append(None) + cleaned_data.append(item) else: errors.append(None) cleaned_data, null_index = self._remove_trailing_nulls(cleaned_data) diff --git a/django/contrib/postgres/locale/cs/LC_MESSAGES/django.mo b/django/contrib/postgres/locale/cs/LC_MESSAGES/django.mo index a56d0b4e29..c8e64ab39f 100644 Binary files a/django/contrib/postgres/locale/cs/LC_MESSAGES/django.mo and b/django/contrib/postgres/locale/cs/LC_MESSAGES/django.mo differ diff --git a/django/contrib/postgres/locale/cs/LC_MESSAGES/django.po b/django/contrib/postgres/locale/cs/LC_MESSAGES/django.po index 6a3c9d1d72..ae65f5fe46 100644 --- a/django/contrib/postgres/locale/cs/LC_MESSAGES/django.po +++ b/django/contrib/postgres/locale/cs/LC_MESSAGES/django.po @@ -1,16 +1,17 @@ # This file is distributed under the same license as the Django package. # # Translators: +# Jiří Podhorecký , 2024 # Tomáš Ehrlich , 2015 # Vláďa Macek , 2015-2019 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-05-11 20:56+0200\n" -"PO-Revision-Date: 2020-05-12 20:01+0000\n" -"Last-Translator: Transifex Bot <>\n" -"Language-Team: Czech (http://www.transifex.com/django/django/language/cs/)\n" +"POT-Creation-Date: 2023-01-17 02:13-0600\n" +"PO-Revision-Date: 2024-10-07 09:22+0000\n" +"Last-Translator: Jiří Podhorecký , 2024\n" +"Language-Team: Czech (http://app.transifex.com/django/django/language/cs/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -109,11 +110,10 @@ msgstr "Byly zadány neznámé klíče: %(keys)s" #, python-format msgid "" -"Ensure that this range is completely less than or equal to %(limit_value)s." -msgstr "Nejvyšší hodnota rozsahu musí být menší nebo rovna %(limit_value)s." +"Ensure that the upper bound of the range is not greater than %(limit_value)s." +msgstr "Ujistěte se, že horní hranice rozsahu není větší než %(limit_value)s." #, python-format msgid "" -"Ensure that this range is completely greater than or equal to " -"%(limit_value)s." -msgstr "Nejnižší hodnota rozsahu musí být větší nebo rovna %(limit_value)s." +"Ensure that the lower bound of the range is not less than %(limit_value)s." +msgstr "Ujistěte se, že spodní hranice rozsahu není menší než %(limit_value)s." diff --git a/django/contrib/postgres/locale/ga/LC_MESSAGES/django.mo b/django/contrib/postgres/locale/ga/LC_MESSAGES/django.mo new file mode 100644 index 0000000000..0f8fe8b46f Binary files /dev/null and b/django/contrib/postgres/locale/ga/LC_MESSAGES/django.mo differ diff --git a/django/contrib/postgres/locale/ga/LC_MESSAGES/django.po b/django/contrib/postgres/locale/ga/LC_MESSAGES/django.po new file mode 100644 index 0000000000..45babc89a3 --- /dev/null +++ b/django/contrib/postgres/locale/ga/LC_MESSAGES/django.po @@ -0,0 +1,125 @@ +# This file is distributed under the same license as the Django package. +# +# Translators: +# Aindriú Mac Giolla Eoin, 2024 +msgid "" +msgstr "" +"Project-Id-Version: django\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-01-17 02:13-0600\n" +"PO-Revision-Date: 2024-10-07 09:22+0000\n" +"Last-Translator: Aindriú Mac Giolla Eoin, 2024\n" +"Language-Team: Irish (http://app.transifex.com/django/django/language/ga/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: ga\n" +"Plural-Forms: nplurals=5; plural=(n==1 ? 0 : n==2 ? 1 : n<7 ? 2 : n<11 ? 3 : " +"4);\n" + +msgid "PostgreSQL extensions" +msgstr "Eisínteachtaí PostgreSQL" + +#, python-format +msgid "Item %(nth)s in the array did not validate:" +msgstr "Níor bhailíochtaigh mír %(nth)s san eagar:" + +msgid "Nested arrays must have the same length." +msgstr "Caithfidh an fad céanna a bheith ag eagair neadaithe." + +msgid "Map of strings to strings/nulls" +msgstr "Léarscáil de theaghráin go teaghráin/nulls" + +#, python-format +msgid "The value of “%(key)s” is not a string or null." +msgstr "Ní teaghrán nó null é luach “%(key)s”." + +msgid "Could not load JSON data." +msgstr "Níorbh fhéidir sonraí JSON a lódáil." + +msgid "Input must be a JSON dictionary." +msgstr "Ní mór gur foclóir JSON é an t-ionchur." + +msgid "Enter two valid values." +msgstr "Cuir isteach dhá luach bhailí." + +msgid "The start of the range must not exceed the end of the range." +msgstr "Ní fhéadfaidh tús an raoin a bheith níos mó ná deireadh an raoin." + +msgid "Enter two whole numbers." +msgstr "Cuir isteach dhá slánuimhir." + +msgid "Enter two numbers." +msgstr "Cuir isteach dhá uimhir." + +msgid "Enter two valid date/times." +msgstr "Cuir isteach dhá dháta bhailí." + +msgid "Enter two valid dates." +msgstr "Cuir isteach dhá dháta bhailí." + +#, python-format +msgid "" +"List contains %(show_value)d item, it should contain no more than " +"%(limit_value)d." +msgid_plural "" +"List contains %(show_value)d items, it should contain no more than " +"%(limit_value)d." +msgstr[0] "" +"Tá %(show_value)d mír ar an liosta, níor cheart go mbeadh níos mó ná " +"%(limit_value)d ann." +msgstr[1] "" +"Tá %(show_value)d míreanna ar an liosta, níor cheart go mbeadh níos mó ná " +"%(limit_value)d ann." +msgstr[2] "" +"Tá %(show_value)d míreanna ar an liosta, níor cheart go mbeadh níos mó ná " +"%(limit_value)d ann." +msgstr[3] "" +"Tá %(show_value)d míreanna ar an liosta, níor cheart go mbeadh níos mó ná " +"%(limit_value)d ann." +msgstr[4] "" +"Tá %(show_value)d míreanna ar an liosta, níor cheart go mbeadh níos mó ná " +"%(limit_value)d ann." + +#, python-format +msgid "" +"List contains %(show_value)d item, it should contain no fewer than " +"%(limit_value)d." +msgid_plural "" +"List contains %(show_value)d items, it should contain no fewer than " +"%(limit_value)d." +msgstr[0] "" +"Tá %(show_value)d mír ar an liosta, níor cheart go mbeadh níos lú ná " +"%(limit_value)d ann." +msgstr[1] "" +"Tá %(show_value)d míreanna ar an liosta, níor cheart go mbeadh níos lú ná " +"%(limit_value)d ann." +msgstr[2] "" +"Tá %(show_value)d míreanna ar an liosta, níor cheart go mbeadh níos lú ná " +"%(limit_value)d ann." +msgstr[3] "" +"Tá %(show_value)d míreanna ar an liosta, níor cheart go mbeadh níos lú ná " +"%(limit_value)d ann." +msgstr[4] "" +"Tá %(show_value)d míreanna ar an liosta, níor cheart go mbeadh níos lú ná " +"%(limit_value)d ann." + +#, python-format +msgid "Some keys were missing: %(keys)s" +msgstr "Bhí roinnt eochracha ar iarraidh: %(keys)s" + +#, python-format +msgid "Some unknown keys were provided: %(keys)s" +msgstr "Soláthraíodh roinnt eochracha anaithnid: %(keys)s" + +#, python-format +msgid "" +"Ensure that the upper bound of the range is not greater than %(limit_value)s." +msgstr "" +"Cinntigh nach bhfuil teorainn uachtarach an raoin níos mó ná %(limit_value)s." + +#, python-format +msgid "" +"Ensure that the lower bound of the range is not less than %(limit_value)s." +msgstr "" +"Cinntigh nach bhfuil teorainn íochtair an raoin níos lú ná %(limit_value)s." diff --git a/django/contrib/postgres/operations.py b/django/contrib/postgres/operations.py index 1ee5fbc2e2..84360febf9 100644 --- a/django/contrib/postgres/operations.py +++ b/django/contrib/postgres/operations.py @@ -237,6 +237,11 @@ class CreateCollation(CollationOperation): def migration_name_fragment(self): return "create_collation_%s" % self.name.lower() + def reduce(self, operation, app_label): + if isinstance(operation, RemoveCollation) and self.name == operation.name: + return [] + return super().reduce(operation, app_label) + class RemoveCollation(CollationOperation): """Remove a collation.""" diff --git a/django/contrib/redirects/locale/ga/LC_MESSAGES/django.mo b/django/contrib/redirects/locale/ga/LC_MESSAGES/django.mo index 52e3967c42..0ed0c01435 100644 Binary files a/django/contrib/redirects/locale/ga/LC_MESSAGES/django.mo and b/django/contrib/redirects/locale/ga/LC_MESSAGES/django.mo differ diff --git a/django/contrib/redirects/locale/ga/LC_MESSAGES/django.po b/django/contrib/redirects/locale/ga/LC_MESSAGES/django.po index 8ad2ce1ab8..805902afdd 100644 --- a/django/contrib/redirects/locale/ga/LC_MESSAGES/django.po +++ b/django/contrib/redirects/locale/ga/LC_MESSAGES/django.po @@ -1,6 +1,7 @@ # This file is distributed under the same license as the Django package. # # Translators: +# Aindriú Mac Giolla Eoin, 2024 # Jannis Leidel , 2011 # Luke Blaney , 2019 # Michael Thornhill , 2015 @@ -8,10 +9,10 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-10-09 17:42+0200\n" -"PO-Revision-Date: 2019-06-22 21:46+0000\n" -"Last-Translator: Luke Blaney \n" -"Language-Team: Irish (http://www.transifex.com/django/django/language/ga/)\n" +"POT-Creation-Date: 2021-01-15 09:00+0100\n" +"PO-Revision-Date: 2024-10-07 18:32+0000\n" +"Last-Translator: Aindriú Mac Giolla Eoin, 2024\n" +"Language-Team: Irish (http://app.transifex.com/django/django/language/ga/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -29,17 +30,21 @@ msgid "redirect from" msgstr "atreoraigh ó" msgid "" -"This should be an absolute path, excluding the domain name. Example: '/" -"events/search/'." -msgstr "Teastaíonn dearbhchosán gan ainm fearainn. Sampla '/events/search/'." +"This should be an absolute path, excluding the domain name. Example: “/" +"events/search/”." +msgstr "" +"Ba cheart gur cosán iomlán é seo, gan an t-ainm fearainn a áireamh. Sampla: " +"\\u201c/events/search/\\u201d." msgid "redirect to" msgstr "atreoraigh go dtí" msgid "" -"This can be either an absolute path (as above) or a full URL starting with " -"'http://'." -msgstr "Is féidir dearbhchosán nó URL lán ag tosnú le 'http://'." +"This can be either an absolute path (as above) or a full URL starting with a " +"scheme such as “https://”." +msgstr "" +"Is féidir gur cosán iomlán é seo (mar atá thuas) nó URL iomlán ag tosú le " +"scéim mar \\u201chttps://\\u201d." msgid "redirect" msgstr "athsheol" diff --git a/django/contrib/redirects/locale/ko/LC_MESSAGES/django.mo b/django/contrib/redirects/locale/ko/LC_MESSAGES/django.mo index d0a6f6da36..82d36159c1 100644 Binary files a/django/contrib/redirects/locale/ko/LC_MESSAGES/django.mo and b/django/contrib/redirects/locale/ko/LC_MESSAGES/django.mo differ diff --git a/django/contrib/redirects/locale/ko/LC_MESSAGES/django.po b/django/contrib/redirects/locale/ko/LC_MESSAGES/django.po index 2f2e380551..4209b98700 100644 --- a/django/contrib/redirects/locale/ko/LC_MESSAGES/django.po +++ b/django/contrib/redirects/locale/ko/LC_MESSAGES/django.po @@ -2,6 +2,7 @@ # # Translators: # Jiyoon, Ha , 2016 +# Hyeonho Kang, 2024 # Jannis Leidel , 2011 # Jay Oh , 2020 # JunGu Kang , 2015 @@ -13,9 +14,9 @@ msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2021-01-15 09:00+0100\n" -"PO-Revision-Date: 2021-12-24 18:32+0000\n" -"Last-Translator: 정훈 이\n" -"Language-Team: Korean (http://www.transifex.com/django/django/language/ko/)\n" +"PO-Revision-Date: 2024-10-07 18:32+0000\n" +"Last-Translator: Hyeonho Kang, 2024\n" +"Language-Team: Korean (http://app.transifex.com/django/django/language/ko/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -36,7 +37,7 @@ msgid "" "events/search/”." msgstr "" "도메인 이름을 제외한 절대 경로여야 합니다.\n" -"예제: \"/events/search/\"." +"예시: \"/events/search/\"." msgid "redirect to" msgstr "(으)로 리다이렉트" @@ -45,8 +46,8 @@ msgid "" "This can be either an absolute path (as above) or a full URL starting with a " "scheme such as “https://”." msgstr "" -"(위와 같은) 절대경로 혹은 \"https://\" 같은 식으로 시작하는 완전한 URL 모두 " -"가능합니다." +"이것은 절대 경로 (위와 같이) 이거나 “https://”와 같은 체계로 시작하는 전체 " +"URL 일 수 있습니다." msgid "redirect" msgstr "리다이렉트" diff --git a/django/contrib/sessions/locale/ko/LC_MESSAGES/django.mo b/django/contrib/sessions/locale/ko/LC_MESSAGES/django.mo index 144d87c191..02949db3ad 100644 Binary files a/django/contrib/sessions/locale/ko/LC_MESSAGES/django.mo and b/django/contrib/sessions/locale/ko/LC_MESSAGES/django.mo differ diff --git a/django/contrib/sessions/locale/ko/LC_MESSAGES/django.po b/django/contrib/sessions/locale/ko/LC_MESSAGES/django.po index 74c622c3f9..b49ff0e67e 100644 --- a/django/contrib/sessions/locale/ko/LC_MESSAGES/django.po +++ b/django/contrib/sessions/locale/ko/LC_MESSAGES/django.po @@ -1,16 +1,17 @@ # This file is distributed under the same license as the Django package. # # Translators: +# Hyeonho Kang, 2024 # Jannis Leidel , 2011 -# Chr0m3 , 2015 +# JunGu Kang , 2015 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-01-17 11:07+0100\n" -"PO-Revision-Date: 2017-09-19 16:40+0000\n" -"Last-Translator: Chr0m3 \n" -"Language-Team: Korean (http://www.transifex.com/django/django/language/ko/)\n" +"PO-Revision-Date: 2024-10-07 18:26+0000\n" +"Last-Translator: Hyeonho Kang, 2024\n" +"Language-Team: Korean (http://app.transifex.com/django/django/language/ko/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -27,10 +28,10 @@ msgid "session data" msgstr "세션 날짜" msgid "expire date" -msgstr "유효날짜" +msgstr "만료 날짜" msgid "session" msgstr "세션" msgid "sessions" -msgstr "세션" +msgstr "세션들" diff --git a/django/contrib/sites/locale/ga/LC_MESSAGES/django.mo b/django/contrib/sites/locale/ga/LC_MESSAGES/django.mo index c15ebefd56..b828e40e41 100644 Binary files a/django/contrib/sites/locale/ga/LC_MESSAGES/django.mo and b/django/contrib/sites/locale/ga/LC_MESSAGES/django.mo differ diff --git a/django/contrib/sites/locale/ga/LC_MESSAGES/django.po b/django/contrib/sites/locale/ga/LC_MESSAGES/django.po index 095d08db92..2b1c9bf9ad 100644 --- a/django/contrib/sites/locale/ga/LC_MESSAGES/django.po +++ b/django/contrib/sites/locale/ga/LC_MESSAGES/django.po @@ -1,6 +1,7 @@ # This file is distributed under the same license as the Django package. # # Translators: +# Aindriú Mac Giolla Eoin, 2024 # Jannis Leidel , 2011 # Luke Blaney , 2019 msgid "" @@ -8,9 +9,9 @@ msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-01-17 11:07+0100\n" -"PO-Revision-Date: 2019-06-22 21:46+0000\n" -"Last-Translator: Luke Blaney \n" -"Language-Team: Irish (http://www.transifex.com/django/django/language/ga/)\n" +"PO-Revision-Date: 2024-10-07 18:05+0000\n" +"Last-Translator: Aindriú Mac Giolla Eoin, 2024\n" +"Language-Team: Irish (http://app.transifex.com/django/django/language/ga/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -22,7 +23,7 @@ msgid "Sites" msgstr "Suíomhanna" msgid "The domain name cannot contain any spaces or tabs." -msgstr "" +msgstr "Ní féidir spásanna ná cluaisíní ar bith a bheith san ainm fearainn." msgid "domain name" msgstr "ainm fearainn" diff --git a/django/contrib/sites/locale/ko/LC_MESSAGES/django.mo b/django/contrib/sites/locale/ko/LC_MESSAGES/django.mo index e697676e25..6cd41462af 100644 Binary files a/django/contrib/sites/locale/ko/LC_MESSAGES/django.mo and b/django/contrib/sites/locale/ko/LC_MESSAGES/django.mo differ diff --git a/django/contrib/sites/locale/ko/LC_MESSAGES/django.po b/django/contrib/sites/locale/ko/LC_MESSAGES/django.po index de726db04c..fcf8adcf5b 100644 --- a/django/contrib/sites/locale/ko/LC_MESSAGES/django.po +++ b/django/contrib/sites/locale/ko/LC_MESSAGES/django.po @@ -2,17 +2,18 @@ # # Translators: # Jiyoon, Ha , 2016 +# Hyeonho Kang, 2024 # Jannis Leidel , 2011 # Le Tartuffe , 2014 -# Chr0m3 , 2015 +# JunGu Kang , 2015 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-01-17 11:07+0100\n" -"PO-Revision-Date: 2017-09-19 16:40+0000\n" -"Last-Translator: Jiyoon, Ha \n" -"Language-Team: Korean (http://www.transifex.com/django/django/language/ko/)\n" +"PO-Revision-Date: 2024-10-07 18:05+0000\n" +"Last-Translator: Hyeonho Kang, 2024\n" +"Language-Team: Korean (http://app.transifex.com/django/django/language/ko/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -23,10 +24,10 @@ msgid "Sites" msgstr "사이트" msgid "The domain name cannot contain any spaces or tabs." -msgstr "도메인 이름은 공백이나 탭을 포함 할 수 없습니다." +msgstr "도메인 이름은 공백이나 탭을 포함할 수 없습니다." msgid "domain name" -msgstr "도메인 명" +msgstr "도메인명" msgid "display name" msgstr "표시명" diff --git a/django/core/checks/__init__.py b/django/core/checks/__init__.py index 998ab9dee2..2502450cdf 100644 --- a/django/core/checks/__init__.py +++ b/django/core/checks/__init__.py @@ -16,6 +16,7 @@ from .registry import Tags, register, run_checks, tag_exists # Import these to force registration of checks import django.core.checks.async_checks # NOQA isort:skip import django.core.checks.caches # NOQA isort:skip +import django.core.checks.commands # NOQA isort:skip import django.core.checks.compatibility.django_4_0 # NOQA isort:skip import django.core.checks.database # NOQA isort:skip import django.core.checks.files # NOQA isort:skip diff --git a/django/core/checks/commands.py b/django/core/checks/commands.py new file mode 100644 index 0000000000..eee1e937e8 --- /dev/null +++ b/django/core/checks/commands.py @@ -0,0 +1,28 @@ +from django.core.checks import Error, Tags, register + + +@register(Tags.commands) +def migrate_and_makemigrations_autodetector(**kwargs): + from django.core.management import get_commands, load_command_class + + commands = get_commands() + + make_migrations = load_command_class(commands["makemigrations"], "makemigrations") + migrate = load_command_class(commands["migrate"], "migrate") + + if make_migrations.autodetector is not migrate.autodetector: + return [ + Error( + "The migrate and makemigrations commands must have the same " + "autodetector.", + hint=( + f"makemigrations.Command.autodetector is " + f"{make_migrations.autodetector.__name__}, but " + f"migrate.Command.autodetector is " + f"{migrate.autodetector.__name__}." + ), + id="commands.E001", + ) + ] + + return [] diff --git a/django/core/checks/registry.py b/django/core/checks/registry.py index 146b28f65e..3139fc3ef4 100644 --- a/django/core/checks/registry.py +++ b/django/core/checks/registry.py @@ -12,6 +12,7 @@ class Tags: admin = "admin" async_support = "async_support" caches = "caches" + commands = "commands" compatibility = "compatibility" database = "database" files = "files" diff --git a/django/core/exceptions.py b/django/core/exceptions.py index 31c18ee7e1..7a02aa19df 100644 --- a/django/core/exceptions.py +++ b/django/core/exceptions.py @@ -1,5 +1,5 @@ """ -Global Django exception and warning classes. +Global Django exception classes. """ import operator diff --git a/django/core/management/commands/makemigrations.py b/django/core/management/commands/makemigrations.py index a4e4d520e6..d5d3466201 100644 --- a/django/core/management/commands/makemigrations.py +++ b/django/core/management/commands/makemigrations.py @@ -24,6 +24,7 @@ from django.db.migrations.writer import MigrationWriter class Command(BaseCommand): + autodetector = MigrationAutodetector help = "Creates new migration(s) for apps." def add_arguments(self, parser): @@ -209,7 +210,7 @@ class Command(BaseCommand): log=self.log, ) # Set up autodetector - autodetector = MigrationAutodetector( + autodetector = self.autodetector( loader.project_state(), ProjectState.from_apps(apps), questioner, @@ -461,7 +462,7 @@ class Command(BaseCommand): # If they still want to merge it, then write out an empty # file depending on the migrations needing merging. numbers = [ - MigrationAutodetector.parse_number(migration.name) + self.autodetector.parse_number(migration.name) for migration in merge_migrations ] try: diff --git a/django/core/management/commands/migrate.py b/django/core/management/commands/migrate.py index 5e6b19c095..fa420ee6e3 100644 --- a/django/core/management/commands/migrate.py +++ b/django/core/management/commands/migrate.py @@ -15,6 +15,7 @@ from django.utils.text import Truncator class Command(BaseCommand): + autodetector = MigrationAutodetector help = ( "Updates database schema. Manages both apps with migrations and those without." ) @@ -329,7 +330,7 @@ class Command(BaseCommand): self.stdout.write(" No migrations to apply.") # If there's changes that aren't in migrations yet, tell them # how to fix it. - autodetector = MigrationAutodetector( + autodetector = self.autodetector( executor.loader.project_state(), ProjectState.from_apps(apps), ) diff --git a/django/db/models/constraints.py b/django/db/models/constraints.py index 3859a60e2f..788e2b635b 100644 --- a/django/db/models/constraints.py +++ b/django/db/models/constraints.py @@ -690,11 +690,19 @@ class UniqueConstraint(BaseConstraint): queryset = queryset.exclude(pk=model_class_pk) if not self.condition: if queryset.exists(): - if self.fields: - # When fields are defined, use the unique_error_message() for - # backward compatibility. + if ( + self.fields + and self.violation_error_message + == self.default_violation_error_message + ): + # When fields are defined, use the unique_error_message() as + # a default for backward compatibility. + validation_error_message = instance.unique_error_message( + model, self.fields + ) raise ValidationError( - instance.unique_error_message(model, self.fields), + validation_error_message, + code=validation_error_message.code, ) raise ValidationError( self.get_violation_error_message(), diff --git a/django/db/models/fields/tuple_lookups.py b/django/db/models/fields/tuple_lookups.py index eb2d80b20f..a94582db95 100644 --- a/django/db/models/fields/tuple_lookups.py +++ b/django/db/models/fields/tuple_lookups.py @@ -2,7 +2,7 @@ import itertools from django.core.exceptions import EmptyResultSet from django.db.models import Field -from django.db.models.expressions import Func, Value +from django.db.models.expressions import ColPairs, Func, Value from django.db.models.lookups import ( Exact, GreaterThan, @@ -28,17 +28,32 @@ class Tuple(Func): class TupleLookupMixin: def get_prep_lookup(self): + self.check_rhs_is_tuple_or_list() self.check_rhs_length_equals_lhs_length() return self.rhs + def check_rhs_is_tuple_or_list(self): + if not isinstance(self.rhs, (tuple, list)): + lhs_str = self.get_lhs_str() + raise ValueError( + f"{self.lookup_name!r} lookup of {lhs_str} must be a tuple or a list" + ) + def check_rhs_length_equals_lhs_length(self): len_lhs = len(self.lhs) if len_lhs != len(self.rhs): + lhs_str = self.get_lhs_str() raise ValueError( - f"'{self.lookup_name}' lookup of '{self.lhs.field.name}' field " - f"must have {len_lhs} elements" + f"{self.lookup_name!r} lookup of {lhs_str} must have {len_lhs} elements" ) + def get_lhs_str(self): + if isinstance(self.lhs, ColPairs): + return repr(self.lhs.field.name) + else: + names = ", ".join(repr(f.name) for f in self.lhs) + return f"({names})" + def get_prep_lhs(self): if isinstance(self.lhs, (tuple, list)): return Tuple(*self.lhs) @@ -196,14 +211,25 @@ class TupleLessThanOrEqual(TupleLookupMixin, LessThanOrEqual): class TupleIn(TupleLookupMixin, In): def get_prep_lookup(self): + self.check_rhs_is_tuple_or_list() + self.check_rhs_is_collection_of_tuples_or_lists() self.check_rhs_elements_length_equals_lhs_length() - return super(TupleLookupMixin, self).get_prep_lookup() + return self.rhs # skip checks from mixin + + def check_rhs_is_collection_of_tuples_or_lists(self): + if not all(isinstance(vals, (tuple, list)) for vals in self.rhs): + lhs_str = self.get_lhs_str() + raise ValueError( + f"{self.lookup_name!r} lookup of {lhs_str} " + "must be a collection of tuples or lists" + ) def check_rhs_elements_length_equals_lhs_length(self): len_lhs = len(self.lhs) if not all(len_lhs == len(vals) for vals in self.rhs): + lhs_str = self.get_lhs_str() raise ValueError( - f"'{self.lookup_name}' lookup of '{self.lhs.field.name}' field " + f"{self.lookup_name!r} lookup of {lhs_str} " f"must have {len_lhs} elements each" ) diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index aef3f48f10..b7b93c235a 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -1021,11 +1021,21 @@ class Query(BaseExpression): if alias == old_alias: table_aliases[pos] = new_alias break + + # 3. Rename the direct external aliases and the ones of combined + # queries (union, intersection, difference). self.external_aliases = { # Table is aliased or it's being changed and thus is aliased. change_map.get(alias, alias): (aliased or alias in change_map) for alias, aliased in self.external_aliases.items() } + for combined_query in self.combined_queries: + external_change_map = { + alias: aliased + for alias, aliased in change_map.items() + if alias in combined_query.external_aliases + } + combined_query.change_aliases(external_change_map) def bump_prefix(self, other_query, exclude=None): """ diff --git a/django/http/response.py b/django/http/response.py index abe71718f2..1dbaf46add 100644 --- a/django/http/response.py +++ b/django/http/response.py @@ -21,6 +21,7 @@ from django.http.cookie import SimpleCookie from django.utils import timezone from django.utils.datastructures import CaseInsensitiveMapping from django.utils.encoding import iri_to_uri +from django.utils.functional import cached_property from django.utils.http import content_disposition_header, http_date from django.utils.regex_helper import _lazy_re_compile @@ -408,6 +409,11 @@ class HttpResponse(HttpResponseBase): content = self.make_bytes(value) # Create a list of properly encoded bytestrings to support write(). self._container = [content] + self.__dict__.pop("text", None) + + @cached_property + def text(self): + return self.content.decode(self.charset or "utf-8") def __iter__(self): return iter(self._container) @@ -460,6 +466,12 @@ class StreamingHttpResponse(HttpResponseBase): "`streaming_content` instead." % self.__class__.__name__ ) + @property + def text(self): + raise AttributeError( + "This %s instance has no `text` attribute." % self.__class__.__name__ + ) + @property def streaming_content(self): if self.is_async: diff --git a/django/template/base.py b/django/template/base.py index ee2e145c04..b974495c9c 100644 --- a/django/template/base.py +++ b/django/template/base.py @@ -533,9 +533,13 @@ class Parser: def extend_nodelist(self, nodelist, node, token): # Check that non-text nodes don't appear before an extends tag. if node.must_be_first and nodelist.contains_nontext: + if self.origin.template_name: + origin = repr(self.origin.template_name) + else: + origin = "the template" raise self.error( token, - "%r must be the first tag in the template." % node, + "{%% %s %%} must be the first tag in %s." % (token.contents, origin), ) if not isinstance(node, TextNode): nodelist.contains_nontext = True diff --git a/django/test/client.py b/django/test/client.py index a755aae05c..85d91b0c44 100644 --- a/django/test/client.py +++ b/django/test/client.py @@ -947,9 +947,7 @@ class ClientMixin: 'Content-Type header is "%s", not "application/json"' % response.get("Content-Type") ) - response._json = json.loads( - response.content.decode(response.charset), **extra - ) + response._json = json.loads(response.text, **extra) return response._json def _follow_redirect( diff --git a/django/urls/base.py b/django/urls/base.py index 753779c75b..bb40ba2224 100644 --- a/django/urls/base.py +++ b/django/urls/base.py @@ -127,8 +127,9 @@ def clear_script_prefix(): def set_urlconf(urlconf_name): """ - Set the URLconf for the current thread (overriding the default one in - settings). If urlconf_name is None, revert back to the default. + Set the URLconf for the current thread or asyncio task (overriding the + default one in settings). If urlconf_name is None, revert back to the + default. """ if urlconf_name: _urlconfs.value = urlconf_name @@ -139,8 +140,8 @@ def set_urlconf(urlconf_name): def get_urlconf(default=None): """ - Return the root URLconf to use for the current thread if it has been - changed from the default one. + Return the root URLconf to use for the current thread or asyncio task if it + has been changed from the default one. """ return getattr(_urlconfs, "value", default) diff --git a/django/views/generic/dates.py b/django/views/generic/dates.py index d2b776c122..12ec4104cd 100644 --- a/django/views/generic/dates.py +++ b/django/views/generic/dates.py @@ -300,7 +300,11 @@ class DateMixin: class BaseDateListView(MultipleObjectMixin, DateMixin, View): - """Abstract base class for date-based views displaying a list of objects.""" + """ + Base class for date-based views displaying a list of objects. + + This requires subclassing to provide a response mixin. + """ allow_empty = False date_list_period = "year" @@ -388,7 +392,9 @@ class BaseDateListView(MultipleObjectMixin, DateMixin, View): class BaseArchiveIndexView(BaseDateListView): """ - Base class for archives of date-based items. Requires a response mixin. + Base view for archives of date-based items. + + This requires subclassing to provide a response mixin. """ context_object_name = "latest" @@ -411,7 +417,11 @@ class ArchiveIndexView(MultipleObjectTemplateResponseMixin, BaseArchiveIndexView class BaseYearArchiveView(YearMixin, BaseDateListView): - """List of objects published in a given year.""" + """ + Base view for a list of objects published in a given year. + + This requires subclassing to provide a response mixin. + """ date_list_period = "month" make_object_list = False @@ -463,7 +473,11 @@ class YearArchiveView(MultipleObjectTemplateResponseMixin, BaseYearArchiveView): class BaseMonthArchiveView(YearMixin, MonthMixin, BaseDateListView): - """List of objects published in a given month.""" + """ + Base view for a list of objects published in a given month. + + This requires subclassing to provide a response mixin. + """ date_list_period = "day" @@ -505,7 +519,11 @@ class MonthArchiveView(MultipleObjectTemplateResponseMixin, BaseMonthArchiveView class BaseWeekArchiveView(YearMixin, WeekMixin, BaseDateListView): - """List of objects published in a given week.""" + """ + Base view for a list of objects published in a given week. + + This requires subclassing to provide a response mixin. + """ def get_dated_items(self): """Return (date_list, items, extra_context) for this request.""" @@ -563,7 +581,11 @@ class WeekArchiveView(MultipleObjectTemplateResponseMixin, BaseWeekArchiveView): class BaseDayArchiveView(YearMixin, MonthMixin, DayMixin, BaseDateListView): - """List of objects published on a given day.""" + """ + Base view for a list of objects published on a given day. + + This requires subclassing to provide a response mixin. + """ def get_dated_items(self): """Return (date_list, items, extra_context) for this request.""" @@ -610,7 +632,11 @@ class DayArchiveView(MultipleObjectTemplateResponseMixin, BaseDayArchiveView): class BaseTodayArchiveView(BaseDayArchiveView): - """List of objects published today.""" + """ + Base view for a list of objects published today. + + This requires subclassing to provide a response mixin. + """ def get_dated_items(self): """Return (date_list, items, extra_context) for this request.""" @@ -625,8 +651,10 @@ class TodayArchiveView(MultipleObjectTemplateResponseMixin, BaseTodayArchiveView class BaseDateDetailView(YearMixin, MonthMixin, DayMixin, DateMixin, BaseDetailView): """ - Detail view of a single object on a single date; this differs from the + Base detail view for a single object on a single date; this differs from the standard DetailView by accepting a year/month/day in the URL. + + This requires subclassing to provide a response mixin. """ def get_object(self, queryset=None): diff --git a/django/views/generic/detail.py b/django/views/generic/detail.py index e4428c8036..a5f604bf1a 100644 --- a/django/views/generic/detail.py +++ b/django/views/generic/detail.py @@ -102,7 +102,11 @@ class SingleObjectMixin(ContextMixin): class BaseDetailView(SingleObjectMixin, View): - """A base view for displaying a single object.""" + """ + Base view for displaying a single object. + + This requires subclassing to provide a response mixin. + """ def get(self, request, *args, **kwargs): self.object = self.get_object() diff --git a/django/views/generic/edit.py b/django/views/generic/edit.py index 97934f58cb..ebd071cf00 100644 --- a/django/views/generic/edit.py +++ b/django/views/generic/edit.py @@ -170,7 +170,7 @@ class BaseCreateView(ModelFormMixin, ProcessFormView): """ Base view for creating a new object instance. - Using this base class requires subclassing to provide a response mixin. + This requires subclassing to provide a response mixin. """ def get(self, request, *args, **kwargs): @@ -194,7 +194,7 @@ class BaseUpdateView(ModelFormMixin, ProcessFormView): """ Base view for updating an existing object. - Using this base class requires subclassing to provide a response mixin. + This requires subclassing to provide a response mixin. """ def get(self, request, *args, **kwargs): @@ -242,7 +242,7 @@ class BaseDeleteView(DeletionMixin, FormMixin, BaseDetailView): """ Base view for deleting an object. - Using this base class requires subclassing to provide a response mixin. + This requires subclassing to provide a response mixin. """ form_class = Form diff --git a/django/views/generic/list.py b/django/views/generic/list.py index 830a8df630..8ed92920c4 100644 --- a/django/views/generic/list.py +++ b/django/views/generic/list.py @@ -148,7 +148,11 @@ class MultipleObjectMixin(ContextMixin): class BaseListView(MultipleObjectMixin, View): - """A base view for displaying a list of objects.""" + """ + Base view for displaying a list of objects. + + This requires subclassing to provide a response mixin. + """ def get(self, request, *args, **kwargs): self.object_list = self.get_queryset() diff --git a/docs/conf.py b/docs/conf.py index b72b1afcf5..9289e821fa 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -13,6 +13,8 @@ import functools import sys from os.path import abspath, dirname, join +from sphinx import version_info as sphinx_version + # Workaround for sphinx-build recursion limit overflow: # pickle.dump(doctree, f, pickle.HIGHEST_PROTOCOL) # RuntimeError: maximum recursion depth exceeded while pickling an object @@ -138,13 +140,15 @@ django_next_version = "5.2" extlinks = { "bpo": ("https://bugs.python.org/issue?@action=redirect&bpo=%s", "bpo-%s"), "commit": ("https://github.com/django/django/commit/%s", "%s"), - "cve": ("https://nvd.nist.gov/vuln/detail/CVE-%s", "CVE-%s"), "pypi": ("https://pypi.org/project/%s/", "%s"), # A file or directory. GitHub redirects from blob to tree if needed. "source": ("https://github.com/django/django/blob/main/%s", "%s"), "ticket": ("https://code.djangoproject.com/ticket/%s", "#%s"), } +if sphinx_version < (8, 1): + extlinks["cve"] = ("https://www.cve.org/CVERecord?id=CVE-%s", "CVE-%s") + # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # language = None diff --git a/docs/faq/install.txt b/docs/faq/install.txt index ddb84d6d9c..4623e288fb 100644 --- a/docs/faq/install.txt +++ b/docs/faq/install.txt @@ -52,7 +52,7 @@ Django version Python versions ============== =============== 4.2 3.8, 3.9, 3.10, 3.11, 3.12 (added in 4.2.8) 5.0 3.10, 3.11, 3.12 -5.1 3.10, 3.11, 3.12 +5.1 3.10, 3.11, 3.12, 3.13 (added in 5.1.3) 5.2 3.10, 3.11, 3.12, 3.13 ============== =============== diff --git a/docs/howto/windows.txt b/docs/howto/windows.txt index 83aa8d0655..235b18a24f 100644 --- a/docs/howto/windows.txt +++ b/docs/howto/windows.txt @@ -2,7 +2,7 @@ How to install Django on Windows ================================ -This document will guide you through installing Python 3.12 and Django on +This document will guide you through installing Python 3.13 and Django on Windows. It also provides instructions for setting up a virtual environment, which makes it easier to work on Python projects. This is meant as a beginner's guide for users working on Django projects and does not reflect how Django @@ -18,7 +18,7 @@ Install Python ============== Django is a Python web framework, thus requiring Python to be installed on your -machine. At the time of writing, Python 3.12 is the latest version. +machine. At the time of writing, Python 3.13 is the latest version. To install Python on your machine go to https://www.python.org/downloads/. The website should offer you a download button for the latest Python version. diff --git a/docs/internals/contributing/writing-code/submitting-patches.txt b/docs/internals/contributing/writing-code/submitting-patches.txt index cac6848d04..799292e3fd 100644 --- a/docs/internals/contributing/writing-code/submitting-patches.txt +++ b/docs/internals/contributing/writing-code/submitting-patches.txt @@ -114,7 +114,7 @@ requirements: feature, the change should also contain documentation. When you think your work is ready to be reviewed, send :doc:`a GitHub pull -request `. +request `. If you can't send a pull request for some reason, you can also use patches in Trac. When using this style, follow these guidelines. @@ -140,20 +140,63 @@ Regardless of the way you submit your work, follow these steps. .. _ticket tracker: https://code.djangoproject.com/ .. _Development dashboard: https://dashboard.djangoproject.com/ -Non-trivial contributions -========================= +Contributions which require community feedback +============================================== -A "non-trivial" contribution is one that is more than a small bug fix. It's a -change that introduces new Django functionality and makes some sort of design -decision. +A wider community discussion is required when a patch introduces new Django +functionality and makes some sort of design decision. This is especially +important if the approach involves a :ref:`deprecation ` +or introduces breaking changes. -If you provide a non-trivial change, include evidence that alternatives have -been discussed on the `Django Forum`_ or |django-developers| list. +The following are different approaches for gaining feedback from the community. -If you're not sure whether your contribution should be considered non-trivial, -ask on the ticket for opinions. +The Django Forum or django-developers mailing list +-------------------------------------------------- + +You can propose a change on the `Django Forum`_ or |django-developers| mailing +list. You should explain the need for the change, go into details of the +approach and discuss alternatives. + +Please include a link to such discussions in your contributions. + +Third party package +------------------- + +Django does not accept experimental features. All features must follow our +:ref:`deprecation policy `. Hence, it can +take months or years for Django to iterate on an API design. + +If you need user feedback on a public interface, it is better to create a +third-party package first. You can iterate on the public API much faster, while +also validating the need for the feature. + +Once this package becomes stable and there are clear benefits of incorporating +aspects into Django core, starting a discussion on the `Django Forum`_ or +|django-developers| mailing list would be the next step. + +Django Enhancement Proposal (DEP) +--------------------------------- + +Similar to Python’s PEPs, Django has `Django Enhancement Proposals`_ or DEPs. A +DEP is a design document which provides information to the Django community, or +describes a new feature or process for Django. They provide concise technical +specifications of features, along with rationales. DEPs are also the primary +mechanism for proposing and collecting community input on major new features. + +Before considering writing a DEP, it is recommended to first open a discussion +on the `Django Forum`_ or |django-developers| mailing list. This allows the +community to provide feedback and helps refine the proposal. Once the DEP is +ready the :ref:`Steering Council ` votes on whether to accept +it. + +Some examples of DEPs that have been approved and fully implemented: + +* `DEP 181: ORM Expressions `_ +* `DEP 182: Multiple Template Engines `_ +* `DEP 201: Simplified routing syntax `_ .. _Django Forum: https://forum.djangoproject.com/ +.. _Django Enhancement Proposals: https://github.com/django/deps .. _deprecating-a-feature: diff --git a/docs/internals/security.txt b/docs/internals/security.txt index 55300b01e1..6aac9a6b66 100644 --- a/docs/internals/security.txt +++ b/docs/internals/security.txt @@ -38,6 +38,41 @@ action to be taken, you may receive further followup emails. .. _our public Trac instance: https://code.djangoproject.com/query +.. _security-report-evaluation: + +How does Django evaluate a report +================================= + +These are criteria used by the security team when evaluating whether a report +requires a security release: + +* The vulnerability is within a :ref:`supported version ` of + Django. + +* The vulnerability applies to a production-grade Django application. This means + the following do not require a security release: + + * Exploits that only affect local development, for example when using + :djadmin:`runserver`. + * Exploits which fail to follow security best practices, such as failure to + sanitize user input. For other examples, see our :ref:`security + documentation `. + * Exploits in AI generated code that do not adhere to security best practices. + +The security team may conclude that the source of the vulnerability is within +the Python standard library, in which case the reporter will be asked to report +the vulnerability to the Python core team. For further details see the `Python +security guidelines `_. + +On occasion, a security release may be issued to help resolve a security +vulnerability within a popular third-party package. These reports should come +from the package maintainers. + +If you are unsure whether your finding meets these criteria, please still report +it :ref:`privately by emailing security@djangoproject.com +`. The security team will review your report and +recommend the correct course of action. + .. _security-support: Supported versions diff --git a/docs/intro/overview.txt b/docs/intro/overview.txt index 8314b3d351..0c41446d01 100644 --- a/docs/intro/overview.txt +++ b/docs/intro/overview.txt @@ -26,7 +26,7 @@ representing your models -- so far, it's been solving many years' worth of database-schema problems. Here's a quick example: .. code-block:: python - :caption: ``mysite/news/models.py`` + :caption: ``news/models.py`` from django.db import models @@ -151,7 +151,7 @@ a website that lets authenticated users add, change and delete objects. The only step required is to register your model in the admin site: .. code-block:: python - :caption: ``mysite/news/models.py`` + :caption: ``news/models.py`` from django.db import models @@ -163,7 +163,7 @@ only step required is to register your model in the admin site: reporter = models.ForeignKey(Reporter, on_delete=models.CASCADE) .. code-block:: python - :caption: ``mysite/news/admin.py`` + :caption: ``news/admin.py`` from django.contrib import admin @@ -195,7 +195,7 @@ Here's what a URLconf might look like for the ``Reporter``/``Article`` example above: .. code-block:: python - :caption: ``mysite/news/urls.py`` + :caption: ``news/urls.py`` from django.urls import path @@ -235,7 +235,7 @@ and renders the template with the retrieved data. Here's an example view for ``year_archive`` from above: .. code-block:: python - :caption: ``mysite/news/views.py`` + :caption: ``news/views.py`` from django.shortcuts import render @@ -265,7 +265,7 @@ Let's say the ``news/year_archive.html`` template was found. Here's what that might look like: .. code-block:: html+django - :caption: ``mysite/news/templates/news/year_archive.html`` + :caption: ``news/templates/news/year_archive.html`` {% extends "base.html" %} @@ -306,7 +306,7 @@ Here's what the "base.html" template, including the use of :doc:`static files `, might look like: .. code-block:: html+django - :caption: ``mysite/templates/base.html`` + :caption: ``templates/base.html`` {% load static %} diff --git a/docs/intro/reusable-apps.txt b/docs/intro/reusable-apps.txt index 98f21c9d91..a9c0768e3b 100644 --- a/docs/intro/reusable-apps.txt +++ b/docs/intro/reusable-apps.txt @@ -57,7 +57,7 @@ After the previous tutorials, our project should look like this: .. code-block:: text - mysite/ + djangotutorial/ manage.py mysite/ __init__.py @@ -90,12 +90,12 @@ After the previous tutorials, our project should look like this: admin/ base_site.html -You created ``mysite/templates`` in :doc:`Tutorial 7 `, -and ``polls/templates`` in :doc:`Tutorial 3 `. Now perhaps -it is clearer why we chose to have separate template directories for the -project and application: everything that is part of the polls application is in -``polls``. It makes the application self-contained and easier to drop into a -new project. +You created ``djangotutorial/templates`` in :doc:`Tutorial 7 +`, and ``polls/templates`` in +:doc:`Tutorial 3 `. Now perhaps it is clearer why we chose +to have separate template directories for the project and application: +everything that is part of the polls application is in ``polls``. It makes the +application self-contained and easier to drop into a new project. The ``polls`` directory could now be copied into a new Django project and immediately reused. It's not quite ready to be published though. For that, we @@ -237,6 +237,7 @@ this. For a small app like polls, this process isn't too difficult. "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: Dynamic Content", ] diff --git a/docs/intro/tutorial01.txt b/docs/intro/tutorial01.txt index 3f89220949..c5ac5a1107 100644 --- a/docs/intro/tutorial01.txt +++ b/docs/intro/tutorial01.txt @@ -48,14 +48,21 @@ including database configuration, Django-specific options and application-specific settings. From the command line, ``cd`` into a directory where you'd like to store your -code, then run the following command: +code and create a new directory named ``djangotutorial``. (This directory name +doesn't matter to Django; you can rename it to anything you like.) .. console:: - $ django-admin startproject mysite + $ mkdir djangotutorial -This will create a ``mysite`` directory in your current directory. If it didn't -work, see :ref:`troubleshooting-django-admin`. +Then, run the following command to bootstrap a new Django project: + +.. console:: + + $ django-admin startproject mysite djangotutorial + +This will create a project called ``mysite`` inside the ``djangotutorial`` +directory. If it didn't work, see :ref:`troubleshooting-django-admin`. .. note:: @@ -68,7 +75,7 @@ Let's look at what :djadmin:`startproject` created: .. code-block:: text - mysite/ + djangotutorial/ manage.py mysite/ __init__.py @@ -79,14 +86,11 @@ Let's look at what :djadmin:`startproject` created: These files are: -* The outer :file:`mysite/` root directory is a container for your project. Its - name doesn't matter to Django; you can rename it to anything you like. - * :file:`manage.py`: A command-line utility that lets you interact with this Django project in various ways. You can read all the details about :file:`manage.py` in :doc:`/ref/django-admin`. -* The inner :file:`mysite/` directory is the actual Python package for your +* :file:`mysite/`: A directory that is the actual Python package for your project. Its name is the Python package name you'll need to use to import anything inside it (e.g. ``mysite.urls``). @@ -111,8 +115,8 @@ These files are: The development server ====================== -Let's verify your Django project works. Change into the outer :file:`mysite` directory, if -you haven't already, and run the following commands: +Let's verify your Django project works. Change into the :file:`djangotutorial` +directory, if you haven't already, and run the following commands: .. console:: @@ -182,10 +186,8 @@ rather than creating directories. configuration and apps for a particular website. A project can contain multiple apps. An app can be in multiple projects. -Your apps can live anywhere on your :ref:`Python path `. In -this tutorial, we'll create our poll app in the same directory as your -:file:`manage.py` file so that it can be imported as its own top-level module, -rather than a submodule of ``mysite``. +Your apps can live anywhere in your :ref:`Python path `. In +this tutorial, we'll create our poll app inside the ``djangotutorial`` folder. To create your app, make sure you're in the same directory as :file:`manage.py` and type this command: @@ -275,6 +277,8 @@ include the URLconf defined in ``polls.urls``. To do this, add an import for path("admin/", admin.site.urls), ] +The :func:`~django.urls.path` function expects at least two arguments: +``route`` and ``view``. The :func:`~django.urls.include` function allows referencing other URLconfs. Whenever Django encounters :func:`~django.urls.include`, it chops off whatever part of the URL matched up to that point and sends the remaining string to the @@ -289,7 +293,8 @@ app will still work. .. admonition:: When to use :func:`~django.urls.include()` You should always use ``include()`` when you include other URL patterns. - ``admin.site.urls`` is the only exception to this. + The only exception is ``admin.site.urls``, which is a pre-built URLconf + provided by Django for the default admin site. You have now wired an ``index`` view into the URLconf. Verify it's working with the following command: @@ -307,45 +312,6 @@ text "*Hello, world. You're at the polls index.*", which you defined in the If you get an error page here, check that you're going to http://localhost:8000/polls/ and not http://localhost:8000/. -The :func:`~django.urls.path` function is passed four arguments, two required: -``route`` and ``view``, and two optional: ``kwargs``, and ``name``. -At this point, it's worth reviewing what these arguments are for. - -:func:`~django.urls.path` argument: ``route`` ---------------------------------------------- - -``route`` is a string that contains a URL pattern. When processing a request, -Django starts at the first pattern in ``urlpatterns`` and makes its way down -the list, comparing the requested URL against each pattern until it finds one -that matches. - -Patterns don't search GET and POST parameters, or the domain name. For example, -in a request to ``https://www.example.com/myapp/``, the URLconf will look for -``myapp/``. In a request to ``https://www.example.com/myapp/?page=3``, the -URLconf will also look for ``myapp/``. - -:func:`~django.urls.path` argument: ``view`` --------------------------------------------- - -When Django finds a matching pattern, it calls the specified view function with -an :class:`~django.http.HttpRequest` object as the first argument and any -"captured" values from the route as keyword arguments. We'll give an example -of this in a bit. - -:func:`~django.urls.path` argument: ``kwargs`` ----------------------------------------------- - -Arbitrary keyword arguments can be passed in a dictionary to the target view. We -aren't going to use this feature of Django in the tutorial. - -:func:`~django.urls.path` argument: ``name`` --------------------------------------------- - -Naming your URL lets you refer to it unambiguously from elsewhere in Django, -especially from within templates. This powerful feature allows you to make -global changes to the URL patterns of your project while only touching a single -file. - When you're comfortable with the basic request and response flow, read :doc:`part 2 of this tutorial ` to start working with the database. diff --git a/docs/intro/tutorial05.txt b/docs/intro/tutorial05.txt index 5f501ce92f..28a634b8c3 100644 --- a/docs/intro/tutorial05.txt +++ b/docs/intro/tutorial05.txt @@ -216,7 +216,7 @@ and you'll see something like: FAIL: test_was_published_recently_with_future_question (polls.tests.QuestionModelTests) ---------------------------------------------------------------------- Traceback (most recent call last): - File "/path/to/mysite/polls/tests.py", line 16, in test_was_published_recently_with_future_question + File "/path/to/djangotutorial/polls/tests.py", line 16, in test_was_published_recently_with_future_question self.assertIs(future_question.was_published_recently(), False) AssertionError: True is not False diff --git a/docs/intro/tutorial07.txt b/docs/intro/tutorial07.txt index e0c87be898..a8d5e61b81 100644 --- a/docs/intro/tutorial07.txt +++ b/docs/intro/tutorial07.txt @@ -306,10 +306,10 @@ powered by Django itself, and its interfaces use Django's own template system. Customizing your *project's* templates -------------------------------------- -Create a ``templates`` directory in your project directory (the one that -contains ``manage.py``). Templates can live anywhere on your filesystem that -Django can access. (Django runs as whatever user your server runs.) However, -keeping your templates within the project is a good convention to follow. +Create a ``templates`` directory in your ``djangotutorial`` directory. +Templates can live anywhere on your filesystem that Django can access. (Django +runs as whatever user your server runs.) However, keeping your templates within +the project is a good convention to follow. Open your settings file (:file:`mysite/settings.py`, remember) and add a :setting:`DIRS ` option in the :setting:`TEMPLATES` setting: @@ -324,7 +324,6 @@ Open your settings file (:file:`mysite/settings.py`, remember) and add a "APP_DIRS": True, "OPTIONS": { "context_processors": [ - "django.template.context_processors.debug", "django.template.context_processors.request", "django.contrib.auth.context_processors.auth", "django.contrib.messages.context_processors.messages", diff --git a/docs/ref/checks.txt b/docs/ref/checks.txt index d78a6f76b2..2308a854c7 100644 --- a/docs/ref/checks.txt +++ b/docs/ref/checks.txt @@ -77,6 +77,7 @@ Django's system checks are organized using the following tags: * ``async_support``: Checks asynchronous-related configuration. * ``caches``: Checks cache related configuration. * ``compatibility``: Flags potential problems with version upgrades. +* ``commands``: Checks custom management commands related configuration. * ``database``: Checks database-related configuration issues. Database checks are not run by default because they do more than static code analysis as regular checks do. They are only run by the :djadmin:`migrate` command or if @@ -428,6 +429,14 @@ Models * **models.W047**: ```` does not support unique constraints with nulls distinct. +Management Commands +------------------- + +The following checks verify custom management commands are correctly configured: + +* **commands.E001**: The ``migrate`` and ``makemigrations`` commands must have + the same ``autodetector``. + Security -------- diff --git a/docs/ref/contrib/auth.txt b/docs/ref/contrib/auth.txt index d5fc724b54..c8699a2913 100644 --- a/docs/ref/contrib/auth.txt +++ b/docs/ref/contrib/auth.txt @@ -197,13 +197,23 @@ Methods been called for this user. .. method:: get_user_permissions(obj=None) + .. method:: aget_user_permissions(obj=None) + + *Asynchronous version*: ``aget_user_permissions()`` Returns a set of permission strings that the user has directly. If ``obj`` is passed in, only returns the user permissions for this specific object. + .. versionchanged:: 5.2 + + ``aget_user_permissions()`` method was added. + .. method:: get_group_permissions(obj=None) + .. method:: aget_group_permissions(obj=None) + + *Asynchronous version*: ``aget_group_permissions()`` Returns a set of permission strings that the user has, through their groups. @@ -211,7 +221,14 @@ Methods If ``obj`` is passed in, only returns the group permissions for this specific object. + .. versionchanged:: 5.2 + + ``aget_group_permissions()`` method was added. + .. method:: get_all_permissions(obj=None) + .. method:: aget_all_permissions(obj=None) + + *Asynchronous version*: ``aget_all_permissions()`` Returns a set of permission strings that the user has, both through group and user permissions. @@ -219,7 +236,14 @@ Methods If ``obj`` is passed in, only returns the permissions for this specific object. + .. versionchanged:: 5.2 + + ``aget_all_permissions()`` method was added. + .. method:: has_perm(perm, obj=None) + .. method:: ahas_perm(perm, obj=None) + + *Asynchronous version*: ``ahas_perm()`` Returns ``True`` if the user has the specified permission, where perm is in the format ``"."``. (see @@ -230,7 +254,14 @@ Methods If ``obj`` is passed in, this method won't check for a permission for the model, but for this specific object. + .. versionchanged:: 5.2 + + ``ahas_perm()`` method was added. + .. method:: has_perms(perm_list, obj=None) + .. method:: ahas_perms(perm_list, obj=None) + + *Asynchronous version*: ``ahas_perms()`` Returns ``True`` if the user has each of the specified permissions, where each perm is in the format @@ -241,13 +272,24 @@ Methods If ``obj`` is passed in, this method won't check for permissions for the model, but for the specific object. + .. versionchanged:: 5.2 + + ``ahas_perms()`` method was added. + .. method:: has_module_perms(package_name) + .. method:: ahas_module_perms(package_name) + + *Asynchronous version*: ``ahas_module_perms()`` Returns ``True`` if the user has any permissions in the given package (the Django app label). If the user is inactive, this method will always return ``False``. For an active superuser, this method will always return ``True``. + .. versionchanged:: 5.2 + + ``ahas_module_perms()`` method was added. + .. method:: email_user(subject, message, from_email=None, **kwargs) Sends an email to the user. If ``from_email`` is ``None``, Django uses @@ -264,6 +306,9 @@ Manager methods by :class:`~django.contrib.auth.models.BaseUserManager`): .. method:: create_user(username, email=None, password=None, **extra_fields) + .. method:: acreate_user(username, email=None, password=None, **extra_fields) + + *Asynchronous version*: ``acreate_user()`` Creates, saves and returns a :class:`~django.contrib.auth.models.User`. @@ -285,11 +330,22 @@ Manager methods See :ref:`Creating users ` for example usage. + .. versionchanged:: 5.2 + + ``acreate_user()`` method was added. + .. method:: create_superuser(username, email=None, password=None, **extra_fields) + .. method:: acreate_superuser(username, email=None, password=None, **extra_fields) + + *Asynchronous version*: ``acreate_superuser()`` Same as :meth:`create_user`, but sets :attr:`~models.User.is_staff` and :attr:`~models.User.is_superuser` to ``True``. + .. versionchanged:: 5.2 + + ``acreate_superuser()`` method was added. + .. method:: with_perm(perm, is_active=True, include_superusers=True, backend=None, obj=None) Returns users that have the given permission ``perm`` either in the @@ -499,23 +555,51 @@ The following backends are available in :mod:`django.contrib.auth.backends`: methods. By default, it will reject any user and provide no permissions. .. method:: get_user_permissions(user_obj, obj=None) + .. method:: aget_user_permissions(user_obj, obj=None) + + *Asynchronous version*: ``aget_user_permissions()`` Returns an empty set. + .. versionchanged:: 5.2 + + ``aget_user_permissions()`` function was added. + .. method:: get_group_permissions(user_obj, obj=None) + .. method:: aget_group_permissions(user_obj, obj=None) + + *Asynchronous version*: ``aget_group_permissions()`` Returns an empty set. + .. versionchanged:: 5.2 + + ``aget_group_permissions()`` function was added. + .. method:: get_all_permissions(user_obj, obj=None) + .. method:: aget_all_permissions(user_obj, obj=None) + + *Asynchronous version*: ``aget_all_permissions()`` Uses :meth:`get_user_permissions` and :meth:`get_group_permissions` to get the set of permission strings the ``user_obj`` has. + .. versionchanged:: 5.2 + + ``aget_all_permissions()`` function was added. + .. method:: has_perm(user_obj, perm, obj=None) + .. method:: ahas_perm(user_obj, perm, obj=None) + + *Asynchronous version*: ``ahas_perm()`` Uses :meth:`get_all_permissions` to check if ``user_obj`` has the permission string ``perm``. + .. versionchanged:: 5.2 + + ``ahas_perm()`` function was added. + .. class:: ModelBackend This is the default authentication backend used by Django. It @@ -539,6 +623,9 @@ The following backends are available in :mod:`django.contrib.auth.backends`: unlike others methods it returns an empty queryset if ``obj is not None``. .. method:: authenticate(request, username=None, password=None, **kwargs) + .. method:: aauthenticate(request, username=None, password=None, **kwargs) + + *Asynchronous version*: ``aauthenticate()`` Tries to authenticate ``username`` with ``password`` by calling :meth:`User.check_password @@ -552,38 +639,77 @@ The following backends are available in :mod:`django.contrib.auth.backends`: if it wasn't provided to :func:`~django.contrib.auth.authenticate` (which passes it on to the backend). + .. versionchanged:: 5.2 + + ``aauthenticate()`` function was added. + .. method:: get_user_permissions(user_obj, obj=None) + .. method:: aget_user_permissions(user_obj, obj=None) + + *Asynchronous version*: ``aget_user_permissions()`` Returns the set of permission strings the ``user_obj`` has from their own user permissions. Returns an empty set if :attr:`~django.contrib.auth.models.AbstractBaseUser.is_anonymous` or :attr:`~django.contrib.auth.models.CustomUser.is_active` is ``False``. + .. versionchanged:: 5.2 + + ``aget_user_permissions()`` function was added. + .. method:: get_group_permissions(user_obj, obj=None) + .. method:: aget_group_permissions(user_obj, obj=None) + + *Asynchronous version*: ``aget_group_permissions()`` Returns the set of permission strings the ``user_obj`` has from the permissions of the groups they belong. Returns an empty set if :attr:`~django.contrib.auth.models.AbstractBaseUser.is_anonymous` or :attr:`~django.contrib.auth.models.CustomUser.is_active` is ``False``. + .. versionchanged:: 5.2 + + ``aget_group_permissions()`` function was added. + .. method:: get_all_permissions(user_obj, obj=None) + .. method:: aget_all_permissions(user_obj, obj=None) + + *Asynchronous version*: ``aget_all_permissions()`` Returns the set of permission strings the ``user_obj`` has, including both user permissions and group permissions. Returns an empty set if :attr:`~django.contrib.auth.models.AbstractBaseUser.is_anonymous` or :attr:`~django.contrib.auth.models.CustomUser.is_active` is ``False``. + + .. versionchanged:: 5.2 + + ``aget_all_permissions()`` function was added. .. method:: has_perm(user_obj, perm, obj=None) + .. method:: ahas_perm(user_obj, perm, obj=None) + + *Asynchronous version*: ``ahas_perm()`` Uses :meth:`get_all_permissions` to check if ``user_obj`` has the permission string ``perm``. Returns ``False`` if the user is not :attr:`~django.contrib.auth.models.CustomUser.is_active`. + .. versionchanged:: 5.2 + + ``ahas_perm()`` function was added. + .. method:: has_module_perms(user_obj, app_label) + .. method:: ahas_module_perms(user_obj, app_label) + + *Asynchronous version*: ``ahas_module_perms()`` Returns whether the ``user_obj`` has any permissions on the app ``app_label``. + .. versionchanged:: 5.2 + + ``ahas_module_perms()`` function was added. + .. method:: user_can_authenticate() Returns whether the user is allowed to authenticate. To match the @@ -637,6 +763,9 @@ The following backends are available in :mod:`django.contrib.auth.backends`: created if not already in the database Defaults to ``True``. .. method:: authenticate(request, remote_user) + .. method:: aauthenticate(request, remote_user) + + *Asynchronous version*: ``aauthenticate()`` The username passed as ``remote_user`` is considered trusted. This method returns the user object with the given username, creating a new @@ -651,6 +780,10 @@ The following backends are available in :mod:`django.contrib.auth.backends`: if it wasn't provided to :func:`~django.contrib.auth.authenticate` (which passes it on to the backend). + .. versionchanged:: 5.2 + + ``aauthenticate()`` function was added. + .. method:: clean_username(username) Performs any cleaning on the ``username`` (e.g. stripping LDAP DN @@ -658,12 +791,17 @@ The following backends are available in :mod:`django.contrib.auth.backends`: the cleaned username. .. method:: configure_user(request, user, created=True) + .. method:: aconfigure_user(request, user, created=True) + + *Asynchronous version*: ``aconfigure_user()`` Configures the user on each authentication attempt. This method is called immediately after fetching or creating the user being authenticated, and can be used to perform custom setup actions, such as setting the user's groups based on attributes in an LDAP directory. - Returns the user object. + Returns the user object. When fetching or creating an user is called + from a synchronous context, ``configure_user`` is called, + ``aconfigure_user`` is called from async contexts. The setup can be performed either once when the user is created (``created`` is ``True``) or on existing users (``created`` is @@ -674,6 +812,10 @@ The following backends are available in :mod:`django.contrib.auth.backends`: if it wasn't provided to :func:`~django.contrib.auth.authenticate` (which passes it on to the backend). + .. versionchanged:: 5.2 + + ``aconfigure_user()`` function was added. + .. method:: user_can_authenticate() Returns whether the user is allowed to authenticate. This method diff --git a/docs/ref/middleware.txt b/docs/ref/middleware.txt index 4aab186c8b..b5534ce8f6 100644 --- a/docs/ref/middleware.txt +++ b/docs/ref/middleware.txt @@ -34,6 +34,12 @@ defines. See the :doc:`cache documentation `. .. class:: CommonMiddleware + .. attribute:: response_redirect_class + + Defaults to :class:`~django.http.HttpResponsePermanentRedirect`. Subclass + ``CommonMiddleware`` and override the attribute to customize the redirects + issued by the middleware. + Adds a few conveniences for perfectionists: * Forbids access to user agents in the :setting:`DISALLOWED_USER_AGENTS` @@ -75,12 +81,6 @@ Adds a few conveniences for perfectionists: * Sets the ``Content-Length`` header for non-streaming responses. -.. attribute:: CommonMiddleware.response_redirect_class - -Defaults to :class:`~django.http.HttpResponsePermanentRedirect`. Subclass -``CommonMiddleware`` and override the attribute to customize the redirects -issued by the middleware. - .. class:: BrokenLinkEmailsMiddleware * Sends broken link notification emails to :setting:`MANAGERS` (see @@ -164,16 +164,16 @@ Locale middleware .. class:: LocaleMiddleware + .. attribute:: LocaleMiddleware.response_redirect_class + + Defaults to :class:`~django.http.HttpResponseRedirect`. Subclass + ``LocaleMiddleware`` and override the attribute to customize the + redirects issued by the middleware. + Enables language selection based on data from the request. It customizes content for each user. See the :doc:`internationalization documentation `. -.. attribute:: LocaleMiddleware.response_redirect_class - -Defaults to :class:`~django.http.HttpResponseRedirect`. Subclass -``LocaleMiddleware`` and override the attribute to customize the redirects -issued by the middleware. - Message middleware ------------------ @@ -500,6 +500,29 @@ every incoming ``HttpRequest`` object. See :ref:`Authentication in web requests .. class:: LoginRequiredMiddleware + Subclass the middleware and override the following attributes and methods + to customize behavior for unauthenticated requests. + + .. attribute:: redirect_field_name + + Defaults to ``"next"``. + + .. method:: get_login_url() + + Returns the URL that unauthenticated requests will be redirected to. This + result is either the ``login_url`` set on the + :func:`~django.contrib.auth.decorators.login_required` decorator (if not + ``None``), or :setting:`settings.LOGIN_URL `. + + .. method:: get_redirect_field_name() + + Returns the name of the query parameter that contains the URL the user + should be redirected to after a successful login. This result is either + the ``redirect_field_name`` set on the + :func:`~.django.contrib.auth.decorators.login_required` decorator (if not + ``None``), or :attr:`redirect_field_name`. If ``None`` is returned, a query + parameter won't be added. + .. versionadded:: 5.1 Redirects all unauthenticated requests to a login page, except for views @@ -552,31 +575,6 @@ Customize the login URL or field name for authenticated views with the :ref:`enabled unauthenticated requests ` to your login view. -**Methods and Attributes** - -Subclass the middleware and override these to customize behavior for -unauthenticated requests. - -.. attribute:: redirect_field_name - - Defaults to ``"next"``. - -.. method:: get_login_url() - - Returns the URL that unauthenticated requests will be redirected to. If - defined, this returns the ``login_url`` set on the - :func:`~.django.contrib.auth.decorators.login_required` decorator. Defaults - to :setting:`settings.LOGIN_URL `. - -.. method:: get_redirect_field_name() - - Returns the name of the query parameter that contains the URL the user - should be redirected to after a successful login. If defined, this returns - the ``redirect_field_name`` set on the - :func:`~.django.contrib.auth.decorators.login_required` decorator. Defaults - to :attr:`redirect_field_name`. If ``None`` is returned, a query parameter - won't be added. - .. class:: RemoteUserMiddleware Middleware for utilizing web server provided authentication. See diff --git a/docs/ref/models/constraints.txt b/docs/ref/models/constraints.txt index 7dfc3b7d28..c1f140c265 100644 --- a/docs/ref/models/constraints.txt +++ b/docs/ref/models/constraints.txt @@ -282,27 +282,47 @@ PostgreSQL 15+. .. attribute:: UniqueConstraint.violation_error_code -The error code used when ``ValidationError`` is raised during -:ref:`model validation `. Defaults to ``None``. +The error code used when a ``ValidationError`` is raised during +:ref:`model validation `. -This code is *not used* for :class:`UniqueConstraint`\s with -:attr:`~UniqueConstraint.fields` and without a -:attr:`~UniqueConstraint.condition`. Such :class:`~UniqueConstraint`\s have the -same error code as constraints defined with :attr:`.Field.unique` or in -:attr:`Meta.unique_together `. +Defaults to :attr:`.BaseConstraint.violation_error_code`, when either +:attr:`.UniqueConstraint.condition` is set or :attr:`.UniqueConstraint.fields` +is not set. + +If :attr:`.UniqueConstraint.fields` is set without a +:attr:`.UniqueConstraint.condition`, defaults to the :attr:`Meta.unique_together +` error code when there are multiple +fields, and to the :attr:`.Field.unique` error code when there is a single +field. + +.. versionchanged:: 5.2 + + In older versions, the provided + :attr:`.UniqueConstraint.violation_error_code` was not used when + :attr:`.UniqueConstraint.fields` was set without a + :attr:`.UniqueConstraint.condition`. ``violation_error_message`` --------------------------- .. attribute:: UniqueConstraint.violation_error_message -The error message used when ``ValidationError`` is raised during -:ref:`model validation `. Defaults to -:attr:`.BaseConstraint.violation_error_message`. +The error message used when a ``ValidationError`` is raised during +:ref:`model validation `. -This message is *not used* for :class:`UniqueConstraint`\s with -:attr:`~UniqueConstraint.fields` and without a -:attr:`~UniqueConstraint.condition`. Such :class:`~UniqueConstraint`\s show the -same message as constraints defined with -:attr:`.Field.unique` or in -:attr:`Meta.unique_together `. +Defaults to :attr:`.BaseConstraint.violation_error_message`, when either +:attr:`.UniqueConstraint.condition` is set or :attr:`.UniqueConstraint.fields` +is not set. + +If :attr:`.UniqueConstraint.fields` is set without a +:attr:`.UniqueConstraint.condition`, defaults to the :attr:`Meta.unique_together +` error message when there are +multiple fields, and to the :attr:`.Field.unique` error message when there is a +single field. + +.. versionchanged:: 5.2 + + In older versions, the provided + :attr:`.UniqueConstraint.violation_error_message` was not used when + :attr:`.UniqueConstraint.fields` was set without a + :attr:`.UniqueConstraint.condition`. diff --git a/docs/ref/request-response.txt b/docs/ref/request-response.txt index 31111a435a..afebd00d8b 100644 --- a/docs/ref/request-response.txt +++ b/docs/ref/request-response.txt @@ -833,6 +833,13 @@ Attributes A bytestring representing the content, encoded from a string if necessary. +.. attribute:: HttpResponse.text + + .. versionadded:: 5.2 + + A string representation of :attr:`HttpResponse.content`, decoded using the + response's :attr:`HttpResponse.charset` (defaulting to ``UTF-8`` if empty). + .. attribute:: HttpResponse.cookies A :py:obj:`http.cookies.SimpleCookie` object holding the cookies included @@ -1272,6 +1279,9 @@ with the following notable differences: :attr:`~StreamingHttpResponse.streaming_content` attribute. This can be used in middleware to wrap the response iterable, but should not be consumed. +* It has no ``text`` attribute, as it would require iterating the response + object. + * You cannot use the file-like object ``tell()`` or ``write()`` methods. Doing so will raise an exception. diff --git a/docs/ref/templates/api.txt b/docs/ref/templates/api.txt index a46717b8d7..8d5c66367d 100644 --- a/docs/ref/templates/api.txt +++ b/docs/ref/templates/api.txt @@ -660,7 +660,6 @@ settings file, the default template engine contains the following context processors:: [ - "django.template.context_processors.debug", "django.template.context_processors.request", "django.contrib.auth.context_processors.auth", "django.contrib.messages.context_processors.messages", diff --git a/docs/ref/urls.txt b/docs/ref/urls.txt index 2ef873d348..95eb03f35a 100644 --- a/docs/ref/urls.txt +++ b/docs/ref/urls.txt @@ -25,6 +25,9 @@ Returns an element for inclusion in ``urlpatterns``. For example:: ..., ] +``route`` +--------- + The ``route`` argument should be a string or :func:`~django.utils.translation.gettext_lazy()` (see :ref:`translating-urlpatterns`) that contains a URL pattern. The string @@ -33,16 +36,43 @@ URL and send it as a keyword argument to the view. The angle brackets may include a converter specification (like the ``int`` part of ````) which limits the characters matched and may also change the type of the variable passed to the view. For example, ```` matches a string -of decimal digits and converts the value to an ``int``. See +of decimal digits and converts the value to an ``int``. + +When processing a request, Django starts at the first pattern in +``urlpatterns`` and makes its way down the list, comparing the requested URL +against each pattern until it finds one that matches. See :ref:`how-django-processes-a-request` for more details. +Patterns don't match GET and POST parameters, or the domain name. For example, +in a request to ``https://www.example.com/myapp/``, the URLconf will look for +``myapp/``. In a request to ``https://www.example.com/myapp/?page=3``, the +URLconf will also look for ``myapp/``. + +``view`` +-------- + The ``view`` argument is a view function or the result of :meth:`~django.views.generic.base.View.as_view` for class-based views. It can -also be an :func:`django.urls.include`. +also be a :func:`django.urls.include`. + +When Django finds a matching pattern, it calls the specified view function with +an :class:`~django.http.HttpRequest` object as the first argument and any +"captured" values from the route as keyword arguments. + +``kwargs`` +---------- The ``kwargs`` argument allows you to pass additional arguments to the view function or method. See :ref:`views-extra-options` for an example. +``name`` +-------- + +Naming your URL lets you refer to it unambiguously from elsewhere in Django, +especially from within templates. This powerful feature allows you to make +global changes to the URL patterns of your project while only touching a single +file. + See :ref:`Naming URL patterns ` for why the ``name`` argument is useful. diff --git a/docs/releases/5.1.2.txt b/docs/releases/5.1.2.txt index 6d2714df85..c6140adcb0 100644 --- a/docs/releases/5.1.2.txt +++ b/docs/releases/5.1.2.txt @@ -2,9 +2,10 @@ Django 5.1.2 release notes ========================== -*Expected October 8, 2024* +*October 8, 2024* -Django 5.1.2 fixes several bugs in 5.1.1. +Django 5.1.2 fixes several bugs in 5.1.1. Also, the latest string translations +from Transifex are incorporated. Bugfixes ======== @@ -15,3 +16,7 @@ Bugfixes * Fixed a regression in Django 5.1 that caused a crash of ``JSONObject()`` when using server-side binding with PostgreSQL 16+ (:ticket:`35734`). + +* Fixed a regression in Django 5.1 that made selected items in multi-select + widgets indistinguishable from non-selected items in the admin dark theme + (:ticket:`35809`). diff --git a/docs/releases/5.1.3.txt b/docs/releases/5.1.3.txt new file mode 100644 index 0000000000..5541a8824a --- /dev/null +++ b/docs/releases/5.1.3.txt @@ -0,0 +1,13 @@ +========================== +Django 5.1.3 release notes +========================== + +*Expected November 5, 2024* + +Django 5.1.3 fixes several bugs in 5.1.2 and adds compatibility with Python +3.13. + +Bugfixes +======== + +* ... diff --git a/docs/releases/5.1.txt b/docs/releases/5.1.txt index bc868fddda..037c76fd54 100644 --- a/docs/releases/5.1.txt +++ b/docs/releases/5.1.txt @@ -18,8 +18,9 @@ project. Python compatibility ==================== -Django 5.1 supports Python 3.10, 3.11, and 3.12. We **highly recommend** and -only officially support the latest release of each series. +Django 5.1 supports Python 3.10, 3.11, 3.12, and 3.13 (as of 5.1.3). We +**highly recommend** and only officially support the latest release of each +series. .. _whats-new-5.1: diff --git a/docs/releases/5.2.txt b/docs/releases/5.2.txt index c6b6193b9e..5792a8d90f 100644 --- a/docs/releases/5.2.txt +++ b/docs/releases/5.2.txt @@ -63,6 +63,40 @@ Minor features * The default iteration count for the PBKDF2 password hasher is increased from 870,000 to 1,000,000. +* The following new asynchronous methods are now provided, using an ``a`` + prefix: + + * :meth:`.UserManager.acreate_user` + * :meth:`.UserManager.acreate_superuser` + * :meth:`.BaseUserManager.aget_by_natural_key` + * :meth:`.User.aget_user_permissions` + * :meth:`.User.aget_all_permissions` + * :meth:`.User.aget_group_permissions` + * :meth:`.User.ahas_perm` + * :meth:`.User.ahas_perms` + * :meth:`.User.ahas_module_perms` + * :meth:`.User.aget_user_permissions` + * :meth:`.User.aget_group_permissions` + * :meth:`.User.ahas_perm` + * :meth:`.ModelBackend.aauthenticate` + * :meth:`.ModelBackend.aget_user_permissions` + * :meth:`.ModelBackend.aget_group_permissions` + * :meth:`.ModelBackend.aget_all_permissions` + * :meth:`.ModelBackend.ahas_perm` + * :meth:`.ModelBackend.ahas_module_perms` + * :meth:`.RemoteUserBackend.aauthenticate` + * :meth:`.RemoteUserBackend.aconfigure_user` + +* Auth backends can now provide async implementations which are used when + calling async auth functions (e.g. + :func:`~.django.contrib.auth.aauthenticate`) to reduce context-switching which + improves performance. See :ref:`adding an async interface + ` for more details. + +* The :ref:`password validator classes ` + now have a new method ``get_error_message()``, which can be overridden in + subclasses to customize the error messages. + :mod:`django.contrib.contenttypes` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -211,6 +245,10 @@ Management Commands setting the :envvar:`HIDE_PRODUCTION_WARNING` environment variable to ``"true"``. +* The :djadmin:`makemigrations` and :djadmin:`migrate` commands have a new + ``Command.autodetector`` attribute for subclasses to override in order to use + a custom autodetector class. + Migrations ~~~~~~~~~~ @@ -241,6 +279,9 @@ Models Requests and Responses ~~~~~~~~~~~~~~~~~~~~~~ +* The new :attr:`.HttpResponse.text` property provides the string representation + of :attr:`.HttpResponse.content`. + * The new :meth:`.HttpRequest.get_preferred_type` method can be used to query the preferred media type the client accepts. @@ -339,6 +380,14 @@ Miscellaneous * ``HttpRequest.accepted_types`` is now sorted by the client's preference, based on the request's ``Accept`` header. +* :attr:`.UniqueConstraint.violation_error_code` and + :attr:`.UniqueConstraint.violation_error_message` are now always used when + provided. Previously, these were ignored when :attr:`.UniqueConstraint.fields` + were set without a :attr:`.UniqueConstraint.condition`. + +* The :func:`~django.template.context_processors.debug` context processor is no + longer included in the default project template. + .. _deprecated-features-5.2: Features deprecated in 5.2 diff --git a/docs/releases/index.txt b/docs/releases/index.txt index 689044cfe3..a38053c2a9 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -32,6 +32,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 5.1.3 5.1.2 5.1.1 5.1 diff --git a/docs/spelling_wordlist b/docs/spelling_wordlist index d715e62e05..747a712a62 100644 --- a/docs/spelling_wordlist +++ b/docs/spelling_wordlist @@ -123,6 +123,7 @@ deduplicates deduplication deepcopy deferrable +DEP deprecations deserialization deserialize @@ -141,6 +142,7 @@ Disqus distro django djangoproject +djangotutorial dm docstring docstrings diff --git a/docs/topics/auth/customizing.txt b/docs/topics/auth/customizing.txt index f41b10fb4a..6fdcd136c0 100644 --- a/docs/topics/auth/customizing.txt +++ b/docs/topics/auth/customizing.txt @@ -790,10 +790,17 @@ utility methods: email address. .. method:: models.BaseUserManager.get_by_natural_key(username) + .. method:: models.BaseUserManager.aget_by_natural_key(username) + + *Asynchronous version*: ``aget_by_natural_key()`` Retrieves a user instance using the contents of the field nominated by ``USERNAME_FIELD``. + .. versionchanged:: 5.2 + + ``aget_by_natural_key()`` method was added. + Extending Django's default ``User`` ----------------------------------- @@ -1186,3 +1193,25 @@ Finally, specify the custom model as the default user model for your project using the :setting:`AUTH_USER_MODEL` setting in your ``settings.py``:: AUTH_USER_MODEL = "customauth.MyUser" + +.. _writing-authentication-backends-async-interface: + +Adding an async interface +~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 5.2 + +To optimize performance when called from an async context authentication, +backends can implement async versions of each function - ``aget_user(user_id)`` +and ``aauthenticate(request, **credentials)``. When an authentication backend +extends ``BaseBackend`` and async versions of these functions are not provided, +they will be automatically synthesized with ``sync_to_async``. This has +:ref:`performance penalties `. + +While an async interface is optional, a synchronous interface is always +required. There is no automatic synthesis for a synchronous interface if an +async interface is implemented. + +Django's out-of-the-box authentication backends have native async support. If +these native backends are extended take special care to make sure the async +versions of modified functions are modified as well. diff --git a/docs/topics/auth/passwords.txt b/docs/topics/auth/passwords.txt index 68f5453d54..8efd2bdebf 100644 --- a/docs/topics/auth/passwords.txt +++ b/docs/topics/auth/passwords.txt @@ -590,6 +590,8 @@ has no settings. The help texts and any errors from password validators are always returned in the order they are listed in :setting:`AUTH_PASSWORD_VALIDATORS`. +.. _included-password-validators: + Included validators ------------------- @@ -600,6 +602,19 @@ Django includes four validators: Validates that the password is of a minimum length. The minimum length can be customized with the ``min_length`` parameter. + .. method:: get_error_message() + + .. versionadded:: 5.2 + + A hook for customizing the ``ValidationError`` error message. Defaults + to ``"This password is too short. It must contain at least + characters."``. + + .. method:: get_help_text() + + A hook for customizing the validator's help text. Defaults to ``"Your + password must contain at least characters."``. + .. class:: UserAttributeSimilarityValidator(user_attributes=DEFAULT_USER_ATTRIBUTES, max_similarity=0.7) Validates that the password is sufficiently different from certain @@ -617,6 +632,18 @@ Django includes four validators: ``user_attributes``, whereas a value of 1.0 rejects only passwords that are identical to an attribute's value. + .. method:: get_error_message() + + .. versionadded:: 5.2 + + A hook for customizing the ``ValidationError`` error message. Defaults + to ``"The password is too similar to the ."``. + + .. method:: get_help_text() + + A hook for customizing the validator's help text. Defaults to ``"Your + password can’t be too similar to your other personal information."``. + .. class:: CommonPasswordValidator(password_list_path=DEFAULT_PASSWORD_LIST_PATH) Validates that the password is not a common password. This converts the @@ -628,10 +655,34 @@ Django includes four validators: common passwords. This file should contain one lowercase password per line and may be plain text or gzipped. + .. method:: get_error_message() + + .. versionadded:: 5.2 + + A hook for customizing the ``ValidationError`` error message. Defaults + to ``"This password is too common."``. + + .. method:: get_help_text() + + A hook for customizing the validator's help text. Defaults to ``"Your + password can’t be a commonly used password."``. + .. class:: NumericPasswordValidator() Validate that the password is not entirely numeric. + .. method:: get_error_message() + + .. versionadded:: 5.2 + + A hook for customizing the ``ValidationError`` error message. Defaults + to ``"This password is entirely numeric."``. + + .. method:: get_help_text() + + A hook for customizing the validator's help text. Defaults to ``"Your + password can’t be entirely numeric."``. + Integrating validation ---------------------- diff --git a/docs/topics/db/queries.txt b/docs/topics/db/queries.txt index 45c6183103..7e3338eaea 100644 --- a/docs/topics/db/queries.txt +++ b/docs/topics/db/queries.txt @@ -62,7 +62,8 @@ class represents a particular record in the database table. To create an object, instantiate it using keyword arguments to the model class, then call :meth:`~django.db.models.Model.save` to save it to the database. -Assuming models live in a file ``mysite/blog/models.py``, here's an example: +Assuming models live in a ``models.py`` file inside a ``blog`` Django app, here +is an example: .. code-block:: pycon diff --git a/package.json b/package.json index 0e6bb80f1c..b3bc782c40 100644 --- a/package.json +++ b/package.json @@ -9,8 +9,8 @@ "npm": ">=1.3.0" }, "devDependencies": { - "eslint": "^9.7.0", - "puppeteer": "^22.13.1", + "eslint": "^9.12.0", + "puppeteer": "^23.5.1", "globals": "^15.8.0", "grunt": "^1.6.1", "grunt-cli": "^1.5.0", diff --git a/pyproject.toml b/pyproject.toml index 540bc47439..86ea7393ec 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,6 +30,7 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: Dynamic Content", "Topic :: Internet :: WWW/HTTP :: WSGI", diff --git a/tests/admin_views/test_autocomplete_view.py b/tests/admin_views/test_autocomplete_view.py index dc3789fc5b..d9595cdb28 100644 --- a/tests/admin_views/test_autocomplete_view.py +++ b/tests/admin_views/test_autocomplete_view.py @@ -102,7 +102,7 @@ class AutocompleteJsonViewTests(AdminViewBasicTestCase): request.user = self.superuser response = AutocompleteJsonView.as_view(**self.as_view_args)(request) self.assertEqual(response.status_code, 200) - data = json.loads(response.content.decode("utf-8")) + data = json.loads(response.text) self.assertEqual( data, { @@ -120,7 +120,7 @@ class AutocompleteJsonViewTests(AdminViewBasicTestCase): request.user = self.superuser response = AutocompleteJsonView.as_view(**self.as_view_args)(request) self.assertEqual(response.status_code, 200) - data = json.loads(response.content.decode("utf-8")) + data = json.loads(response.text) self.assertEqual( data, { @@ -150,7 +150,7 @@ class AutocompleteJsonViewTests(AdminViewBasicTestCase): request.user = self.superuser response = AutocompleteJsonView.as_view(**self.as_view_args)(request) self.assertEqual(response.status_code, 200) - data = json.loads(response.content.decode("utf-8")) + data = json.loads(response.text) self.assertEqual( data, { @@ -184,7 +184,7 @@ class AutocompleteJsonViewTests(AdminViewBasicTestCase): request.user = self.superuser response = AutocompleteJsonView.as_view(**self.as_view_args)(request) self.assertEqual(response.status_code, 200) - data = json.loads(response.content.decode("utf-8")) + data = json.loads(response.text) self.assertEqual( data, { @@ -205,7 +205,7 @@ class AutocompleteJsonViewTests(AdminViewBasicTestCase): request.user = self.superuser response = AutocompleteJsonView.as_view(**self.as_view_args)(request) self.assertEqual(response.status_code, 200) - data = json.loads(response.content.decode("utf-8")) + data = json.loads(response.text) self.assertEqual( data, { @@ -250,7 +250,7 @@ class AutocompleteJsonViewTests(AdminViewBasicTestCase): request.user = self.superuser response = AutocompleteJsonView.as_view(**self.as_view_args)(request) self.assertEqual(response.status_code, 200) - data = json.loads(response.content.decode("utf-8")) + data = json.loads(response.text) self.assertEqual( data, { @@ -306,7 +306,7 @@ class AutocompleteJsonViewTests(AdminViewBasicTestCase): with model_admin(Question, DistinctQuestionAdmin): response = AutocompleteJsonView.as_view(**self.as_view_args)(request) self.assertEqual(response.status_code, 200) - data = json.loads(response.content.decode("utf-8")) + data = json.loads(response.text) self.assertEqual(len(data["results"]), 3) def test_missing_search_fields(self): @@ -335,7 +335,7 @@ class AutocompleteJsonViewTests(AdminViewBasicTestCase): with model_admin(Question, PKOrderingQuestionAdmin): response = AutocompleteJsonView.as_view(**self.as_view_args)(request) self.assertEqual(response.status_code, 200) - data = json.loads(response.content.decode("utf-8")) + data = json.loads(response.text) self.assertEqual( data, { @@ -352,7 +352,7 @@ class AutocompleteJsonViewTests(AdminViewBasicTestCase): with model_admin(Question, PKOrderingQuestionAdmin): response = AutocompleteJsonView.as_view(**self.as_view_args)(request) self.assertEqual(response.status_code, 200) - data = json.loads(response.content.decode("utf-8")) + data = json.loads(response.text) self.assertEqual( data, { @@ -380,7 +380,7 @@ class AutocompleteJsonViewTests(AdminViewBasicTestCase): request ) self.assertEqual(response.status_code, 200) - data = json.loads(response.content.decode("utf-8")) + data = json.loads(response.text) self.assertEqual( data, { diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py index 9ec38e45ec..f024725cb5 100644 --- a/tests/admin_views/tests.py +++ b/tests/admin_views/tests.py @@ -296,9 +296,7 @@ class AdminViewBasicTestCase(TestCase): self.assertLess( response.content.index(text1.encode()), response.content.index(text2.encode()), - (failing_msg or "") - + "\nResponse:\n" - + response.content.decode(response.charset), + (failing_msg or "") + "\nResponse:\n" + response.text, ) @@ -1686,7 +1684,6 @@ class AdminViewBasicTest(AdminViewBasicTestCase): "APP_DIRS": True, "OPTIONS": { "context_processors": [ - "django.template.context_processors.debug", "django.template.context_processors.request", "django.contrib.auth.context_processors.auth", "django.contrib.messages.context_processors.messages", @@ -3604,7 +3601,7 @@ class AdminViewDeletedObjectsTest(TestCase): response = self.client.get( reverse("admin:admin_views_villain_delete", args=(self.v1.pk,)) ) - self.assertRegex(response.content.decode(), pattern) + self.assertRegex(response.text, pattern) def test_cyclic(self): """ @@ -6176,6 +6173,65 @@ class SeleniumTests(AdminSeleniumTestCase): ) self.take_screenshot("selectbox-non-collapsible") + @screenshot_cases(["desktop_size", "mobile_size", "rtl", "dark", "high_contrast"]) + def test_selectbox_selected_rows(self): + from selenium.webdriver import ActionChains + from selenium.webdriver.common.by import By + from selenium.webdriver.common.keys import Keys + + self.admin_login( + username="super", password="secret", login_url=reverse("admin:index") + ) + # Create a new user to ensure that no extra permissions have been set. + user = User.objects.create_user(username="new", password="newuser") + url = self.live_server_url + reverse("admin:auth_user_change", args=[user.id]) + self.selenium.get(url) + + # Scroll to the User permissions section. + user_permissions = self.selenium.find_element( + By.CSS_SELECTOR, "#id_user_permissions_from" + ) + ActionChains(self.selenium).move_to_element(user_permissions).perform() + self.take_screenshot("selectbox-available-perms-none-selected") + + # Select multiple permissions from the "Available" list. + ct = ContentType.objects.get_for_model(Permission) + perms = list(Permission.objects.filter(content_type=ct)) + for perm in perms: + elem = self.selenium.find_element( + By.CSS_SELECTOR, f"#id_user_permissions_from option[value='{perm.id}']" + ) + ActionChains(self.selenium).key_down(Keys.CONTROL).click(elem).key_up( + Keys.CONTROL + ).perform() + + # Move focus to other element. + self.selenium.find_element( + By.CSS_SELECTOR, "#id_user_permissions_input" + ).click() + self.take_screenshot("selectbox-available-perms-some-selected") + + # Move permissions to the "Chosen" list, but none is selected yet. + self.selenium.find_element( + By.CSS_SELECTOR, "#id_user_permissions_add_link" + ).click() + self.take_screenshot("selectbox-chosen-perms-none-selected") + + # Select some permissions from the "Chosen" list. + for perm in [perms[0], perms[-1]]: + elem = self.selenium.find_element( + By.CSS_SELECTOR, f"#id_user_permissions_to option[value='{perm.id}']" + ) + ActionChains(self.selenium).key_down(Keys.CONTROL).click(elem).key_up( + Keys.CONTROL + ).perform() + + # Move focus to other element. + self.selenium.find_element( + By.CSS_SELECTOR, "#id_user_permissions_selected_input" + ).click() + self.take_screenshot("selectbox-chosen-perms-some-selected") + @screenshot_cases(["desktop_size", "mobile_size", "rtl", "dark", "high_contrast"]) def test_first_field_focus(self): """JavaScript-assisted auto-focus on first usable form field.""" @@ -7714,7 +7770,6 @@ class AdminDocsTest(TestCase): "APP_DIRS": True, "OPTIONS": { "context_processors": [ - "django.template.context_processors.debug", "django.template.context_processors.request", "django.contrib.auth.context_processors.auth", "django.contrib.messages.context_processors.messages", @@ -8228,7 +8283,7 @@ class AdminKeepChangeListFiltersTests(TestCase): # Check the `change_view` link has the correct querystring. detail_link = re.search( '{}'.format(self.joepublicuser.username), - response.content.decode(), + response.text, ) self.assertURLEqual(detail_link[1], self.get_change_url()) @@ -8240,7 +8295,7 @@ class AdminKeepChangeListFiltersTests(TestCase): # Check the form action. form_action = re.search( '
', - response.content.decode(), + response.text, ) self.assertURLEqual( form_action[1], "?%s" % self.get_preserved_filters_querystring() @@ -8248,13 +8303,13 @@ class AdminKeepChangeListFiltersTests(TestCase): # Check the history link. history_link = re.search( - 'History', response.content.decode() + 'History', response.text ) self.assertURLEqual(history_link[1], self.get_history_url()) # Check the delete link. delete_link = re.search( - 'Delete', response.content.decode() + 'Delete', response.text ) self.assertURLEqual(delete_link[1], self.get_delete_url()) @@ -8294,7 +8349,7 @@ class AdminKeepChangeListFiltersTests(TestCase): self.client.force_login(viewuser) response = self.client.get(self.get_change_url()) close_link = re.search( - 'Close', response.content.decode() + 'Close', response.text ) close_link = close_link[1].replace("&", "&") self.assertURLEqual(close_link, self.get_changelist_url()) @@ -8312,7 +8367,7 @@ class AdminKeepChangeListFiltersTests(TestCase): # Check the form action. form_action = re.search( '', - response.content.decode(), + response.text, ) self.assertURLEqual( form_action[1], "?%s" % self.get_preserved_filters_querystring() diff --git a/tests/async/test_async_auth.py b/tests/async/test_async_auth.py index f6551c63ee..37884d13a6 100644 --- a/tests/async/test_async_auth.py +++ b/tests/async/test_async_auth.py @@ -33,9 +33,40 @@ class AsyncAuthTest(TestCase): self.assertIsInstance(user, User) self.assertEqual(user.username, self.test_user.username) + async def test_changed_password_invalidates_aget_user(self): + request = HttpRequest() + request.session = await self.client.asession() + await alogin(request, self.test_user) + + self.test_user.set_password("new_password") + await self.test_user.asave() + + user = await aget_user(request) + + self.assertIsNotNone(user) + self.assertTrue(user.is_anonymous) + # Session should be flushed. + self.assertIsNone(request.session.session_key) + + async def test_alogin_new_user(self): + request = HttpRequest() + request.session = await self.client.asession() + await alogin(request, self.test_user) + second_user = await User.objects.acreate_user( + "testuser2", "test2@example.com", "testpw2" + ) + await alogin(request, second_user) + user = await aget_user(request) + self.assertIsInstance(user, User) + self.assertEqual(user.username, second_user.username) + async def test_alogin_without_user(self): + async def auser(): + return self.test_user + request = HttpRequest() request.user = self.test_user + request.auser = auser request.session = await self.client.asession() await alogin(request, None) user = await aget_user(request) diff --git a/tests/auth_tests/models/custom_user.py b/tests/auth_tests/models/custom_user.py index b9938681ca..4586e452cd 100644 --- a/tests/auth_tests/models/custom_user.py +++ b/tests/auth_tests/models/custom_user.py @@ -29,6 +29,19 @@ class CustomUserManager(BaseUserManager): user.save(using=self._db) return user + async def acreate_user(self, email, date_of_birth, password=None, **fields): + """See create_user()""" + if not email: + raise ValueError("Users must have an email address") + + user = self.model( + email=self.normalize_email(email), date_of_birth=date_of_birth, **fields + ) + + user.set_password(password) + await user.asave(using=self._db) + return user + def create_superuser(self, email, password, date_of_birth, **fields): u = self.create_user( email, password=password, date_of_birth=date_of_birth, **fields diff --git a/tests/auth_tests/test_auth_backends.py b/tests/auth_tests/test_auth_backends.py index 3b4f40e6e0..b612d27ab0 100644 --- a/tests/auth_tests/test_auth_backends.py +++ b/tests/auth_tests/test_auth_backends.py @@ -2,6 +2,8 @@ import sys from datetime import date from unittest import mock +from asgiref.sync import sync_to_async + from django.contrib.auth import ( BACKEND_SESSION_KEY, SESSION_KEY, @@ -55,17 +57,33 @@ class BaseBackendTest(TestCase): def test_get_user_permissions(self): self.assertEqual(self.user.get_user_permissions(), {"user_perm"}) + async def test_aget_user_permissions(self): + self.assertEqual(await self.user.aget_user_permissions(), {"user_perm"}) + def test_get_group_permissions(self): self.assertEqual(self.user.get_group_permissions(), {"group_perm"}) + async def test_aget_group_permissions(self): + self.assertEqual(await self.user.aget_group_permissions(), {"group_perm"}) + def test_get_all_permissions(self): self.assertEqual(self.user.get_all_permissions(), {"user_perm", "group_perm"}) + async def test_aget_all_permissions(self): + self.assertEqual( + await self.user.aget_all_permissions(), {"user_perm", "group_perm"} + ) + def test_has_perm(self): self.assertIs(self.user.has_perm("user_perm"), True) self.assertIs(self.user.has_perm("group_perm"), True) self.assertIs(self.user.has_perm("other_perm", TestObj()), False) + async def test_ahas_perm(self): + self.assertIs(await self.user.ahas_perm("user_perm"), True) + self.assertIs(await self.user.ahas_perm("group_perm"), True) + self.assertIs(await self.user.ahas_perm("other_perm", TestObj()), False) + def test_has_perms_perm_list_invalid(self): msg = "perm_list must be an iterable of permissions." with self.assertRaisesMessage(ValueError, msg): @@ -73,6 +91,13 @@ class BaseBackendTest(TestCase): with self.assertRaisesMessage(ValueError, msg): self.user.has_perms(object()) + async def test_ahas_perms_perm_list_invalid(self): + msg = "perm_list must be an iterable of permissions." + with self.assertRaisesMessage(ValueError, msg): + await self.user.ahas_perms("user_perm") + with self.assertRaisesMessage(ValueError, msg): + await self.user.ahas_perms(object()) + class CountingMD5PasswordHasher(MD5PasswordHasher): """Hasher that counts how many times it computes a hash.""" @@ -125,6 +150,25 @@ class BaseModelBackendTest: user.save() self.assertIs(user.has_perm("auth.test"), False) + async def test_ahas_perm(self): + user = await self.UserModel._default_manager.aget(pk=self.user.pk) + self.assertIs(await user.ahas_perm("auth.test"), False) + + user.is_staff = True + await user.asave() + self.assertIs(await user.ahas_perm("auth.test"), False) + + user.is_superuser = True + await user.asave() + self.assertIs(await user.ahas_perm("auth.test"), True) + self.assertIs(await user.ahas_module_perms("auth"), True) + + user.is_staff = True + user.is_superuser = True + user.is_active = False + await user.asave() + self.assertIs(await user.ahas_perm("auth.test"), False) + def test_custom_perms(self): user = self.UserModel._default_manager.get(pk=self.user.pk) content_type = ContentType.objects.get_for_model(Group) @@ -174,6 +218,55 @@ class BaseModelBackendTest: self.assertIs(user.has_perm("test"), False) self.assertIs(user.has_perms(["auth.test2", "auth.test3"]), False) + async def test_acustom_perms(self): + user = await self.UserModel._default_manager.aget(pk=self.user.pk) + content_type = await sync_to_async(ContentType.objects.get_for_model)(Group) + perm = await Permission.objects.acreate( + name="test", content_type=content_type, codename="test" + ) + await user.user_permissions.aadd(perm) + + # Reloading user to purge the _perm_cache. + user = await self.UserModel._default_manager.aget(pk=self.user.pk) + self.assertEqual(await user.aget_all_permissions(), {"auth.test"}) + self.assertEqual(await user.aget_user_permissions(), {"auth.test"}) + self.assertEqual(await user.aget_group_permissions(), set()) + self.assertIs(await user.ahas_module_perms("Group"), False) + self.assertIs(await user.ahas_module_perms("auth"), True) + + perm = await Permission.objects.acreate( + name="test2", content_type=content_type, codename="test2" + ) + await user.user_permissions.aadd(perm) + perm = await Permission.objects.acreate( + name="test3", content_type=content_type, codename="test3" + ) + await user.user_permissions.aadd(perm) + user = await self.UserModel._default_manager.aget(pk=self.user.pk) + expected_user_perms = {"auth.test2", "auth.test", "auth.test3"} + self.assertEqual(await user.aget_all_permissions(), expected_user_perms) + self.assertIs(await user.ahas_perm("test"), False) + self.assertIs(await user.ahas_perm("auth.test"), True) + self.assertIs(await user.ahas_perms(["auth.test2", "auth.test3"]), True) + + perm = await Permission.objects.acreate( + name="test_group", content_type=content_type, codename="test_group" + ) + group = await Group.objects.acreate(name="test_group") + await group.permissions.aadd(perm) + await user.groups.aadd(group) + user = await self.UserModel._default_manager.aget(pk=self.user.pk) + self.assertEqual( + await user.aget_all_permissions(), {*expected_user_perms, "auth.test_group"} + ) + self.assertEqual(await user.aget_user_permissions(), expected_user_perms) + self.assertEqual(await user.aget_group_permissions(), {"auth.test_group"}) + self.assertIs(await user.ahas_perms(["auth.test3", "auth.test_group"]), True) + + user = AnonymousUser() + self.assertIs(await user.ahas_perm("test"), False) + self.assertIs(await user.ahas_perms(["auth.test2", "auth.test3"]), False) + def test_has_no_object_perm(self): """Regressiontest for #12462""" user = self.UserModel._default_manager.get(pk=self.user.pk) @@ -188,6 +281,20 @@ class BaseModelBackendTest: self.assertIs(user.has_perm("auth.test"), True) self.assertEqual(user.get_all_permissions(), {"auth.test"}) + async def test_ahas_no_object_perm(self): + """See test_has_no_object_perm()""" + user = await self.UserModel._default_manager.aget(pk=self.user.pk) + content_type = await sync_to_async(ContentType.objects.get_for_model)(Group) + perm = await Permission.objects.acreate( + name="test", content_type=content_type, codename="test" + ) + await user.user_permissions.aadd(perm) + + self.assertIs(await user.ahas_perm("auth.test", "object"), False) + self.assertEqual(await user.aget_all_permissions("object"), set()) + self.assertIs(await user.ahas_perm("auth.test"), True) + self.assertEqual(await user.aget_all_permissions(), {"auth.test"}) + def test_anonymous_has_no_permissions(self): """ #17903 -- Anonymous users shouldn't have permissions in @@ -220,6 +327,38 @@ class BaseModelBackendTest: self.assertEqual(backend.get_user_permissions(user), set()) self.assertEqual(backend.get_group_permissions(user), set()) + async def test_aanonymous_has_no_permissions(self): + """See test_anonymous_has_no_permissions()""" + backend = ModelBackend() + + user = await self.UserModel._default_manager.aget(pk=self.user.pk) + content_type = await sync_to_async(ContentType.objects.get_for_model)(Group) + user_perm = await Permission.objects.acreate( + name="test", content_type=content_type, codename="test_user" + ) + group_perm = await Permission.objects.acreate( + name="test2", content_type=content_type, codename="test_group" + ) + await user.user_permissions.aadd(user_perm) + + group = await Group.objects.acreate(name="test_group") + await user.groups.aadd(group) + await group.permissions.aadd(group_perm) + + self.assertEqual( + await backend.aget_all_permissions(user), + {"auth.test_user", "auth.test_group"}, + ) + self.assertEqual(await backend.aget_user_permissions(user), {"auth.test_user"}) + self.assertEqual( + await backend.aget_group_permissions(user), {"auth.test_group"} + ) + + with mock.patch.object(self.UserModel, "is_anonymous", True): + self.assertEqual(await backend.aget_all_permissions(user), set()) + self.assertEqual(await backend.aget_user_permissions(user), set()) + self.assertEqual(await backend.aget_group_permissions(user), set()) + def test_inactive_has_no_permissions(self): """ #17903 -- Inactive users shouldn't have permissions in @@ -254,11 +393,52 @@ class BaseModelBackendTest: self.assertEqual(backend.get_user_permissions(user), set()) self.assertEqual(backend.get_group_permissions(user), set()) + async def test_ainactive_has_no_permissions(self): + """See test_inactive_has_no_permissions()""" + backend = ModelBackend() + + user = await self.UserModel._default_manager.aget(pk=self.user.pk) + content_type = await sync_to_async(ContentType.objects.get_for_model)(Group) + user_perm = await Permission.objects.acreate( + name="test", content_type=content_type, codename="test_user" + ) + group_perm = await Permission.objects.acreate( + name="test2", content_type=content_type, codename="test_group" + ) + await user.user_permissions.aadd(user_perm) + + group = await Group.objects.acreate(name="test_group") + await user.groups.aadd(group) + await group.permissions.aadd(group_perm) + + self.assertEqual( + await backend.aget_all_permissions(user), + {"auth.test_user", "auth.test_group"}, + ) + self.assertEqual(await backend.aget_user_permissions(user), {"auth.test_user"}) + self.assertEqual( + await backend.aget_group_permissions(user), {"auth.test_group"} + ) + + user.is_active = False + await user.asave() + + self.assertEqual(await backend.aget_all_permissions(user), set()) + self.assertEqual(await backend.aget_user_permissions(user), set()) + self.assertEqual(await backend.aget_group_permissions(user), set()) + def test_get_all_superuser_permissions(self): """A superuser has all permissions. Refs #14795.""" user = self.UserModel._default_manager.get(pk=self.superuser.pk) self.assertEqual(len(user.get_all_permissions()), len(Permission.objects.all())) + async def test_aget_all_superuser_permissions(self): + """See test_get_all_superuser_permissions()""" + user = await self.UserModel._default_manager.aget(pk=self.superuser.pk) + self.assertEqual( + len(await user.aget_all_permissions()), await Permission.objects.acount() + ) + @override_settings( PASSWORD_HASHERS=["auth_tests.test_auth_backends.CountingMD5PasswordHasher"] ) @@ -277,6 +457,24 @@ class BaseModelBackendTest: authenticate(username="no_such_user", password="test") self.assertEqual(CountingMD5PasswordHasher.calls, 1) + @override_settings( + PASSWORD_HASHERS=["auth_tests.test_auth_backends.CountingMD5PasswordHasher"] + ) + async def test_aauthentication_timing(self): + """See test_authentication_timing()""" + # Re-set the password, because this tests overrides PASSWORD_HASHERS. + self.user.set_password("test") + await self.user.asave() + + CountingMD5PasswordHasher.calls = 0 + username = getattr(self.user, self.UserModel.USERNAME_FIELD) + await aauthenticate(username=username, password="test") + self.assertEqual(CountingMD5PasswordHasher.calls, 1) + + CountingMD5PasswordHasher.calls = 0 + await aauthenticate(username="no_such_user", password="test") + self.assertEqual(CountingMD5PasswordHasher.calls, 1) + @override_settings( PASSWORD_HASHERS=["auth_tests.test_auth_backends.CountingMD5PasswordHasher"] ) @@ -320,6 +518,15 @@ class ModelBackendTest(BaseModelBackendTest, TestCase): self.user.save() self.assertIsNone(authenticate(**self.user_credentials)) + async def test_aauthenticate_inactive(self): + """ + An inactive user can't authenticate. + """ + self.assertEqual(await aauthenticate(**self.user_credentials), self.user) + self.user.is_active = False + await self.user.asave() + self.assertIsNone(await aauthenticate(**self.user_credentials)) + @override_settings(AUTH_USER_MODEL="auth_tests.CustomUserWithoutIsActiveField") def test_authenticate_user_without_is_active_field(self): """ @@ -332,6 +539,18 @@ class ModelBackendTest(BaseModelBackendTest, TestCase): ) self.assertEqual(authenticate(username="test", password="test"), user) + @override_settings(AUTH_USER_MODEL="auth_tests.CustomUserWithoutIsActiveField") + async def test_aauthenticate_user_without_is_active_field(self): + """ + A custom user without an `is_active` field is allowed to authenticate. + """ + user = await CustomUserWithoutIsActiveField.objects._acreate_user( + username="test", + email="test@example.com", + password="test", + ) + self.assertEqual(await aauthenticate(username="test", password="test"), user) + @override_settings(AUTH_USER_MODEL="auth_tests.ExtensionUser") class ExtensionUserModelBackendTest(BaseModelBackendTest, TestCase): @@ -403,6 +622,15 @@ class CustomUserModelBackendAuthenticateTest(TestCase): authenticated_user = authenticate(email="test@example.com", password="test") self.assertEqual(test_user, authenticated_user) + async def test_aauthenticate(self): + test_user = await CustomUser._default_manager.acreate_user( + email="test@example.com", password="test", date_of_birth=date(2006, 4, 25) + ) + authenticated_user = await aauthenticate( + email="test@example.com", password="test" + ) + self.assertEqual(test_user, authenticated_user) + @override_settings(AUTH_USER_MODEL="auth_tests.UUIDUser") class UUIDUserTests(TestCase): @@ -416,6 +644,13 @@ class UUIDUserTests(TestCase): UUIDUser.objects.get(pk=self.client.session[SESSION_KEY]), user ) + async def test_alogin(self): + """See test_login()""" + user = await UUIDUser.objects.acreate_user(username="uuid", password="test") + self.assertTrue(await self.client.alogin(username="uuid", password="test")) + session_key = await self.client.session.aget(SESSION_KEY) + self.assertEqual(await UUIDUser.objects.aget(pk=session_key), user) + class TestObj: pass @@ -435,9 +670,15 @@ class SimpleRowlevelBackend: return True return False + async def ahas_perm(self, user, perm, obj=None): + return self.has_perm(user, perm, obj) + def has_module_perms(self, user, app_label): return (user.is_anonymous or user.is_active) and app_label == "app1" + async def ahas_module_perms(self, user, app_label): + return self.has_module_perms(user, app_label) + def get_all_permissions(self, user, obj=None): if not obj: return [] # We only support row level perms @@ -452,6 +693,9 @@ class SimpleRowlevelBackend: else: return ["simple"] + async def aget_all_permissions(self, user, obj=None): + return self.get_all_permissions(user, obj) + def get_group_permissions(self, user, obj=None): if not obj: return # We only support row level perms @@ -524,10 +768,18 @@ class AnonymousUserBackendTest(SimpleTestCase): self.assertIs(self.user1.has_perm("perm", TestObj()), False) self.assertIs(self.user1.has_perm("anon", TestObj()), True) + async def test_ahas_perm(self): + self.assertIs(await self.user1.ahas_perm("perm", TestObj()), False) + self.assertIs(await self.user1.ahas_perm("anon", TestObj()), True) + def test_has_perms(self): self.assertIs(self.user1.has_perms(["anon"], TestObj()), True) self.assertIs(self.user1.has_perms(["anon", "perm"], TestObj()), False) + async def test_ahas_perms(self): + self.assertIs(await self.user1.ahas_perms(["anon"], TestObj()), True) + self.assertIs(await self.user1.ahas_perms(["anon", "perm"], TestObj()), False) + def test_has_perms_perm_list_invalid(self): msg = "perm_list must be an iterable of permissions." with self.assertRaisesMessage(ValueError, msg): @@ -535,13 +787,27 @@ class AnonymousUserBackendTest(SimpleTestCase): with self.assertRaisesMessage(ValueError, msg): self.user1.has_perms(object()) + async def test_ahas_perms_perm_list_invalid(self): + msg = "perm_list must be an iterable of permissions." + with self.assertRaisesMessage(ValueError, msg): + await self.user1.ahas_perms("perm") + with self.assertRaisesMessage(ValueError, msg): + await self.user1.ahas_perms(object()) + def test_has_module_perms(self): self.assertIs(self.user1.has_module_perms("app1"), True) self.assertIs(self.user1.has_module_perms("app2"), False) + async def test_ahas_module_perms(self): + self.assertIs(await self.user1.ahas_module_perms("app1"), True) + self.assertIs(await self.user1.ahas_module_perms("app2"), False) + def test_get_all_permissions(self): self.assertEqual(self.user1.get_all_permissions(TestObj()), {"anon"}) + async def test_aget_all_permissions(self): + self.assertEqual(await self.user1.aget_all_permissions(TestObj()), {"anon"}) + @override_settings(AUTHENTICATION_BACKENDS=[]) class NoBackendsTest(TestCase): @@ -561,6 +827,14 @@ class NoBackendsTest(TestCase): with self.assertRaisesMessage(ImproperlyConfigured, msg): self.user.has_perm(("perm", TestObj())) + async def test_araises_exception(self): + msg = ( + "No authentication backends have been defined. " + "Does AUTHENTICATION_BACKENDS contain anything?" + ) + with self.assertRaisesMessage(ImproperlyConfigured, msg): + await self.user.ahas_perm(("perm", TestObj())) + @override_settings( AUTHENTICATION_BACKENDS=["auth_tests.test_auth_backends.SimpleRowlevelBackend"] @@ -593,12 +867,21 @@ class PermissionDeniedBackend: def authenticate(self, request, username=None, password=None): raise PermissionDenied + async def aauthenticate(self, request, username=None, password=None): + raise PermissionDenied + def has_perm(self, user_obj, perm, obj=None): raise PermissionDenied + async def ahas_perm(self, user_obj, perm, obj=None): + raise PermissionDenied + def has_module_perms(self, user_obj, app_label): raise PermissionDenied + async def ahas_module_perms(self, user_obj, app_label): + raise PermissionDenied + class PermissionDeniedBackendTest(TestCase): """ @@ -631,10 +914,25 @@ class PermissionDeniedBackendTest(TestCase): [{"password": "********************", "username": "test"}], ) + @modify_settings(AUTHENTICATION_BACKENDS={"prepend": backend}) + async def test_aauthenticate_permission_denied(self): + self.assertIsNone(await aauthenticate(username="test", password="test")) + # user_login_failed signal is sent. + self.assertEqual( + self.user_login_failed, + [{"password": "********************", "username": "test"}], + ) + @modify_settings(AUTHENTICATION_BACKENDS={"append": backend}) def test_authenticates(self): self.assertEqual(authenticate(username="test", password="test"), self.user1) + @modify_settings(AUTHENTICATION_BACKENDS={"append": backend}) + async def test_aauthenticate(self): + self.assertEqual( + await aauthenticate(username="test", password="test"), self.user1 + ) + @modify_settings(AUTHENTICATION_BACKENDS={"prepend": backend}) def test_has_perm_denied(self): content_type = ContentType.objects.get_for_model(Group) @@ -646,6 +944,17 @@ class PermissionDeniedBackendTest(TestCase): self.assertIs(self.user1.has_perm("auth.test"), False) self.assertIs(self.user1.has_module_perms("auth"), False) + @modify_settings(AUTHENTICATION_BACKENDS={"prepend": backend}) + async def test_ahas_perm_denied(self): + content_type = await sync_to_async(ContentType.objects.get_for_model)(Group) + perm = await Permission.objects.acreate( + name="test", content_type=content_type, codename="test" + ) + await self.user1.user_permissions.aadd(perm) + + self.assertIs(await self.user1.ahas_perm("auth.test"), False) + self.assertIs(await self.user1.ahas_module_perms("auth"), False) + @modify_settings(AUTHENTICATION_BACKENDS={"append": backend}) def test_has_perm(self): content_type = ContentType.objects.get_for_model(Group) @@ -657,6 +966,17 @@ class PermissionDeniedBackendTest(TestCase): self.assertIs(self.user1.has_perm("auth.test"), True) self.assertIs(self.user1.has_module_perms("auth"), True) + @modify_settings(AUTHENTICATION_BACKENDS={"append": backend}) + async def test_ahas_perm(self): + content_type = await sync_to_async(ContentType.objects.get_for_model)(Group) + perm = await Permission.objects.acreate( + name="test", content_type=content_type, codename="test" + ) + await self.user1.user_permissions.aadd(perm) + + self.assertIs(await self.user1.ahas_perm("auth.test"), True) + self.assertIs(await self.user1.ahas_module_perms("auth"), True) + class NewModelBackend(ModelBackend): pass @@ -715,6 +1035,10 @@ class TypeErrorBackend: def authenticate(self, request, username=None, password=None): raise TypeError + @sensitive_variables("password") + async def aauthenticate(self, request, username=None, password=None): + raise TypeError + class SkippedBackend: def authenticate(self): diff --git a/tests/auth_tests/test_basic.py b/tests/auth_tests/test_basic.py index d7a7750b54..8d54e187fc 100644 --- a/tests/auth_tests/test_basic.py +++ b/tests/auth_tests/test_basic.py @@ -1,5 +1,3 @@ -from asgiref.sync import sync_to_async - from django.conf import settings from django.contrib.auth import aget_user, get_user, get_user_model from django.contrib.auth.models import AnonymousUser, User @@ -44,6 +42,12 @@ class BasicTestCase(TestCase): u2 = User.objects.create_user("testuser2", "test2@example.com") self.assertFalse(u2.has_usable_password()) + async def test_acreate(self): + u = await User.objects.acreate_user("testuser", "test@example.com", "testpw") + self.assertTrue(u.has_usable_password()) + self.assertFalse(await u.acheck_password("bad")) + self.assertTrue(await u.acheck_password("testpw")) + def test_unicode_username(self): User.objects.create_user("jörg") User.objects.create_user("Григорий") @@ -73,6 +77,15 @@ class BasicTestCase(TestCase): self.assertTrue(super.is_active) self.assertTrue(super.is_staff) + async def test_asuperuser(self): + "Check the creation and properties of a superuser" + super = await User.objects.acreate_superuser( + "super", "super@example.com", "super" + ) + self.assertTrue(super.is_superuser) + self.assertTrue(super.is_active) + self.assertTrue(super.is_staff) + def test_superuser_no_email_or_password(self): cases = [ {}, @@ -171,13 +184,25 @@ class TestGetUser(TestCase): self.assertIsInstance(user, User) self.assertEqual(user.username, created_user.username) - async def test_aget_user(self): - created_user = await sync_to_async(User.objects.create_user)( + async def test_aget_user_fallback_secret(self): + created_user = await User.objects.acreate_user( "testuser", "test@example.com", "testpw" ) await self.client.alogin(username="testuser", password="testpw") request = HttpRequest() request.session = await self.client.asession() - user = await aget_user(request) - self.assertIsInstance(user, User) - self.assertEqual(user.username, created_user.username) + prev_session_key = request.session.session_key + with override_settings( + SECRET_KEY="newsecret", + SECRET_KEY_FALLBACKS=[settings.SECRET_KEY], + ): + user = await aget_user(request) + self.assertIsInstance(user, User) + self.assertEqual(user.username, created_user.username) + self.assertNotEqual(request.session.session_key, prev_session_key) + # Remove the fallback secret. + # The session hash should be updated using the current secret. + with override_settings(SECRET_KEY="newsecret"): + user = await aget_user(request) + self.assertIsInstance(user, User) + self.assertEqual(user.username, created_user.username) diff --git a/tests/auth_tests/test_decorators.py b/tests/auth_tests/test_decorators.py index e585b28bd5..fa2672beb4 100644 --- a/tests/auth_tests/test_decorators.py +++ b/tests/auth_tests/test_decorators.py @@ -1,7 +1,5 @@ from asyncio import iscoroutinefunction -from asgiref.sync import sync_to_async - from django.conf import settings from django.contrib.auth import models from django.contrib.auth.decorators import ( @@ -374,7 +372,7 @@ class UserPassesTestDecoratorTest(TestCase): def test_decorator_async_test_func(self): async def async_test_func(user): - return await sync_to_async(user.has_perms)(["auth_tests.add_customuser"]) + return await user.ahas_perms(["auth_tests.add_customuser"]) @user_passes_test(async_test_func) def sync_view(request): @@ -410,7 +408,7 @@ class UserPassesTestDecoratorTest(TestCase): async def test_decorator_async_view_async_test_func(self): async def async_test_func(user): - return await sync_to_async(user.has_perms)(["auth_tests.add_customuser"]) + return await user.ahas_perms(["auth_tests.add_customuser"]) @user_passes_test(async_test_func) async def async_view(request): diff --git a/tests/auth_tests/test_models.py b/tests/auth_tests/test_models.py index 983424843c..a3e7a3205b 100644 --- a/tests/auth_tests/test_models.py +++ b/tests/auth_tests/test_models.py @@ -1,7 +1,5 @@ from unittest import mock -from asgiref.sync import sync_to_async - from django.conf.global_settings import PASSWORD_HASHERS from django.contrib.auth import get_user_model from django.contrib.auth.backends import ModelBackend @@ -30,10 +28,19 @@ class NaturalKeysTestCase(TestCase): self.assertEqual(User.objects.get_by_natural_key("staff"), staff_user) self.assertEqual(staff_user.natural_key(), ("staff",)) + async def test_auser_natural_key(self): + staff_user = await User.objects.acreate_user(username="staff") + self.assertEqual(await User.objects.aget_by_natural_key("staff"), staff_user) + self.assertEqual(staff_user.natural_key(), ("staff",)) + def test_group_natural_key(self): users_group = Group.objects.create(name="users") self.assertEqual(Group.objects.get_by_natural_key("users"), users_group) + async def test_agroup_natural_key(self): + users_group = await Group.objects.acreate(name="users") + self.assertEqual(await Group.objects.aget_by_natural_key("users"), users_group) + class LoadDataWithoutNaturalKeysTestCase(TestCase): fixtures = ["regular.json"] @@ -157,6 +164,17 @@ class UserManagerTestCase(TransactionTestCase): is_superuser=False, ) + async def test_acreate_super_user_raises_error_on_false_is_superuser(self): + with self.assertRaisesMessage( + ValueError, "Superuser must have is_superuser=True." + ): + await User.objects.acreate_superuser( + username="test", + email="test@test.com", + password="test", + is_superuser=False, + ) + def test_create_superuser_raises_error_on_false_is_staff(self): with self.assertRaisesMessage(ValueError, "Superuser must have is_staff=True."): User.objects.create_superuser( @@ -166,6 +184,15 @@ class UserManagerTestCase(TransactionTestCase): is_staff=False, ) + async def test_acreate_superuser_raises_error_on_false_is_staff(self): + with self.assertRaisesMessage(ValueError, "Superuser must have is_staff=True."): + await User.objects.acreate_superuser( + username="test", + email="test@test.com", + password="test", + is_staff=False, + ) + def test_runpython_manager_methods(self): def forwards(apps, schema_editor): UserModel = apps.get_model("auth", "User") @@ -301,9 +328,7 @@ class AbstractUserTestCase(TestCase): @override_settings(PASSWORD_HASHERS=PASSWORD_HASHERS) async def test_acheck_password_upgrade(self): - user = await sync_to_async(User.objects.create_user)( - username="user", password="foo" - ) + user = await User.objects.acreate_user(username="user", password="foo") initial_password = user.password self.assertIs(await user.acheck_password("foo"), True) hasher = get_hasher("default") @@ -557,6 +582,12 @@ class AnonymousUserTests(SimpleTestCase): self.assertEqual(self.user.get_user_permissions(), set()) self.assertEqual(self.user.get_group_permissions(), set()) + async def test_properties_async_versions(self): + self.assertEqual(await self.user.groups.acount(), 0) + self.assertEqual(await self.user.user_permissions.acount(), 0) + self.assertEqual(await self.user.aget_user_permissions(), set()) + self.assertEqual(await self.user.aget_group_permissions(), set()) + def test_str(self): self.assertEqual(str(self.user), "AnonymousUser") diff --git a/tests/auth_tests/test_remote_user.py b/tests/auth_tests/test_remote_user.py index d3cf4b9da5..85de931c1a 100644 --- a/tests/auth_tests/test_remote_user.py +++ b/tests/auth_tests/test_remote_user.py @@ -1,12 +1,18 @@ from datetime import datetime, timezone from django.conf import settings -from django.contrib.auth import authenticate +from django.contrib.auth import aauthenticate, authenticate from django.contrib.auth.backends import RemoteUserBackend from django.contrib.auth.middleware import RemoteUserMiddleware from django.contrib.auth.models import User from django.middleware.csrf import _get_new_csrf_string, _mask_cipher_secret -from django.test import Client, TestCase, modify_settings, override_settings +from django.test import ( + AsyncClient, + Client, + TestCase, + modify_settings, + override_settings, +) @override_settings(ROOT_URLCONF="auth_tests.urls") @@ -30,6 +36,11 @@ class RemoteUserTest(TestCase): ) super().setUpClass() + def test_passing_explicit_none(self): + msg = "get_response must be provided." + with self.assertRaisesMessage(ValueError, msg): + RemoteUserMiddleware(None) + def test_no_remote_user(self): """Users are not created when remote user is not specified.""" num_users = User.objects.count() @@ -46,6 +57,18 @@ class RemoteUserTest(TestCase): self.assertTrue(response.context["user"].is_anonymous) self.assertEqual(User.objects.count(), num_users) + async def test_no_remote_user_async(self): + """See test_no_remote_user.""" + num_users = await User.objects.acount() + + response = await self.async_client.get("/remote_user/") + self.assertTrue(response.context["user"].is_anonymous) + self.assertEqual(await User.objects.acount(), num_users) + + response = await self.async_client.get("/remote_user/", **{self.header: ""}) + self.assertTrue(response.context["user"].is_anonymous) + self.assertEqual(await User.objects.acount(), num_users) + def test_csrf_validation_passes_after_process_request_login(self): """ CSRF check must access the CSRF token from the session or cookie, @@ -75,6 +98,31 @@ class RemoteUserTest(TestCase): response = csrf_client.post("/remote_user/", data, **headers) self.assertEqual(response.status_code, 200) + async def test_csrf_validation_passes_after_process_request_login_async(self): + """See test_csrf_validation_passes_after_process_request_login.""" + csrf_client = AsyncClient(enforce_csrf_checks=True) + csrf_secret = _get_new_csrf_string() + csrf_token = _mask_cipher_secret(csrf_secret) + csrf_token_form = _mask_cipher_secret(csrf_secret) + headers = {self.header: "fakeuser"} + data = {"csrfmiddlewaretoken": csrf_token_form} + + # Verify that CSRF is configured for the view + csrf_client.cookies.load({settings.CSRF_COOKIE_NAME: csrf_token}) + response = await csrf_client.post("/remote_user/", **headers) + self.assertEqual(response.status_code, 403) + self.assertIn(b"CSRF verification failed.", response.content) + + # This request will call django.contrib.auth.alogin() which will call + # django.middleware.csrf.rotate_token() thus changing the value of + # request.META['CSRF_COOKIE'] from the user submitted value set by + # CsrfViewMiddleware.process_request() to the new csrftoken value set + # by rotate_token(). Csrf validation should still pass when the view is + # later processed by CsrfViewMiddleware.process_view() + csrf_client.cookies.load({settings.CSRF_COOKIE_NAME: csrf_token}) + response = await csrf_client.post("/remote_user/", data, **headers) + self.assertEqual(response.status_code, 200) + def test_unknown_user(self): """ Tests the case where the username passed in the header does not exist @@ -90,6 +138,22 @@ class RemoteUserTest(TestCase): response = self.client.get("/remote_user/", **{self.header: "newuser"}) self.assertEqual(User.objects.count(), num_users + 1) + async def test_unknown_user_async(self): + """See test_unknown_user.""" + num_users = await User.objects.acount() + response = await self.async_client.get( + "/remote_user/", **{self.header: "newuser"} + ) + self.assertEqual(response.context["user"].username, "newuser") + self.assertEqual(await User.objects.acount(), num_users + 1) + await User.objects.aget(username="newuser") + + # Another request with same user should not create any new users. + response = await self.async_client.get( + "/remote_user/", **{self.header: "newuser"} + ) + self.assertEqual(await User.objects.acount(), num_users + 1) + def test_known_user(self): """ Tests the case where the username passed in the header is a valid User. @@ -106,6 +170,24 @@ class RemoteUserTest(TestCase): self.assertEqual(response.context["user"].username, "knownuser2") self.assertEqual(User.objects.count(), num_users) + async def test_known_user_async(self): + """See test_known_user.""" + await User.objects.acreate(username="knownuser") + await User.objects.acreate(username="knownuser2") + num_users = await User.objects.acount() + response = await self.async_client.get( + "/remote_user/", **{self.header: self.known_user} + ) + self.assertEqual(response.context["user"].username, "knownuser") + self.assertEqual(await User.objects.acount(), num_users) + # A different user passed in the headers causes the new user + # to be logged in. + response = await self.async_client.get( + "/remote_user/", **{self.header: self.known_user2} + ) + self.assertEqual(response.context["user"].username, "knownuser2") + self.assertEqual(await User.objects.acount(), num_users) + def test_last_login(self): """ A user's last_login is set the first time they make a @@ -128,6 +210,29 @@ class RemoteUserTest(TestCase): response = self.client.get("/remote_user/", **{self.header: self.known_user}) self.assertEqual(default_login, response.context["user"].last_login) + async def test_last_login_async(self): + """See test_last_login.""" + user = await User.objects.acreate(username="knownuser") + # Set last_login to something so we can determine if it changes. + default_login = datetime(2000, 1, 1) + if settings.USE_TZ: + default_login = default_login.replace(tzinfo=timezone.utc) + user.last_login = default_login + await user.asave() + + response = await self.async_client.get( + "/remote_user/", **{self.header: self.known_user} + ) + self.assertNotEqual(default_login, response.context["user"].last_login) + + user = await User.objects.aget(username="knownuser") + user.last_login = default_login + await user.asave() + response = await self.async_client.get( + "/remote_user/", **{self.header: self.known_user} + ) + self.assertEqual(default_login, response.context["user"].last_login) + def test_header_disappears(self): """ A logged in user is logged out automatically when @@ -148,6 +253,25 @@ class RemoteUserTest(TestCase): response = self.client.get("/remote_user/") self.assertEqual(response.context["user"].username, "modeluser") + async def test_header_disappears_async(self): + """See test_header_disappears.""" + await User.objects.acreate(username="knownuser") + # Known user authenticates + response = await self.async_client.get( + "/remote_user/", **{self.header: self.known_user} + ) + self.assertEqual(response.context["user"].username, "knownuser") + # During the session, the REMOTE_USER header disappears. Should trigger logout. + response = await self.async_client.get("/remote_user/") + self.assertTrue(response.context["user"].is_anonymous) + # verify the remoteuser middleware will not remove a user + # authenticated via another backend + await User.objects.acreate_user(username="modeluser", password="foo") + await self.async_client.alogin(username="modeluser", password="foo") + await aauthenticate(username="modeluser", password="foo") + response = await self.async_client.get("/remote_user/") + self.assertEqual(response.context["user"].username, "modeluser") + def test_user_switch_forces_new_login(self): """ If the username in the header changes between requests @@ -164,11 +288,35 @@ class RemoteUserTest(TestCase): # In backends that do not create new users, it is '' (anonymous user) self.assertNotEqual(response.context["user"].username, "knownuser") + async def test_user_switch_forces_new_login_async(self): + """See test_user_switch_forces_new_login.""" + await User.objects.acreate(username="knownuser") + # Known user authenticates + response = await self.async_client.get( + "/remote_user/", **{self.header: self.known_user} + ) + self.assertEqual(response.context["user"].username, "knownuser") + # During the session, the REMOTE_USER changes to a different user. + response = await self.async_client.get( + "/remote_user/", **{self.header: "newnewuser"} + ) + # The current user is not the prior remote_user. + # In backends that create a new user, username is "newnewuser" + # In backends that do not create new users, it is '' (anonymous user) + self.assertNotEqual(response.context["user"].username, "knownuser") + def test_inactive_user(self): User.objects.create(username="knownuser", is_active=False) response = self.client.get("/remote_user/", **{self.header: "knownuser"}) self.assertTrue(response.context["user"].is_anonymous) + async def test_inactive_user_async(self): + await User.objects.acreate(username="knownuser", is_active=False) + response = await self.async_client.get( + "/remote_user/", **{self.header: "knownuser"} + ) + self.assertTrue(response.context["user"].is_anonymous) + class RemoteUserNoCreateBackend(RemoteUserBackend): """Backend that doesn't create unknown users.""" @@ -190,6 +338,14 @@ class RemoteUserNoCreateTest(RemoteUserTest): self.assertTrue(response.context["user"].is_anonymous) self.assertEqual(User.objects.count(), num_users) + async def test_unknown_user_async(self): + num_users = await User.objects.acount() + response = await self.async_client.get( + "/remote_user/", **{self.header: "newuser"} + ) + self.assertTrue(response.context["user"].is_anonymous) + self.assertEqual(await User.objects.acount(), num_users) + class AllowAllUsersRemoteUserBackendTest(RemoteUserTest): """Backend that allows inactive users.""" @@ -201,6 +357,13 @@ class AllowAllUsersRemoteUserBackendTest(RemoteUserTest): response = self.client.get("/remote_user/", **{self.header: self.known_user}) self.assertEqual(response.context["user"].username, user.username) + async def test_inactive_user_async(self): + user = await User.objects.acreate(username="knownuser", is_active=False) + response = await self.async_client.get( + "/remote_user/", **{self.header: self.known_user} + ) + self.assertEqual(response.context["user"].username, user.username) + class CustomRemoteUserBackend(RemoteUserBackend): """ @@ -311,3 +474,16 @@ class PersistentRemoteUserTest(RemoteUserTest): response = self.client.get("/remote_user/") self.assertFalse(response.context["user"].is_anonymous) self.assertEqual(response.context["user"].username, "knownuser") + + async def test_header_disappears_async(self): + """See test_header_disappears.""" + await User.objects.acreate(username="knownuser") + # Known user authenticates + response = await self.async_client.get( + "/remote_user/", **{self.header: self.known_user} + ) + self.assertEqual(response.context["user"].username, "knownuser") + # Should stay logged in if the REMOTE_USER header disappears. + response = await self.async_client.get("/remote_user/") + self.assertFalse(response.context["user"].is_anonymous) + self.assertEqual(response.context["user"].username, "knownuser") diff --git a/tests/auth_tests/test_validators.py b/tests/auth_tests/test_validators.py index 506c85c0ae..d7e4968951 100644 --- a/tests/auth_tests/test_validators.py +++ b/tests/auth_tests/test_validators.py @@ -144,6 +144,20 @@ class MinimumLengthValidatorTest(SimpleTestCase): "Your password must contain at least 8 characters.", ) + def test_custom_error(self): + class CustomMinimumLengthValidator(MinimumLengthValidator): + def get_error_message(self): + return "Your password must be %d characters long" % self.min_length + + expected_error = "Your password must be %d characters long" + + with self.assertRaisesMessage(ValidationError, expected_error % 8) as cm: + CustomMinimumLengthValidator().validate("1234567") + self.assertEqual(cm.exception.error_list[0].code, "password_too_short") + + with self.assertRaisesMessage(ValidationError, expected_error % 3) as cm: + CustomMinimumLengthValidator(min_length=3).validate("12") + class UserAttributeSimilarityValidatorTest(TestCase): def test_validate(self): @@ -213,6 +227,42 @@ class UserAttributeSimilarityValidatorTest(TestCase): "Your password can’t be too similar to your other personal information.", ) + def test_custom_error(self): + class CustomUserAttributeSimilarityValidator(UserAttributeSimilarityValidator): + def get_error_message(self): + return "The password is too close to the %(verbose_name)s." + + user = User.objects.create_user( + username="testclient", + password="password", + email="testclient@example.com", + first_name="Test", + last_name="Client", + ) + + expected_error = "The password is too close to the %s." + + with self.assertRaisesMessage(ValidationError, expected_error % "username"): + CustomUserAttributeSimilarityValidator().validate("testclient", user=user) + + def test_custom_error_verbose_name_not_used(self): + class CustomUserAttributeSimilarityValidator(UserAttributeSimilarityValidator): + def get_error_message(self): + return "The password is too close to a user attribute." + + user = User.objects.create_user( + username="testclient", + password="password", + email="testclient@example.com", + first_name="Test", + last_name="Client", + ) + + expected_error = "The password is too close to a user attribute." + + with self.assertRaisesMessage(ValidationError, expected_error): + CustomUserAttributeSimilarityValidator().validate("testclient", user=user) + class CommonPasswordValidatorTest(SimpleTestCase): def test_validate(self): @@ -247,6 +297,16 @@ class CommonPasswordValidatorTest(SimpleTestCase): "Your password can’t be a commonly used password.", ) + def test_custom_error(self): + class CustomCommonPasswordValidator(CommonPasswordValidator): + def get_error_message(self): + return "This password has been used too much." + + expected_error = "This password has been used too much." + + with self.assertRaisesMessage(ValidationError, expected_error): + CustomCommonPasswordValidator().validate("godzilla") + class NumericPasswordValidatorTest(SimpleTestCase): def test_validate(self): @@ -264,6 +324,16 @@ class NumericPasswordValidatorTest(SimpleTestCase): "Your password can’t be entirely numeric.", ) + def test_custom_error(self): + class CustomNumericPasswordValidator(NumericPasswordValidator): + def get_error_message(self): + return "This password is all digits." + + expected_error = "This password is all digits." + + with self.assertRaisesMessage(ValidationError, expected_error): + CustomNumericPasswordValidator().validate("42424242") + class UsernameValidatorsTests(SimpleTestCase): def test_unicode_validator(self): diff --git a/tests/auth_tests/test_views.py b/tests/auth_tests/test_views.py index 97d0448ab1..98fdfe79b7 100644 --- a/tests/auth_tests/test_views.py +++ b/tests/auth_tests/test_views.py @@ -1521,7 +1521,7 @@ class ChangelistTests(MessagesTestMixin, AuthViewsTestCase): # Test the link inside password field help_text. rel_link = re.search( r'Reset password', - response.content.decode(), + response.text, )[1] self.assertEqual(urljoin(user_change_url, rel_link), password_change_url) @@ -1617,7 +1617,7 @@ class ChangelistTests(MessagesTestMixin, AuthViewsTestCase): # Test the link inside password field help_text. rel_link = re.search( r'Set password', - response.content.decode(), + response.text, )[1] self.assertEqual(urljoin(user_change_url, rel_link), password_change_url) diff --git a/tests/check_framework/custom_commands_app/management/commands/makemigrations.py b/tests/check_framework/custom_commands_app/management/commands/makemigrations.py new file mode 100644 index 0000000000..a6494cba4c --- /dev/null +++ b/tests/check_framework/custom_commands_app/management/commands/makemigrations.py @@ -0,0 +1,7 @@ +from django.core.management.commands.makemigrations import ( + Command as MakeMigrationsCommand, +) + + +class Command(MakeMigrationsCommand): + autodetector = int diff --git a/tests/check_framework/test_commands.py b/tests/check_framework/test_commands.py new file mode 100644 index 0000000000..a51db77402 --- /dev/null +++ b/tests/check_framework/test_commands.py @@ -0,0 +1,25 @@ +from django.core import checks +from django.core.checks import Error +from django.test import SimpleTestCase +from django.test.utils import isolate_apps, override_settings, override_system_checks + + +@isolate_apps("check_framework.custom_commands_app", attr_name="apps") +@override_settings(INSTALLED_APPS=["check_framework.custom_commands_app"]) +@override_system_checks([checks.commands.migrate_and_makemigrations_autodetector]) +class CommandCheckTests(SimpleTestCase): + def test_migrate_and_makemigrations_autodetector_different(self): + expected_error = Error( + "The migrate and makemigrations commands must have the same " + "autodetector.", + hint=( + "makemigrations.Command.autodetector is int, but " + "migrate.Command.autodetector is MigrationAutodetector." + ), + id="commands.E001", + ) + + self.assertEqual( + checks.run_checks(app_configs=self.apps.get_app_configs()), + [expected_error], + ) diff --git a/tests/constraints/models.py b/tests/constraints/models.py index 829f671cdd..95a29ffa4d 100644 --- a/tests/constraints/models.py +++ b/tests/constraints/models.py @@ -72,15 +72,13 @@ class GeneratedFieldVirtualProduct(models.Model): class UniqueConstraintProduct(models.Model): name = models.CharField(max_length=255) color = models.CharField(max_length=32, null=True) + age = models.IntegerField(null=True) class Meta: constraints = [ models.UniqueConstraint( fields=["name", "color"], name="name_color_uniq", - # Custom message and error code are ignored. - violation_error_code="custom_code", - violation_error_message="Custom message", ) ] diff --git a/tests/constraints/tests.py b/tests/constraints/tests.py index e1c431956f..9047710098 100644 --- a/tests/constraints/tests.py +++ b/tests/constraints/tests.py @@ -953,6 +953,41 @@ class UniqueConstraintTests(TestCase): ChildUniqueConstraintProduct(name=self.p1.name, color=self.p1.color), ) + def test_validate_unique_custom_code_and_message(self): + product = UniqueConstraintProduct.objects.create( + name="test", color="red", age=42 + ) + code = "custom_code" + message = "Custom message" + multiple_fields_constraint = models.UniqueConstraint( + fields=["color", "age"], + name="color_age_uniq", + violation_error_code=code, + violation_error_message=message, + ) + single_field_constraint = models.UniqueConstraint( + fields=["color"], + name="color_uniq", + violation_error_code=code, + violation_error_message=message, + ) + + with self.assertRaisesMessage(ValidationError, message) as cm: + multiple_fields_constraint.validate( + UniqueConstraintProduct, + UniqueConstraintProduct( + name="new-test", color=product.color, age=product.age + ), + ) + self.assertEqual(cm.exception.code, code) + + with self.assertRaisesMessage(ValidationError, message) as cm: + single_field_constraint.validate( + UniqueConstraintProduct, + UniqueConstraintProduct(name="new-test", color=product.color), + ) + self.assertEqual(cm.exception.code, code) + @skipUnlessDBFeature("supports_table_check_constraints") def test_validate_fields_unattached(self): Product.objects.create(price=42) diff --git a/tests/csrf_tests/tests.py b/tests/csrf_tests/tests.py index b736276534..956cff11d9 100644 --- a/tests/csrf_tests/tests.py +++ b/tests/csrf_tests/tests.py @@ -1481,9 +1481,11 @@ class CsrfInErrorHandlingViewsTests(CsrfFunctionTestMixin, SimpleTestCase): response = self.client.get("/does not exist/") # The error handler returns status code 599. self.assertEqual(response.status_code, 599) - token1 = response.content.decode("ascii") + response.charset = "ascii" + token1 = response.text response = self.client.get("/does not exist/") self.assertEqual(response.status_code, 599) - token2 = response.content.decode("ascii") + response.charset = "ascii" + token2 = response.text secret2 = _unmask_cipher_token(token2) self.assertMaskedSecretCorrect(token1, secret2) diff --git a/tests/deprecation/test_middleware_mixin.py b/tests/deprecation/test_middleware_mixin.py index f4eafc14e3..7e86832e2b 100644 --- a/tests/deprecation/test_middleware_mixin.py +++ b/tests/deprecation/test_middleware_mixin.py @@ -6,7 +6,6 @@ from django.contrib.admindocs.middleware import XViewMiddleware from django.contrib.auth.middleware import ( AuthenticationMiddleware, LoginRequiredMiddleware, - RemoteUserMiddleware, ) from django.contrib.flatpages.middleware import FlatpageFallbackMiddleware from django.contrib.messages.middleware import MessageMiddleware @@ -48,7 +47,6 @@ class MiddlewareMixinTests(SimpleTestCase): LocaleMiddleware, MessageMiddleware, RedirectFallbackMiddleware, - RemoteUserMiddleware, SecurityMiddleware, SessionMiddleware, UpdateCacheMiddleware, diff --git a/tests/foreign_object/test_tuple_lookups.py b/tests/foreign_object/test_tuple_lookups.py index e2561676f3..06182d3bb5 100644 --- a/tests/foreign_object/test_tuple_lookups.py +++ b/tests/foreign_object/test_tuple_lookups.py @@ -1,3 +1,4 @@ +import itertools import unittest from django.db import NotSupportedError, connection @@ -129,6 +130,37 @@ class TupleLookupsTests(TestCase): (self.contact_1, self.contact_2, self.contact_5), ) + def test_tuple_in_rhs_must_be_collection_of_tuples_or_lists(self): + test_cases = ( + (1, 2, 3), + ((1, 2), (3, 4), None), + ) + + for rhs in test_cases: + with self.subTest(rhs=rhs): + with self.assertRaisesMessage( + ValueError, + "'in' lookup of ('customer_code', 'company_code') " + "must be a collection of tuples or lists", + ): + TupleIn((F("customer_code"), F("company_code")), rhs) + + def test_tuple_in_rhs_must_have_2_elements_each(self): + test_cases = ( + ((),), + ((1,),), + ((1, 2, 3),), + ) + + for rhs in test_cases: + with self.subTest(rhs=rhs): + with self.assertRaisesMessage( + ValueError, + "'in' lookup of ('customer_code', 'company_code') " + "must have 2 elements each", + ): + TupleIn((F("customer_code"), F("company_code")), rhs) + def test_lt(self): c1, c2, c3, c4, c5, c6 = ( self.contact_1, @@ -358,8 +390,8 @@ class TupleLookupsTests(TestCase): ) def test_lookup_errors(self): - m_2_elements = "'%s' lookup of 'customer' field must have 2 elements" - m_2_elements_each = "'in' lookup of 'customer' field must have 2 elements each" + m_2_elements = "'%s' lookup of 'customer' must have 2 elements" + m_2_elements_each = "'in' lookup of 'customer' must have 2 elements each" test_cases = ( ({"customer": 1}, m_2_elements % "exact"), ({"customer": (1, 2, 3)}, m_2_elements % "exact"), @@ -381,3 +413,77 @@ class TupleLookupsTests(TestCase): self.assertRaisesMessage(ValueError, message), ): Contact.objects.get(**kwargs) + + def test_tuple_lookup_names(self): + test_cases = ( + (TupleExact, "exact"), + (TupleGreaterThan, "gt"), + (TupleGreaterThanOrEqual, "gte"), + (TupleLessThan, "lt"), + (TupleLessThanOrEqual, "lte"), + (TupleIn, "in"), + (TupleIsNull, "isnull"), + ) + + for lookup_class, lookup_name in test_cases: + with self.subTest(lookup_name): + self.assertEqual(lookup_class.lookup_name, lookup_name) + + def test_tuple_lookup_rhs_must_be_tuple_or_list(self): + test_cases = itertools.product( + ( + TupleExact, + TupleGreaterThan, + TupleGreaterThanOrEqual, + TupleLessThan, + TupleLessThanOrEqual, + TupleIn, + ), + ( + 0, + 1, + None, + True, + False, + {"foo": "bar"}, + ), + ) + + for lookup_cls, rhs in test_cases: + lookup_name = lookup_cls.lookup_name + with self.subTest(lookup_name=lookup_name, rhs=rhs): + with self.assertRaisesMessage( + ValueError, + f"'{lookup_name}' lookup of ('customer_code', 'company_code') " + "must be a tuple or a list", + ): + lookup_cls((F("customer_code"), F("company_code")), rhs) + + def test_tuple_lookup_rhs_must_have_2_elements(self): + test_cases = itertools.product( + ( + TupleExact, + TupleGreaterThan, + TupleGreaterThanOrEqual, + TupleLessThan, + TupleLessThanOrEqual, + ), + ( + [], + [1], + [1, 2, 3], + (), + (1,), + (1, 2, 3), + ), + ) + + for lookup_cls, rhs in test_cases: + lookup_name = lookup_cls.lookup_name + with self.subTest(lookup_name=lookup_name, rhs=rhs): + with self.assertRaisesMessage( + ValueError, + f"'{lookup_name}' lookup of ('customer_code', 'company_code') " + "must have 2 elements", + ): + lookup_cls((F("customer_code"), F("company_code")), rhs) diff --git a/tests/httpwrappers/tests.py b/tests/httpwrappers/tests.py index 2197c6f7ea..3774ff2d67 100644 --- a/tests/httpwrappers/tests.py +++ b/tests/httpwrappers/tests.py @@ -530,6 +530,22 @@ class HttpResponseTests(SimpleTestCase): headers={"Content-Type": "text/csv"}, ) + def test_text_updates_when_content_updates(self): + response = HttpResponse("Hello, world!") + self.assertEqual(response.text, "Hello, world!") + response.content = "Updated content" + self.assertEqual(response.text, "Updated content") + + def test_text_charset(self): + for content_type, content in [ + (None, b"Ol\xc3\xa1 Mundo"), + ("text/plain; charset=utf-8", b"Ol\xc3\xa1 Mundo"), + ("text/plain; charset=iso-8859-1", b"Ol\xe1 Mundo"), + ]: + with self.subTest(content_type=content_type): + response = HttpResponse(content, content_type=content_type) + self.assertEqual(response.text, "Olá Mundo") + class HttpResponseSubclassesTests(SimpleTestCase): def test_redirect(self): @@ -614,7 +630,7 @@ class JsonResponseTests(SimpleTestCase): def test_json_response_non_ascii(self): data = {"key": "łóżko"} response = JsonResponse(data) - self.assertEqual(json.loads(response.content.decode()), data) + self.assertEqual(json.loads(response.text), data) def test_json_response_raises_type_error_with_default_setting(self): with self.assertRaisesMessage( @@ -626,16 +642,16 @@ class JsonResponseTests(SimpleTestCase): def test_json_response_text(self): response = JsonResponse("foobar", safe=False) - self.assertEqual(json.loads(response.content.decode()), "foobar") + self.assertEqual(json.loads(response.text), "foobar") def test_json_response_list(self): response = JsonResponse(["foo", "bar"], safe=False) - self.assertEqual(json.loads(response.content.decode()), ["foo", "bar"]) + self.assertEqual(json.loads(response.text), ["foo", "bar"]) def test_json_response_uuid(self): u = uuid.uuid4() response = JsonResponse(u, safe=False) - self.assertEqual(json.loads(response.content.decode()), str(u)) + self.assertEqual(json.loads(response.text), str(u)) def test_json_response_custom_encoder(self): class CustomDjangoJSONEncoder(DjangoJSONEncoder): @@ -643,11 +659,11 @@ class JsonResponseTests(SimpleTestCase): return json.dumps({"foo": "bar"}) response = JsonResponse({}, encoder=CustomDjangoJSONEncoder) - self.assertEqual(json.loads(response.content.decode()), {"foo": "bar"}) + self.assertEqual(json.loads(response.text), {"foo": "bar"}) def test_json_response_passing_arguments_to_json_dumps(self): response = JsonResponse({"foo": "bar"}, json_dumps_params={"indent": 2}) - self.assertEqual(response.content.decode(), '{\n "foo": "bar"\n}') + self.assertEqual(response.text, '{\n "foo": "bar"\n}') class StreamingHttpResponseTests(SimpleTestCase): @@ -756,6 +772,13 @@ class StreamingHttpResponseTests(SimpleTestCase): with self.assertWarnsMessage(Warning, msg): self.assertEqual(b"hello", await anext(aiter(r))) + def test_text_attribute_error(self): + r = StreamingHttpResponse(iter(["hello", "world"])) + msg = "This %s instance has no `text` attribute." % r.__class__.__name__ + + with self.assertRaisesMessage(AttributeError, msg): + r.text + class FileCloseTests(SimpleTestCase): def setUp(self): diff --git a/tests/migrations/test_commands.py b/tests/migrations/test_commands.py index cab2906ed1..724c88a28f 100644 --- a/tests/migrations/test_commands.py +++ b/tests/migrations/test_commands.py @@ -9,6 +9,10 @@ from unittest import mock from django.apps import apps from django.core.management import CommandError, call_command +from django.core.management.commands.makemigrations import ( + Command as MakeMigrationsCommand, +) +from django.core.management.commands.migrate import Command as MigrateCommand from django.db import ( ConnectionHandler, DatabaseError, @@ -19,10 +23,11 @@ from django.db import ( ) from django.db.backends.base.schema import BaseDatabaseSchemaEditor from django.db.backends.utils import truncate_name +from django.db.migrations.autodetector import MigrationAutodetector from django.db.migrations.exceptions import InconsistentMigrationHistory from django.db.migrations.recorder import MigrationRecorder from django.test import TestCase, override_settings, skipUnlessDBFeature -from django.test.utils import captured_stdout, extend_sys_path +from django.test.utils import captured_stdout, extend_sys_path, isolate_apps from django.utils import timezone from django.utils.version import get_docs_version @@ -3296,3 +3301,59 @@ class OptimizeMigrationTests(MigrationTestBase): msg = "Cannot find a migration matching 'nonexistent' from app 'migrations'." with self.assertRaisesMessage(CommandError, msg): call_command("optimizemigration", "migrations", "nonexistent") + + +class CustomMigrationCommandTests(MigrationTestBase): + @override_settings( + MIGRATION_MODULES={"migrations": "migrations.test_migrations"}, + INSTALLED_APPS=["migrations.migrations_test_apps.migrated_app"], + ) + @isolate_apps("migrations.migrations_test_apps.migrated_app") + def test_makemigrations_custom_autodetector(self): + class CustomAutodetector(MigrationAutodetector): + def changes(self, *args, **kwargs): + return [] + + class CustomMakeMigrationsCommand(MakeMigrationsCommand): + autodetector = CustomAutodetector + + class NewModel(models.Model): + class Meta: + app_label = "migrated_app" + + out = io.StringIO() + command = CustomMakeMigrationsCommand(stdout=out) + call_command(command, "migrated_app", stdout=out) + self.assertIn("No changes detected", out.getvalue()) + + @override_settings(INSTALLED_APPS=["migrations.migrations_test_apps.migrated_app"]) + @isolate_apps("migrations.migrations_test_apps.migrated_app") + def test_migrate_custom_autodetector(self): + class CustomAutodetector(MigrationAutodetector): + def changes(self, *args, **kwargs): + return [] + + class CustomMigrateCommand(MigrateCommand): + autodetector = CustomAutodetector + + class NewModel(models.Model): + class Meta: + app_label = "migrated_app" + + out = io.StringIO() + command = CustomMigrateCommand(stdout=out) + + out = io.StringIO() + try: + call_command(command, verbosity=0) + call_command(command, stdout=out, no_color=True) + command_stdout = out.getvalue().lower() + self.assertEqual( + "operations to perform:\n" + " apply all migrations: migrated_app\n" + "running migrations:\n" + " no migrations to apply.\n", + command_stdout, + ) + finally: + call_command(command, "migrated_app", "zero", verbosity=0) diff --git a/tests/postgres_tests/test_array.py b/tests/postgres_tests/test_array.py index ff0c4aabb1..ea7807687e 100644 --- a/tests/postgres_tests/test_array.py +++ b/tests/postgres_tests/test_array.py @@ -1339,6 +1339,22 @@ class TestSplitFormField(PostgreSQLSimpleTestCase): ], ) + def test_invalid_char_length_with_remove_trailing_nulls(self): + field = SplitArrayField( + forms.CharField(max_length=2, required=False), + size=3, + remove_trailing_nulls=True, + ) + with self.assertRaises(exceptions.ValidationError) as cm: + field.clean(["abc", "", ""]) + self.assertEqual( + cm.exception.messages, + [ + "Item 1 in the array did not validate: Ensure this value has at most 2 " + "characters (it has 3).", + ], + ) + def test_splitarraywidget_value_omitted_from_data(self): class Form(forms.ModelForm): field = SplitArrayField(forms.IntegerField(), required=False, size=2) diff --git a/tests/postgres_tests/test_operations.py b/tests/postgres_tests/test_operations.py index f344d4ae74..322f38148b 100644 --- a/tests/postgres_tests/test_operations.py +++ b/tests/postgres_tests/test_operations.py @@ -318,7 +318,7 @@ class CreateExtensionTests(PostgreSQLTestCase): @unittest.skipUnless(connection.vendor == "postgresql", "PostgreSQL specific tests.") -class CreateCollationTests(PostgreSQLTestCase): +class CreateCollationTests(OptimizerTestBase, PostgreSQLTestCase): app_label = "test_allow_create_collation" @override_settings(DATABASE_ROUTERS=[NoMigrationRouter()]) @@ -459,6 +459,24 @@ class CreateCollationTests(PostgreSQLTestCase): "),", ) + def test_reduce_create_remove(self): + self.assertOptimizesTo( + [ + CreateCollation( + "sample_collation", + "und-u-ks-level2", + provider="icu", + deterministic=False, + ), + RemoveCollation( + "sample_collation", + # Different locale + "de-u-ks-level1", + ), + ], + [], + ) + @unittest.skipUnless(connection.vendor == "postgresql", "PostgreSQL specific tests.") class RemoveCollationTests(PostgreSQLTestCase): diff --git a/tests/queries/test_qs_combinators.py b/tests/queries/test_qs_combinators.py index ad1017c8af..2f6e93cde8 100644 --- a/tests/queries/test_qs_combinators.py +++ b/tests/queries/test_qs_combinators.py @@ -14,7 +14,16 @@ from django.db.models.functions import Mod from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature from django.test.utils import CaptureQueriesContext -from .models import Author, Celebrity, ExtraInfo, Number, ReservedName +from .models import ( + Annotation, + Author, + Celebrity, + ExtraInfo, + Note, + Number, + ReservedName, + Tag, +) @skipUnlessDBFeature("supports_select_union") @@ -450,6 +459,27 @@ class QuerySetSetOperationTests(TestCase): [8, 1], ) + @skipUnlessDBFeature("supports_select_intersection") + def test_intersection_in_nested_subquery(self): + tag = Tag.objects.create(name="tag") + note = Note.objects.create(tag=tag) + annotation = Annotation.objects.create(tag=tag) + tags = Tag.objects.order_by() + tags = tags.filter(id=OuterRef("tag_id")).intersection( + tags.filter(id=OuterRef(OuterRef("tag_id"))) + ) + qs = Note.objects.filter( + Exists( + Annotation.objects.filter( + Exists(tags), + notes__in=OuterRef("pk"), + ) + ) + ) + self.assertIsNone(qs.first()) + annotation.notes.add(note) + self.assertEqual(qs.first(), note) + def test_union_in_subquery_related_outerref(self): e1 = ExtraInfo.objects.create(value=7, info="e3") e2 = ExtraInfo.objects.create(value=5, info="e2") diff --git a/tests/runtests.py b/tests/runtests.py index c5bb637d33..516da84768 100755 --- a/tests/runtests.py +++ b/tests/runtests.py @@ -221,7 +221,6 @@ def setup_collect_tests(start_at, start_after, test_labels=None): "APP_DIRS": True, "OPTIONS": { "context_processors": [ - "django.template.context_processors.debug", "django.template.context_processors.request", "django.contrib.auth.context_processors.auth", "django.contrib.messages.context_processors.messages", diff --git a/tests/serializers/tests.py b/tests/serializers/tests.py index 6ca0c15e04..420246db0b 100644 --- a/tests/serializers/tests.py +++ b/tests/serializers/tests.py @@ -155,7 +155,7 @@ class SerializersTestBase: if isinstance(stream, StringIO): self.assertEqual(string_data, stream.getvalue()) else: - self.assertEqual(string_data, stream.content.decode()) + self.assertEqual(string_data, stream.text) def test_serialize_specific_fields(self): obj = ComplexModel(field1="first", field2="second", field3="third") diff --git a/tests/sitemaps_tests/test_generic.py b/tests/sitemaps_tests/test_generic.py index dc998eec93..f0cd14699b 100644 --- a/tests/sitemaps_tests/test_generic.py +++ b/tests/sitemaps_tests/test_generic.py @@ -45,7 +45,7 @@ class GenericViewsSitemapTests(SitemapTestsBase): "%s\n" "" ) % expected - self.assertXMLEqual(response.content.decode(), expected_content) + self.assertXMLEqual(response.text, expected_content) def test_generic_sitemap_lastmod(self): test_model = TestModel.objects.first() @@ -61,7 +61,7 @@ class GenericViewsSitemapTests(SitemapTestsBase): self.base_url, test_model.pk, ) - self.assertXMLEqual(response.content.decode(), expected_content) + self.assertXMLEqual(response.text, expected_content) self.assertEqual( response.headers["Last-Modified"], "Wed, 13 Mar 2013 10:00:00 GMT" ) @@ -89,4 +89,4 @@ class GenericViewsSitemapTests(SitemapTestsBase): http://example.com/simple/sitemap-generic.xml2013-03-13T10:00:00 """ - self.assertXMLEqual(response.content.decode("utf-8"), expected_content) + self.assertXMLEqual(response.text, expected_content) diff --git a/tests/sitemaps_tests/test_http.py b/tests/sitemaps_tests/test_http.py index 74d183a7b0..6ae7e0d7c4 100644 --- a/tests/sitemaps_tests/test_http.py +++ b/tests/sitemaps_tests/test_http.py @@ -29,7 +29,7 @@ class HTTPSitemapTests(SitemapTestsBase): self.base_url, date.today(), ) - self.assertXMLEqual(response.content.decode(), expected_content) + self.assertXMLEqual(response.text, expected_content) def test_sitemap_not_callable(self): """A sitemap may not be callable.""" @@ -42,7 +42,7 @@ class HTTPSitemapTests(SitemapTestsBase): self.base_url, date.today(), ) - self.assertXMLEqual(response.content.decode(), expected_content) + self.assertXMLEqual(response.text, expected_content) def test_paged_sitemap(self): """A sitemap may have multiple pages.""" @@ -54,7 +54,7 @@ class HTTPSitemapTests(SitemapTestsBase): """.format( self.base_url, date.today() ) - self.assertXMLEqual(response.content.decode(), expected_content) + self.assertXMLEqual(response.text, expected_content) @override_settings( TEMPLATES=[ @@ -76,7 +76,7 @@ class HTTPSitemapTests(SitemapTestsBase): self.base_url, date.today(), ) - self.assertXMLEqual(response.content.decode(), expected_content) + self.assertXMLEqual(response.text, expected_content) def test_simple_sitemap_section(self): "A simple sitemap section can be rendered" @@ -92,7 +92,7 @@ class HTTPSitemapTests(SitemapTestsBase): self.base_url, date.today(), ) - self.assertXMLEqual(response.content.decode(), expected_content) + self.assertXMLEqual(response.text, expected_content) def test_no_section(self): response = self.client.get("/simple/sitemap-simple2.xml") @@ -126,7 +126,7 @@ class HTTPSitemapTests(SitemapTestsBase): self.base_url, date.today(), ) - self.assertXMLEqual(response.content.decode(), expected_content) + self.assertXMLEqual(response.text, expected_content) @override_settings( TEMPLATES=[ @@ -148,7 +148,7 @@ class HTTPSitemapTests(SitemapTestsBase): self.base_url, date.today(), ) - self.assertXMLEqual(response.content.decode(), expected_content) + self.assertXMLEqual(response.text, expected_content) def test_sitemap_last_modified(self): "Last-Modified header is set correctly" @@ -268,7 +268,7 @@ class HTTPSitemapTests(SitemapTestsBase): "never0.5\n" "" ) % date.today() - self.assertXMLEqual(response.content.decode(), expected_content) + self.assertXMLEqual(response.text, expected_content) def test_sitemap_get_urls_no_site_1(self): """ @@ -316,7 +316,7 @@ class HTTPSitemapTests(SitemapTestsBase): self.base_url, date.today(), ) - self.assertXMLEqual(response.content.decode(), expected_content) + self.assertXMLEqual(response.text, expected_content) def test_x_robots_sitemap(self): response = self.client.get("/simple/index.xml") @@ -346,7 +346,7 @@ class HTTPSitemapTests(SitemapTestsBase): "never0.5\n" "" ).format(self.base_url, self.i18n_model.pk) - self.assertXMLEqual(response.content.decode(), expected_content) + self.assertXMLEqual(response.text, expected_content) @override_settings(LANGUAGES=(("en", "English"), ("pt", "Portuguese"))) def test_alternate_i18n_sitemap_index(self): @@ -374,7 +374,7 @@ class HTTPSitemapTests(SitemapTestsBase): f"{expected_urls}\n" f"" ) - self.assertXMLEqual(response.content.decode(), expected_content) + self.assertXMLEqual(response.text, expected_content) @override_settings( LANGUAGES=(("en", "English"), ("pt", "Portuguese"), ("es", "Spanish")) @@ -404,7 +404,7 @@ class HTTPSitemapTests(SitemapTestsBase): f"{expected_urls}\n" f"" ) - self.assertXMLEqual(response.content.decode(), expected_content) + self.assertXMLEqual(response.text, expected_content) @override_settings(LANGUAGES=(("en", "English"), ("pt", "Portuguese"))) def test_alternate_i18n_sitemap_xdefault(self): @@ -434,7 +434,7 @@ class HTTPSitemapTests(SitemapTestsBase): f"{expected_urls}\n" f"" ) - self.assertXMLEqual(response.content.decode(), expected_content) + self.assertXMLEqual(response.text, expected_content) @override_settings(LANGUAGES=(("en", "English"), ("pt", "Portuguese"))) def test_language_for_item_i18n_sitemap(self): @@ -460,7 +460,7 @@ class HTTPSitemapTests(SitemapTestsBase): f"{expected_urls}\n" f"" ) - self.assertXMLEqual(response.content.decode(), expected_content) + self.assertXMLEqual(response.text, expected_content) @override_settings(LANGUAGES=(("en", "English"), ("pt", "Portuguese"))) def test_alternate_language_for_item_i18n_sitemap(self): @@ -500,7 +500,7 @@ class HTTPSitemapTests(SitemapTestsBase): f"{expected_urls}\n" f"" ) - self.assertXMLEqual(response.content.decode(), expected_content) + self.assertXMLEqual(response.text, expected_content) def test_sitemap_without_entries(self): response = self.client.get("/sitemap-without-entries/sitemap.xml") @@ -510,7 +510,7 @@ class HTTPSitemapTests(SitemapTestsBase): 'xmlns:xhtml="http://www.w3.org/1999/xhtml">\n\n' "" ) - self.assertXMLEqual(response.content.decode(), expected_content) + self.assertXMLEqual(response.text, expected_content) def test_callable_sitemod_partial(self): """ @@ -535,8 +535,8 @@ class HTTPSitemapTests(SitemapTestsBase): "http://example.com/location/\n" "" ) - self.assertXMLEqual(index_response.content.decode(), expected_content_index) - self.assertXMLEqual(sitemap_response.content.decode(), expected_content_sitemap) + self.assertXMLEqual(index_response.text, expected_content_index) + self.assertXMLEqual(sitemap_response.text, expected_content_sitemap) def test_callable_sitemod_full(self): """ @@ -566,8 +566,8 @@ class HTTPSitemapTests(SitemapTestsBase): "2014-03-13\n" "" ) - self.assertXMLEqual(index_response.content.decode(), expected_content_index) - self.assertXMLEqual(sitemap_response.content.decode(), expected_content_sitemap) + self.assertXMLEqual(index_response.text, expected_content_index) + self.assertXMLEqual(sitemap_response.text, expected_content_sitemap) def test_callable_sitemod_no_items(self): index_response = self.client.get("/callable-lastmod-no-items/index.xml") @@ -577,4 +577,4 @@ class HTTPSitemapTests(SitemapTestsBase): http://example.com/simple/sitemap-callable-lastmod.xml """ - self.assertXMLEqual(index_response.content.decode(), expected_content_index) + self.assertXMLEqual(index_response.text, expected_content_index) diff --git a/tests/sitemaps_tests/test_https.py b/tests/sitemaps_tests/test_https.py index 2eae71e4cc..a5369869f9 100644 --- a/tests/sitemaps_tests/test_https.py +++ b/tests/sitemaps_tests/test_https.py @@ -20,7 +20,7 @@ class HTTPSSitemapTests(SitemapTestsBase): self.base_url, date.today(), ) - self.assertXMLEqual(response.content.decode(), expected_content) + self.assertXMLEqual(response.text, expected_content) def test_secure_sitemap_section(self): "A secure sitemap section can be rendered" @@ -36,7 +36,7 @@ class HTTPSSitemapTests(SitemapTestsBase): self.base_url, date.today(), ) - self.assertXMLEqual(response.content.decode(), expected_content) + self.assertXMLEqual(response.text, expected_content) @override_settings(SECURE_PROXY_SSL_HEADER=False) @@ -54,7 +54,7 @@ class HTTPSDetectionSitemapTests(SitemapTestsBase): self.base_url.replace("http://", "https://"), date.today(), ) - self.assertXMLEqual(response.content.decode(), expected_content) + self.assertXMLEqual(response.text, expected_content) def test_sitemap_section_with_https_request(self): "A sitemap section requested in HTTPS is rendered with HTTPS links" @@ -70,4 +70,4 @@ class HTTPSDetectionSitemapTests(SitemapTestsBase): self.base_url.replace("http://", "https://"), date.today(), ) - self.assertXMLEqual(response.content.decode(), expected_content) + self.assertXMLEqual(response.text, expected_content) diff --git a/tests/template_tests/test_extends.py b/tests/template_tests/test_extends.py index ce1838654b..0d2a93468c 100644 --- a/tests/template_tests/test_extends.py +++ b/tests/template_tests/test_extends.py @@ -1,9 +1,9 @@ import os -from django.template import Context, Engine, TemplateDoesNotExist +from django.template import Context, Engine, TemplateDoesNotExist, TemplateSyntaxError from django.test import SimpleTestCase -from .utils import ROOT +from .utils import ROOT, setup RECURSIVE = os.path.join(ROOT, "recursive_templates") @@ -181,3 +181,17 @@ class ExtendsBehaviorTests(SimpleTestCase): ) template = engine.get_template("base.html") self.assertEqual(template.render(Context({})), "12AB") + + @setup( + {"index.html": "{% block content %}B{% endblock %}{% extends 'base.html' %}"} + ) + def test_extends_not_first_tag_in_extended_template(self): + msg = "{% extends 'base.html' %} must be the first tag in 'index.html'." + with self.assertRaisesMessage(TemplateSyntaxError, msg): + self.engine.get_template("index.html") + + def test_extends_not_first_tag_in_extended_template_from_string(self): + template_string = "{% block content %}B{% endblock %}{% extends 'base.html' %}" + msg = "{% extends 'base.html' %} must be the first tag in the template." + with self.assertRaisesMessage(TemplateSyntaxError, msg): + Engine().from_string(template_string) diff --git a/tests/view_tests/tests/test_i18n.py b/tests/view_tests/tests/test_i18n.py index 93e91bcc83..229ce68bfc 100644 --- a/tests/view_tests/tests/test_i18n.py +++ b/tests/view_tests/tests/test_i18n.py @@ -295,7 +295,7 @@ class I18NViewTests(SimpleTestCase): """ with override("de"): response = self.client.get("/jsoni18n/") - data = json.loads(response.content.decode()) + data = json.loads(response.text) self.assertIn("catalog", data) self.assertIn("formats", data) self.assertEqual( @@ -329,7 +329,7 @@ class I18NViewTests(SimpleTestCase): """ with self.settings(LANGUAGE_CODE="es"), override("en-us"): response = self.client.get("/jsoni18n/") - data = json.loads(response.content.decode()) + data = json.loads(response.text) self.assertIn("catalog", data) self.assertIn("formats", data) self.assertIn("plural", data) diff --git a/tests/view_tests/tests/test_json.py b/tests/view_tests/tests/test_json.py index 145e6e05a4..b314510f3c 100644 --- a/tests/view_tests/tests/test_json.py +++ b/tests/view_tests/tests/test_json.py @@ -10,7 +10,7 @@ class JsonResponseTests(SimpleTestCase): self.assertEqual(response.status_code, 200) self.assertEqual(response.headers["content-type"], "application/json") self.assertEqual( - json.loads(response.content.decode()), + json.loads(response.text), { "a": [1, 2, 3], "foo": {"bar": "baz"}, diff --git a/tox.ini b/tox.ini index c635a129b2..7a76693f21 100644 --- a/tox.ini +++ b/tox.ini @@ -26,7 +26,7 @@ setenv = PYTHONDONTWRITEBYTECODE=1 deps = -e . - py{3,310,311,312,py3}: -rtests/requirements/py3.txt + py{3,310,311,312,313,py3}: -rtests/requirements/py3.txt postgres: -rtests/requirements/postgres.txt mysql: -rtests/requirements/mysql.txt oracle: -rtests/requirements/oracle.txt