diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index f2116902ef..6c43c1c99a 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,12 +1,12 @@ -# Trac ticket number +#### Trac ticket number ticket-XXXXX -# Branch description +#### Branch description Provide a concise overview of the issue or rationale behind the proposed changes. -# Checklist +#### Checklist - [ ] This PR targets the `main` branch. - [ ] The commit message is written in past tense, mentions the ticket number, and ends with a period. - [ ] I have checked the "Has patch" ticket flag in the Trac system. diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 27cac36b23..0d5ec23550 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -17,6 +17,11 @@ jobs: with: repository: django/django-asv path: "." + - name: Setup Miniforge + uses: conda-incubator/setup-miniconda@v3 + with: + miniforge-version: "24.1.2-0" + activate-environment: asv-bench - name: Install Requirements run: pip install -r requirements.txt - name: Cache Django diff --git a/.github/workflows/data/test_postgres.py.tpl b/.github/workflows/data/test_postgres.py.tpl index e121946d3f..15dfa0d62e 100644 --- a/.github/workflows/data/test_postgres.py.tpl +++ b/.github/workflows/data/test_postgres.py.tpl @@ -1,3 +1,4 @@ +import os from test_sqlite import * # NOQA DATABASES = { @@ -8,6 +9,9 @@ DATABASES = { "PASSWORD": "postgres", "HOST": "localhost", "PORT": 5432, + "OPTIONS": { + "server_side_binding": os.getenv("SERVER_SIDE_BINDING") == "1", + }, }, "other": { "ENGINE": "django.db.backends.postgresql", 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/reminders_check.yml b/.github/workflows/reminders_check.yml deleted file mode 100644 index 6b5ef92367..0000000000 --- a/.github/workflows/reminders_check.yml +++ /dev/null @@ -1,17 +0,0 @@ -name: Check reminders - -on: - schedule: - - cron: '0 * * * *' # At the start of every hour - workflow_dispatch: - -permissions: - contents: read - pull-requests: write - -jobs: - reminders: - runs-on: ubuntu-latest - steps: - - name: Check reminders and notify users - uses: agrc/reminder-action@e59091b4e9705a6108120cb50823108df35b5392 diff --git a/.github/workflows/reminders_create.yml b/.github/workflows/reminders_create.yml deleted file mode 100644 index 97059e507b..0000000000 --- a/.github/workflows/reminders_create.yml +++ /dev/null @@ -1,17 +0,0 @@ -name: Create reminders - -on: - issue_comment: - types: [created, edited] - workflow_dispatch: - -permissions: - contents: read - pull-requests: write - -jobs: - reminders: - runs-on: ubuntu-latest - steps: - - name: Check comments and create reminders - uses: agrc/create-reminder-action@922893a5705067719c4c4751843962f56aabf5eb diff --git a/.github/workflows/schedule_tests.yml b/.github/workflows/schedule_tests.yml index 8b1f01ad86..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 @@ -195,3 +195,47 @@ jobs: working-directory: ./tests/ run: | python -Wall runtests.py --verbosity 2 --noinput --selenium=chrome --headless --settings=test_postgres --parallel 2 + + postgresql: + strategy: + fail-fast: false + matrix: + version: [16, 17] + server_side_bindings: [0, 1] + runs-on: ubuntu-latest + name: PostgreSQL Versions + env: + SERVER_SIDE_BINDING: ${{ matrix.server_side_bindings }} + services: + postgres: + image: postgres:${{ matrix.version }}-alpine + env: + POSTGRES_DB: django + POSTGRES_USER: user + POSTGRES_PASSWORD: postgres + ports: + - 5432:5432 + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.13' + 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 -r tests/requirements/postgres.txt -e . + - name: Create PostgreSQL settings file + run: mv ./.github/workflows/data/test_postgres.py.tpl ./tests/test_postgres.py + - name: Run tests + working-directory: ./tests/ + run: python -Wall runtests.py --settings=test_postgres --verbosity=2 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 d1c74a66c8..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.2.0 + rev: 24.10.0 hooks: - id: black exclude: \.py-tpl$ - repo: https://github.com/adamchainz/blacken-docs - rev: 1.16.0 + rev: 1.19.0 hooks: - id: blacken-docs additional_dependencies: - - black==24.2.0 + - 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.0.0 + rev: 7.1.1 hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-eslint - rev: v9.2.0 + rev: v9.12.0 hooks: - id: eslint diff --git a/AUTHORS b/AUTHORS index d394290728..573a030ea1 100644 --- a/AUTHORS +++ b/AUTHORS @@ -68,7 +68,7 @@ answer newbie questions, and generally made Django that much better: Aljaž Košir Aljosa Mohorovic Alokik Vijay - Amir Karimi + Amir Karimi Amit Chakradeo Amit Ramon Amit Upadhyay @@ -110,8 +110,10 @@ answer newbie questions, and generally made Django that much better: Anubhav Joshi Anvesh Mishra Anže Pečar + A. Rafey Khan Aram Dulyan arien + Arjun Omray Armin Ronacher Aron Podrigal Arsalan Ghassemi @@ -152,6 +154,7 @@ answer newbie questions, and generally made Django that much better: Ben Lomax Ben Slavin Ben Sturmfels + Bendegúz Csirmaz Berker Peksag Bernd Schlapsi Bernhard Essl @@ -495,6 +498,7 @@ answer newbie questions, and generally made Django that much better: Jeremy Carbaugh Jeremy Dunck Jeremy Lainé + Jeremy Thompson Jerin Peter George Jesse Young Jezeniel Zapanta @@ -621,6 +625,7 @@ answer newbie questions, and generally made Django that much better: Lowe Thiderman Luan Pablo Lucas Connors + Lucas Esposito Luciano Ramalho Lucidiot Ludvig Ericson diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py index 8e1d2ace09..f4535acb09 100644 --- a/django/conf/global_settings.py +++ b/django/conf/global_settings.py @@ -43,8 +43,10 @@ TIME_ZONE = "America/Chicago" # If you set this to True, Django will use timezone-aware datetimes. USE_TZ = True -# Language code for this installation. All choices can be found here: -# http://www.i18nguy.com/unicode/language-identifiers.html +# Language code for this installation. Valid choices can be found here: +# https://www.iana.org/assignments/language-subtag-registry/ +# If LANGUAGE_CODE is not listed in LANGUAGES (below), the project must +# provide the necessary translations and locale definitions. LANGUAGE_CODE = "en-us" # Languages we provide translations for, out of the box. diff --git a/django/conf/locale/az/LC_MESSAGES/django.mo b/django/conf/locale/az/LC_MESSAGES/django.mo index f24150dc82..441b0ca0c9 100644 Binary files a/django/conf/locale/az/LC_MESSAGES/django.mo and b/django/conf/locale/az/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/az/LC_MESSAGES/django.po b/django/conf/locale/az/LC_MESSAGES/django.po index 2e25c04597..72734bb6fa 100644 --- a/django/conf/locale/az/LC_MESSAGES/django.po +++ b/django/conf/locale/az/LC_MESSAGES/django.po @@ -5,14 +5,16 @@ # Emin Mastizada , 2015-2016 # Metin Amiroff , 2011 # Nicat Məmmədov , 2022 +# Nijat Mammadov, 2024 +# Sevdimali , 2024 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: Nicat Məmmədov , 2022\n" -"Language-Team: Azerbaijani (http://www.transifex.com/django/django/language/" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-08-07 06:49+0000\n" +"Last-Translator: Sevdimali , 2024\n" +"Language-Team: Azerbaijani (http://app.transifex.com/django/django/language/" "az/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -24,55 +26,58 @@ msgid "Afrikaans" msgstr "Afrikaans" msgid "Arabic" -msgstr "Ərəbcə" +msgstr "Ərəb" msgid "Algerian Arabic" msgstr "Əlcəzair Ərəbcəsi" msgid "Asturian" -msgstr "Asturiyaca" +msgstr "Asturiya" msgid "Azerbaijani" -msgstr "Azərbaycanca" +msgstr "Azərbaycan" msgid "Bulgarian" -msgstr "Bolqarca" +msgstr "Bolqar" msgid "Belarusian" -msgstr "Belarusca" +msgstr "Belarus" msgid "Bengali" -msgstr "Benqalca" +msgstr "Benqal" msgid "Breton" -msgstr "Bretonca" +msgstr "Breton" msgid "Bosnian" -msgstr "Bosniyaca" +msgstr "Bosniya" msgid "Catalan" -msgstr "Katalanca" +msgstr "Katalon" + +msgid "Central Kurdish (Sorani)" +msgstr "Mərkəzi Kürd dili (Sorani)" msgid "Czech" -msgstr "Çexcə" +msgstr "Çex" msgid "Welsh" -msgstr "Uelscə" +msgstr "Uels" msgid "Danish" -msgstr "Danimarkaca" +msgstr "Danimarka" msgid "German" -msgstr "Almanca" +msgstr "Alman" msgid "Lower Sorbian" -msgstr "Aşağı Sorbca" +msgstr "Aşağı Sorb" msgid "Greek" -msgstr "Yunanca" +msgstr "Yunan" msgid "English" -msgstr "İngiliscə" +msgstr "İngilis" msgid "Australian English" msgstr "Avstraliya İngiliscəsi" @@ -84,7 +89,7 @@ msgid "Esperanto" msgstr "Esperanto" msgid "Spanish" -msgstr "İspanca" +msgstr "İspan" msgid "Argentinian Spanish" msgstr "Argentina İspancası" @@ -102,73 +107,73 @@ msgid "Venezuelan Spanish" msgstr "Venesuela İspancası" msgid "Estonian" -msgstr "Estonca" +msgstr "Eston" msgid "Basque" -msgstr "Baskca" +msgstr "Bask" msgid "Persian" -msgstr "Farsca" +msgstr "Fars" msgid "Finnish" -msgstr "Fincə" +msgstr "Fin" msgid "French" -msgstr "Fransızca" +msgstr "Fransız" msgid "Frisian" -msgstr "Friscə" +msgstr "Fris" msgid "Irish" -msgstr "İrlandca" +msgstr "İrland" msgid "Scottish Gaelic" msgstr "Şotland Keltcəsi" msgid "Galician" -msgstr "Qallik dili" +msgstr "Qalisiya" msgid "Hebrew" -msgstr "İbranicə" +msgstr "İvrit" msgid "Hindi" -msgstr "Hindcə" +msgstr "Hind" msgid "Croatian" -msgstr "Xorvatca" +msgstr "Xorvat" msgid "Upper Sorbian" -msgstr "Üst Sorbca" +msgstr "Yuxarı Sorb" msgid "Hungarian" -msgstr "Macarca" +msgstr "Macar" msgid "Armenian" -msgstr "Ermənicə" +msgstr "Erməni" msgid "Interlingua" msgstr "İnterlinqua" msgid "Indonesian" -msgstr "İndonezcə" +msgstr "İndoneziya dili" msgid "Igbo" -msgstr "İqbo dili" +msgstr "İqbo" msgid "Ido" -msgstr "İdoca" +msgstr "İdo" msgid "Icelandic" -msgstr "İslandca" +msgstr "İsland" msgid "Italian" -msgstr "İtalyanca" +msgstr "İtalyan" msgid "Japanese" -msgstr "Yaponca" +msgstr "Yapon" msgid "Georgian" -msgstr "Gürcücə" +msgstr "Gürcü" msgid "Kabyle" msgstr "Kabile" @@ -177,43 +182,43 @@ msgid "Kazakh" msgstr "Qazax" msgid "Khmer" -msgstr "Kxmercə" +msgstr "Xmer" msgid "Kannada" -msgstr "Kannada dili" +msgstr "Kannada" msgid "Korean" -msgstr "Koreyca" +msgstr "Koreya" msgid "Kyrgyz" msgstr "Qırğız" msgid "Luxembourgish" -msgstr "Lüksemburqca" +msgstr "Lüksemburq" msgid "Lithuanian" -msgstr "Litva dili" +msgstr "Litva" msgid "Latvian" -msgstr "Latviya dili" +msgstr "Latış" msgid "Macedonian" -msgstr "Makedonca" +msgstr "Makedon" msgid "Malayalam" -msgstr "Malayamca" +msgstr "Malayam" msgid "Mongolian" -msgstr "Monqolca" +msgstr "Monqol" msgid "Marathi" -msgstr "Marathicə" +msgstr "Marathi" msgid "Malay" msgstr "Malay" msgid "Burmese" -msgstr "Burmescə" +msgstr "Birman" msgid "Norwegian Bokmål" msgstr "Norveç Bukmolcası" @@ -222,94 +227,97 @@ msgid "Nepali" msgstr "Nepal" msgid "Dutch" -msgstr "Flamandca" +msgstr "Niderland" msgid "Norwegian Nynorsk" -msgstr "Nynorsk Norveçcəsi" +msgstr "Norveç Nyunorskcası" msgid "Ossetic" -msgstr "Osetincə" +msgstr "Osetin" msgid "Punjabi" -msgstr "Pancabicə" +msgstr "Pəncab" msgid "Polish" -msgstr "Polyakca" +msgstr "Polyak" msgid "Portuguese" -msgstr "Portuqalca" +msgstr "Portuqal" msgid "Brazilian Portuguese" msgstr "Braziliya Portuqalcası" msgid "Romanian" -msgstr "Rumınca" +msgstr "Rumın" msgid "Russian" -msgstr "Rusca" +msgstr "Rus" msgid "Slovak" -msgstr "Slovakca" +msgstr "Slovak" msgid "Slovenian" -msgstr "Slovencə" +msgstr "Sloven" msgid "Albanian" -msgstr "Albanca" +msgstr "Alban" msgid "Serbian" -msgstr "Serbcə" +msgstr "Serb" msgid "Serbian Latin" -msgstr "Serbcə Latın" +msgstr "Serb (Latın)" msgid "Swedish" -msgstr "İsveçcə" +msgstr "İsveç" msgid "Swahili" msgstr "Suahili" msgid "Tamil" -msgstr "Tamilcə" +msgstr "Tamil" msgid "Telugu" -msgstr "Teluqu dili" +msgstr "Teluqu" msgid "Tajik" msgstr "Tacik" msgid "Thai" -msgstr "Tayca" +msgstr "Tay" msgid "Turkmen" msgstr "Türkmən" msgid "Turkish" -msgstr "Türkcə" +msgstr "Türk" msgid "Tatar" msgstr "Tatar" msgid "Udmurt" -msgstr "Udmurtca" +msgstr "Udmurt" + +msgid "Uyghur" +msgstr "Uyğur" msgid "Ukrainian" -msgstr "Ukraynaca" +msgstr "Ukrayn" msgid "Urdu" -msgstr "Urduca" +msgstr "Urdu" msgid "Uzbek" -msgstr "Özbəkcə" +msgstr "Özbək" msgid "Vietnamese" -msgstr "Vyetnamca" +msgstr "Vyetnam" msgid "Simplified Chinese" -msgstr "Sadələşdirilmiş Çincə" +msgstr "Sadələşdirilmiş Çin dili" msgid "Traditional Chinese" -msgstr "Ənənəvi Çincə" +msgstr "Ənənəvi Çin dili" msgid "Messages" msgstr "Mesajlar" @@ -335,10 +343,13 @@ msgid "That page number is less than 1" msgstr "Səhifə nömrəsi 1-dən balacadır" msgid "That page contains no results" -msgstr "Səhifədə nəticə yoxdur" +msgstr "O səhifədə nəticə yoxdur" msgid "Enter a valid value." -msgstr "Düzgün qiymət daxil edin." +msgstr "Düzgün dəyər daxil edin." + +msgid "Enter a valid domain name." +msgstr "Düzgün domen adı daxil edin." msgid "Enter a valid URL." msgstr "Düzgün URL daxil edin." @@ -363,35 +374,52 @@ msgstr "" "Unicode hərflərdən, rəqəmlərdən, alt-xətlərdən və ya defislərdən ibarət " "düzgün qısaltma (“slug”) daxil edin." -msgid "Enter a valid IPv4 address." -msgstr "Düzgün IPv4 ünvanı daxil edin." +#, python-format +msgid "Enter a valid %(protocol)s address." +msgstr "Düzgün %(protocol)s adres daxil edin." -msgid "Enter a valid IPv6 address." -msgstr "Düzgün IPv6 ünvanını daxil edin." +msgid "IPv4" +msgstr "IPv4" -msgid "Enter a valid IPv4 or IPv6 address." -msgstr "Düzgün IPv4 və ya IPv6 ünvanını daxil edin." +msgid "IPv6" +msgstr "IPv6" + +msgid "IPv4 or IPv6" +msgstr "IPv4 və ya IPv6" msgid "Enter only digits separated by commas." msgstr "Vergüllə ayırmaqla yalnız rəqəmlər daxil edin." #, python-format msgid "Ensure this value is %(limit_value)s (it is %(show_value)s)." -msgstr "Əmin edin ki, bu qiymət %(limit_value)s-dir (bu %(show_value)s-dir)." +msgstr "" +"Əmin olun ki, bu dəyər %(limit_value)s-dir/dır (bu %(show_value)s-dir/dır)." #, python-format msgid "Ensure this value is less than or equal to %(limit_value)s." msgstr "" -"Bu qiymətin %(limit_value)s-ya bərabər və ya ondan kiçik olduğunu yoxlayın." +"Bu qiymətin %(limit_value)s-(y)a/ə bərabər və ya ondan kiçik olduğunu " +"yoxlayın." #, python-format msgid "Ensure this value is greater than or equal to %(limit_value)s." msgstr "" -"Bu qiymətin %(limit_value)s-ya bərabər və ya ondan böyük olduğunu yoxlayın." +"Bu qiymətin %(limit_value)s-(y)a/ə bərabər və ya ondan böyük olduğunu " +"yoxlayın." #, python-format msgid "Ensure this value is a multiple of step size %(limit_value)s." msgstr "" +"Bu dəyərin %(limit_value)s addım ölçüsünün mərtəbələri olduğundan əmin olun." + +#, 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 "" +"Bu dəyərin %(offset)s dəyərindən başlayaraq %(limit_value)s addım ölçüsü " +"mərtəbəsi olduğundan əmin olun. Məs: %(offset)s, %(valid_value1)s, " +"%(valid_value2)s və s." #, python-format msgid "" @@ -402,10 +430,10 @@ msgid_plural "" "%(show_value)d)." msgstr[0] "" "Bu dəyərin ən az %(limit_value)d simvol olduğuna əmin olun (%(show_value)d " -"var)" +"simvol var)" msgstr[1] "" "Bu dəyərin ən az %(limit_value)d simvol olduğuna əmin olun (%(show_value)d " -"var)" +"simvol var)" #, python-format msgid "" @@ -416,10 +444,10 @@ msgid_plural "" "%(show_value)d)." msgstr[0] "" "Bu dəyərin ən çox %(limit_value)d simvol olduğuna əmin olun (%(show_value)d " -"var)" +"simvol var)" msgstr[1] "" "Bu dəyərin ən çox %(limit_value)d simvol olduğuna əmin olun (%(show_value)d " -"var)" +"simvol var)" msgid "Enter a number." msgstr "Ədəd daxil edin." @@ -464,7 +492,7 @@ msgstr "%(field_labels)s ilə %(model_name)s artıq mövcuddur." #, python-format msgid "Constraint “%(name)s” is violated." -msgstr "" +msgstr "“%(name)s” məhdudiyyəti pozuldu." #, python-format msgid "Value %(value)r is not a valid choice." @@ -495,7 +523,7 @@ msgstr "Sahənin tipi: %(field_type)s" #, python-format msgid "“%(value)s” value must be either True or False." -msgstr "“%(value)s” dəyəri True və ya False olmalıdır." +msgstr "“%(value)s” dəyəri ya True, ya da False olmalıdır." #, python-format msgid "“%(value)s” value must be either True, False, or None." @@ -508,6 +536,9 @@ msgstr "Bul (ya Doğru, ya Yalan)" msgid "String (up to %(max_length)s)" msgstr "Sətir (%(max_length)s simvola kimi)" +msgid "String (unlimited)" +msgstr "Sətir (limitsiz)" + msgid "Comma-separated integers" msgstr "Vergüllə ayrılmış tam ədədlər" @@ -523,7 +554,7 @@ msgid "" "“%(value)s” value has the correct format (YYYY-MM-DD) but it is an invalid " "date." msgstr "" -"“%(value)s” dəyəri düzgün formatdadır (YYYY-MM-DD) amma bu tarix xətalıdır." +"“%(value)s” dəyəri düzgün formatdadır (YYYY-MM-DD), amma bu tarix xətalıdır." msgid "Date (without time)" msgstr "Tarix (saatsız)" @@ -602,7 +633,7 @@ msgid "“%(value)s” value must be either None, True or False." msgstr "“%(value)s” dəyəri None, True və ya False olmalıdır." msgid "Boolean (Either True, False or None)" -msgstr "Bul (Ya Doğru, ya Yalan, ya da Heç nə)" +msgstr "Bul (Ya True, ya False, ya da None)" msgid "Positive big integer" msgstr "Müsbət böyük rəqəm" @@ -661,7 +692,7 @@ msgid "A JSON object" msgstr "JSON obyekti" msgid "Value must be valid JSON." -msgstr "Dəyər etibarlı JSON olmalıdır." +msgstr "Dəyər düzgün JSON olmalıdır." #, python-format msgid "%(model)s instance with %(field)s %(value)r does not exist." @@ -770,6 +801,9 @@ 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 "" +"ManagementForm datası ya əskikdir, ya da dəyişdirilib. Çatışmayan xanalar: " +"%(field_names)s. Problem davam edərsə, səhv hesabatı təqdim etməli ola " +"bilərsiniz." #, python-format msgid "Please submit at most %(num)d form." @@ -826,7 +860,7 @@ msgid "" "may be ambiguous or it may not exist." msgstr "" "%(datetime)s vaxtı %(current_timezone)s zaman qurşağında ifadə oluna bilmir; " -"ya duallıq, ya da mövcud olmaya bilər." +"ya qeyri-müəyyənlik, ya da mövcud olmaya bilər." msgid "Clear" msgstr "Təmizlə" @@ -877,16 +911,16 @@ msgid "%s PB" msgstr "%s PB" msgid "p.m." -msgstr "p.m." +msgstr "g.s." msgid "a.m." -msgstr "a.m." +msgstr "g.ə." msgid "PM" -msgstr "PM" +msgstr "GS" msgid "AM" -msgstr "AM" +msgstr "GƏ" msgid "midnight" msgstr "gecə yarısı" @@ -1042,7 +1076,7 @@ msgstr "Avq." msgctxt "abbrev. month" msgid "Sept." -msgstr "Sent." +msgstr "Sen." msgctxt "abbrev. month" msgid "Oct." @@ -1167,6 +1201,10 @@ msgid "" "required for security reasons, to ensure that your browser is not being " "hijacked by third parties." msgstr "" +"Bu mesajı ona görə görürsünüz ki, bu HTTPS saytı sizin səyyah tərəfindən " +"“Referer header”in göndərilməsini tələb etdiyi halda heç nə " +"göndərilməmişdir. Bu başlıq sizin veb-səyyahınızın kənar şəxlər tərəfindən " +"ələ keçirilmədiyindən əmin olmaq üçün tələb olunur." msgid "" "If you have configured your browser to disable “Referer” headers, please re-" @@ -1181,14 +1219,14 @@ 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 "" "Əgər etiketini və ya " "“Referrer-Policy: no-referrer” başlığını işlədirsinizsə, lütfən silin. CSRF " "qoruma dəqiq yönləndirən yoxlaması üçün “Referer” başlığını tələb edir. Əgər " -"məxfilik üçün düşünürsünüzsə, üçüncü tərəf sayt keçidləri üçün kimi bir alternativ işlədin." +"məxfilik üçün düşünürsünüzsə, üçüncü tərəf sayt keçidləri üçün kimi bir alternativ işlədin." msgid "" "You are seeing this message because this site requires a CSRF cookie when " @@ -1249,7 +1287,7 @@ msgstr "Səhifə həm “axırıncı” deyil, həm də tam ədədə çevrilə b #, python-format msgid "Invalid page (%(page_number)s): %(message)s" -msgstr "Qeyri-düzgün səhifə (%(page_number)s): %(message)s" +msgstr "Yanlış səhifə (%(page_number)s): %(message)s" #, python-format msgid "Empty list and “%(class_name)s.allow_empty” is False." @@ -1281,16 +1319,17 @@ 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 "" "Tənzimləmə faylınızda DEBUG=True və heç bir URL qurmadığınız üçün bu səhifəni görürsünüz." +"%(version)s/ref/settings/#debug\" target=\"_blank\" " +"rel=\"noopener\">DEBUG=True və heç bir URL qurmadığınız üçün bu səhifəni " +"görürsünüz." msgid "Django Documentation" -msgstr "Django Sənədləri" +msgstr "Django Dokumentasiya" msgid "Topics, references, & how-to’s" msgstr "Mövzular, istinadlar və nümunələr" @@ -1299,7 +1338,7 @@ msgid "Tutorial: A Polling App" msgstr "Məşğələ: Səsvermə Tətbiqi" msgid "Get started with Django" -msgstr "Django-ya başla" +msgstr "Django ilə başla" msgid "Django Community" msgstr "Django İcması" diff --git a/django/conf/locale/be/LC_MESSAGES/django.mo b/django/conf/locale/be/LC_MESSAGES/django.mo index c639c8bdf1..9c04ff16ca 100644 Binary files a/django/conf/locale/be/LC_MESSAGES/django.mo and b/django/conf/locale/be/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/be/LC_MESSAGES/django.po b/django/conf/locale/be/LC_MESSAGES/django.po index c8dbffe4f3..a8172066ae 100644 --- a/django/conf/locale/be/LC_MESSAGES/django.po +++ b/django/conf/locale/be/LC_MESSAGES/django.po @@ -2,15 +2,16 @@ # # Translators: # Viktar Palstsiuk , 2014-2015 -# znotdead , 2016-2017,2019-2021,2023 +# znotdead , 2016-2017,2019-2021,2023-2024 # Bobsans , 2016 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: znotdead , 2016-2017,2019-2021,2023\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-08-07 06:49+0000\n" +"Last-Translator: znotdead , " +"2016-2017,2019-2021,2023-2024\n" "Language-Team: Belarusian (http://app.transifex.com/django/django/language/" "be/)\n" "MIME-Version: 1.0\n" @@ -347,6 +348,9 @@ msgstr "Гэтая старонка не мае ніякіх вынікаў" msgid "Enter a valid value." msgstr "Пазначце правільнае значэньне." +msgid "Enter a valid domain name." +msgstr "Пазначце сапраўднае даменнае имя." + msgid "Enter a valid URL." msgstr "Пазначце чынную спасылку." @@ -370,14 +374,18 @@ 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/locale/bg/LC_MESSAGES/django.mo b/django/conf/locale/bg/LC_MESSAGES/django.mo index b7b8c865a4..f6bd12b04d 100644 Binary files a/django/conf/locale/bg/LC_MESSAGES/django.mo and b/django/conf/locale/bg/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/bg/LC_MESSAGES/django.po b/django/conf/locale/bg/LC_MESSAGES/django.po index 7446b5478c..52cc913016 100644 --- a/django/conf/locale/bg/LC_MESSAGES/django.po +++ b/django/conf/locale/bg/LC_MESSAGES/django.po @@ -1,7 +1,7 @@ # This file is distributed under the same license as the Django package. # # Translators: -# arneatec , 2022-2023 +# arneatec , 2022-2024 # Boris Chervenkov , 2012 # Claude Paroz , 2020 # Jannis Leidel , 2011 @@ -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 06:49+0000\n" -"Last-Translator: Mariusz Felisiak , 2023\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-08-07 06:49+0000\n" +"Last-Translator: arneatec , 2022-2024\n" "Language-Team: Bulgarian (http://app.transifex.com/django/django/language/" "bg/)\n" "MIME-Version: 1.0\n" @@ -303,7 +303,7 @@ msgid "Udmurt" msgstr "удмурт" msgid "Uyghur" -msgstr "" +msgstr "Уйгурски" msgid "Ukrainian" msgstr "украински" @@ -352,6 +352,9 @@ msgstr "В тази страница няма резултати" msgid "Enter a valid value." msgstr "Въведете валидна стойност. " +msgid "Enter a valid domain name." +msgstr "Въведете валидно име на домейн." + msgid "Enter a valid URL." msgstr "Въведете валиден URL адрес." @@ -374,14 +377,18 @@ msgstr "" "Въведете валиден 'слъг', състоящ се от Уникод букви, цифри, тирета или долни " "тирета." -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 "Въведете само еднозначни числа, разделени със запетая. " @@ -408,6 +415,8 @@ 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 "" +"Въведете стойност, кратна на стъпката %(limit_value)s, започвайки от " +"%(offset)s, например %(offset)s, %(valid_value1)s, %(valid_value2)s, и т.н." #, python-format msgid "" diff --git a/django/conf/locale/ckb/LC_MESSAGES/django.mo b/django/conf/locale/ckb/LC_MESSAGES/django.mo index cf41a447ec..39b9108995 100644 Binary files a/django/conf/locale/ckb/LC_MESSAGES/django.mo and b/django/conf/locale/ckb/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/ckb/LC_MESSAGES/django.po b/django/conf/locale/ckb/LC_MESSAGES/django.po index 0d78c77e4b..1caebbc6b1 100644 --- a/django/conf/locale/ckb/LC_MESSAGES/django.po +++ b/django/conf/locale/ckb/LC_MESSAGES/django.po @@ -4,14 +4,14 @@ # Bawar Jalal, 2021 # Bawar Jalal, 2020-2021 # Bawar Jalal, 2020 -# kosar tofiq , 2020-2021 +# Kosar Tofiq Saeed , 2020-2021 # Swara , 2022-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: 2024-01-12 06:49+0000\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-08-07 06:49+0000\n" "Last-Translator: Swara , 2022-2024\n" "Language-Team: Central Kurdish (http://app.transifex.com/django/django/" "language/ckb/)\n" @@ -347,6 +347,9 @@ msgstr "ئەو پەڕەیە هیچ ئەنجامێکی تێدا نییە" msgid "Enter a valid value." msgstr "نرخێکی دروست لەناودابنێ." +msgid "Enter a valid domain name." +msgstr "پاوەن/دۆمەینی دروست بنوسە." + msgid "Enter a valid URL." msgstr "URL ی دروست لەناودابنێ." @@ -368,14 +371,18 @@ msgstr "" "\"سلەگ\"ێکی دروست بنوسە کە پێکهاتووە لە پیتی یونیکۆد، ژمارە، هێڵی ژێرەوە، " "یان هێما." -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 "ناونیشانێکی IPv64 ی دروست لەناودابنێ." +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/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/da/LC_MESSAGES/django.mo b/django/conf/locale/da/LC_MESSAGES/django.mo index 253c828318..59b111a325 100644 Binary files a/django/conf/locale/da/LC_MESSAGES/django.mo and b/django/conf/locale/da/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/da/LC_MESSAGES/django.po b/django/conf/locale/da/LC_MESSAGES/django.po index 7853017477..eb89a01242 100644 --- a/django/conf/locale/da/LC_MESSAGES/django.po +++ b/django/conf/locale/da/LC_MESSAGES/django.po @@ -3,7 +3,7 @@ # Translators: # Christian Joergensen , 2012 # Danni Randeris , 2014 -# Erik Ramsgaard Wognsen , 2020-2023 +# Erik Ramsgaard Wognsen , 2020-2024 # Erik Ramsgaard Wognsen , 2013-2019 # Finn Gruwier Larsen, 2011 # Jannis Leidel , 2011 @@ -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: Erik Ramsgaard Wognsen , 2020-2023\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-08-07 06:49+0000\n" +"Last-Translator: Erik Ramsgaard Wognsen , 2020-2024\n" "Language-Team: Danish (http://app.transifex.com/django/django/language/da/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -350,6 +350,9 @@ msgstr "Den side indeholder ingen resultater" msgid "Enter a valid value." msgstr "Indtast en gyldig værdi." +msgid "Enter a valid domain name." +msgstr "Indtast et gyldigt domænenavn." + msgid "Enter a valid URL." msgstr "Indtast en gyldig URL." @@ -373,14 +376,18 @@ msgstr "" "Indtast en gyldig “slug” bestående af Unicode-bogstaver, cifre, understreger " "eller bindestreger." -msgid "Enter a valid IPv4 address." -msgstr "Indtast en gyldig IPv4-adresse." +#, python-format +msgid "Enter a valid %(protocol)s address." +msgstr "Indtast en gyldig %(protocol)s-adresse." -msgid "Enter a valid IPv6 address." -msgstr "Indtast en gyldig IPv6-adresse." +msgid "IPv4" +msgstr "IPv4" -msgid "Enter a valid IPv4 or IPv6 address." -msgstr "Indtast en gyldig IPv4- eller IPv6-adresse." +msgid "IPv6" +msgstr "IPv6" + +msgid "IPv4 or IPv6" +msgstr "IPv4 eller IPv6" msgid "Enter only digits separated by commas." msgstr "Indtast kun cifre adskilt af kommaer." 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/LC_MESSAGES/django.mo b/django/conf/locale/es/LC_MESSAGES/django.mo index f84b533674..20ea819b3c 100644 Binary files a/django/conf/locale/es/LC_MESSAGES/django.mo and b/django/conf/locale/es/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/es/LC_MESSAGES/django.po b/django/conf/locale/es/LC_MESSAGES/django.po index fa014d6660..77ec5f4834 100644 --- a/django/conf/locale/es/LC_MESSAGES/django.po +++ b/django/conf/locale/es/LC_MESSAGES/django.po @@ -22,6 +22,7 @@ # Ignacio José Lizarán Rus , 2019 # Igor Támara , 2015 # Jannis Leidel , 2011 +# Jorge Andres Bravo Meza, 2024 # José Luis , 2016 # José Luis , 2016 # Josue Naaman Nistal Guerra , 2014 @@ -32,7 +33,7 @@ # Mariusz Felisiak , 2021 # mpachas , 2022 # monobotsoft , 2012 -# Natalia (Django Fellow), 2024 +# Natalia, 2024 # ntrrgc , 2013 # ntrrgc , 2013 # Pablo, 2015 @@ -46,9 +47,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: 2024-01-12 06:49+0000\n" -"Last-Translator: Natalia (Django Fellow), 2024\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-08-07 06:49+0000\n" +"Last-Translator: Jorge Andres Bravo Meza, 2024\n" "Language-Team: Spanish (http://app.transifex.com/django/django/language/" "es/)\n" "MIME-Version: 1.0\n" @@ -384,6 +385,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 "Ingrese un nombre de dominio válido." + msgid "Enter a valid URL." msgstr "Introduzca una URL válida." @@ -407,14 +411,18 @@ msgstr "" "Introduzca un 'slug' válido, consistente en letras, números, guiones bajos o " "medios de Unicode." -msgid "Enter a valid IPv4 address." -msgstr "Introduzca una dirección IPv4 válida." +#, python-format +msgid "Enter a valid %(protocol)s address." +msgstr "Ingrese 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/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/et/LC_MESSAGES/django.mo b/django/conf/locale/et/LC_MESSAGES/django.mo index 186a2588de..3556bb11e8 100644 Binary files a/django/conf/locale/et/LC_MESSAGES/django.mo and b/django/conf/locale/et/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/et/LC_MESSAGES/django.po b/django/conf/locale/et/LC_MESSAGES/django.po index ed0121e9e8..813f3ab717 100644 --- a/django/conf/locale/et/LC_MESSAGES/django.po +++ b/django/conf/locale/et/LC_MESSAGES/django.po @@ -2,11 +2,11 @@ # # Translators: # eallik , 2011 -# Erlend , 2020 +# Erlend Eelmets , 2020 # Jannis Leidel , 2011 # Janno Liivak , 2013-2015 # madisvain , 2011 -# Martin , 2014-2015,2021-2023 +# Martin , 2014-2015,2021-2024 # Martin , 2016-2017,2019-2020 # Marti Raudsepp , 2014,2016 # Ragnar Rebase , 2019 @@ -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 , 2014-2015,2021-2023\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-08-07 06:49+0000\n" +"Last-Translator: Martin , 2014-2015,2021-2024\n" "Language-Team: Estonian (http://app.transifex.com/django/django/language/" "et/)\n" "MIME-Version: 1.0\n" @@ -351,6 +351,9 @@ msgstr "See leht ei sisalda tulemusi" msgid "Enter a valid value." msgstr "Sisestage korrektne väärtus." +msgid "Enter a valid domain name." +msgstr "Sisestage korrektne domeeninimi." + msgid "Enter a valid URL." msgstr "Sisestage korrektne URL." @@ -374,14 +377,18 @@ msgstr "" "Sisestage korrektne “nälk”, mis koosneb Unicode tähtedest, numbritest, ala- " "või sidekriipsudest." -msgid "Enter a valid IPv4 address." -msgstr "Sisestage korrektne IPv4 aadress." +#, python-format +msgid "Enter a valid %(protocol)s address." +msgstr "Sisestage korrektne %(protocol)s aadress." -msgid "Enter a valid IPv6 address." -msgstr "Sisestage korrektne IPv6 aadress." +msgid "IPv4" +msgstr "IPv4" -msgid "Enter a valid IPv4 or IPv6 address." -msgstr "Sisestage korrektne IPv4 või IPv6 aadress." +msgid "IPv6" +msgstr "IPv6" + +msgid "IPv4 or IPv6" +msgstr "IPv4 või IPv6" msgid "Enter only digits separated by commas." msgstr "Sisestage ainult komaga eraldatud numbreid." diff --git a/django/conf/locale/eu/LC_MESSAGES/django.mo b/django/conf/locale/eu/LC_MESSAGES/django.mo index 572ea61467..031ad43496 100644 Binary files a/django/conf/locale/eu/LC_MESSAGES/django.mo and b/django/conf/locale/eu/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/eu/LC_MESSAGES/django.po b/django/conf/locale/eu/LC_MESSAGES/django.po index 563aa694dd..f3e91a8677 100644 --- a/django/conf/locale/eu/LC_MESSAGES/django.po +++ b/django/conf/locale/eu/LC_MESSAGES/django.po @@ -3,7 +3,7 @@ # Translators: # Aitzol Naberan , 2013,2016 # Ander Martinez , 2013-2014 -# Eneko Illarramendi , 2017-2019,2021-2022 +# Eneko Illarramendi , 2017-2019,2021-2022,2024 # Jannis Leidel , 2011 # jazpillaga , 2011 # julen, 2011-2012 @@ -16,10 +16,11 @@ 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: Eneko Illarramendi \n" -"Language-Team: Basque (http://www.transifex.com/django/django/language/eu/)\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-08-07 06:49+0000\n" +"Last-Translator: Eneko Illarramendi , " +"2017-2019,2021-2022,2024\n" +"Language-Team: Basque (http://app.transifex.com/django/django/language/eu/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -59,6 +60,9 @@ msgstr "Bosniera" msgid "Catalan" msgstr "Katalana" +msgid "Central Kurdish (Sorani)" +msgstr "" + msgid "Czech" msgstr "Txekiera" @@ -159,7 +163,7 @@ msgid "Indonesian" msgstr "Indonesiera" msgid "Igbo" -msgstr "" +msgstr "Igboera" msgid "Ido" msgstr "Ido" @@ -299,6 +303,9 @@ msgstr "Tatarera" msgid "Udmurt" msgstr "Udmurtera" +msgid "Uyghur" +msgstr "" + msgid "Ukrainian" msgstr "Ukrainera" @@ -346,6 +353,9 @@ msgstr "Orrialde horrek ez du emaitzarik" msgid "Enter a valid value." msgstr "Idatzi baleko balio bat." +msgid "Enter a valid domain name." +msgstr "" + msgid "Enter a valid URL." msgstr "Idatzi baleko URL bat." @@ -365,14 +375,18 @@ msgid "" "hyphens." msgstr "" -msgid "Enter a valid IPv4 address." -msgstr "Idatzi baleko IPv4 sare-helbide bat." +#, python-format +msgid "Enter a valid %(protocol)s address." +msgstr "" -msgid "Enter a valid IPv6 address." -msgstr "Idatzi baleko IPv6 sare-helbide bat." +msgid "IPv4" +msgstr "" -msgid "Enter a valid IPv4 or IPv6 address." -msgstr "Idatzi baleko IPv4 edo IPv6 sare-helbide bat." +msgid "IPv6" +msgstr "" + +msgid "IPv4 or IPv6" +msgstr "" msgid "Enter only digits separated by commas." msgstr "Idatzi komaz bereizitako digitoak soilik." @@ -394,6 +408,12 @@ msgstr "Ziurtatu balio hau %(limit_value)s baino handiagoa edo berdina dela." 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 " @@ -506,6 +526,9 @@ msgstr "Boolearra (True edo False)" msgid "String (up to %(max_length)s)" msgstr "String-a (%(max_length)s gehienez)" +msgid "String (unlimited)" +msgstr "" + msgid "Comma-separated integers" msgstr "Komaz bereiztutako zenbaki osoak" @@ -1169,8 +1192,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 "" msgid "" @@ -1241,7 +1264,7 @@ msgstr "Direktorio zerrendak ez daude baimenduak." #, python-format msgid "“%(path)s” does not exist" -msgstr "" +msgstr "\"%(path)s\" ez da existitzen" #, python-format msgid "Index of %(directory)s" @@ -1262,14 +1285,14 @@ 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 "" "Zure settings fitxategian DEBUG=True jarrita eta URLrik konfiguratu gabe duzulako ari zara " -"ikusten orrialde hau." +"%(version)s/ref/settings/#debug\" target=\"_blank\" " +"rel=\"noopener\">DEBUG=True jarrita eta URLrik konfiguratu gabe duzulako " +"ari zara ikusten orrialde hau." msgid "Django Documentation" msgstr "Django dokumentazioa" diff --git a/django/conf/locale/fr/LC_MESSAGES/django.mo b/django/conf/locale/fr/LC_MESSAGES/django.mo index 08b33f0a79..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 6c5a6dda75..253672ee44 100644 --- a/django/conf/locale/fr/LC_MESSAGES/django.po +++ b/django/conf/locale/fr/LC_MESSAGES/django.po @@ -3,7 +3,7 @@ # Translators: # Bruno Brouard , 2021 # Simon Charette , 2012 -# Claude Paroz , 2013-2023 +# Claude Paroz , 2013-2024 # Claude Paroz , 2011 # Jannis Leidel , 2011 # Jean-Baptiste Mora, 2014 @@ -13,9 +13,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: Claude Paroz , 2013-2023\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\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" "Content-Type: text/plain; charset=UTF-8\n" @@ -228,7 +228,7 @@ msgid "Nepali" msgstr "Népalais" msgid "Dutch" -msgstr "Hollandais" +msgstr "Néerlandais" msgid "Norwegian Nynorsk" msgstr "Norvégien nynorsk" @@ -349,6 +349,9 @@ msgstr "Cette page ne contient aucun résultat" msgid "Enter a valid value." msgstr "Saisissez une valeur valide." +msgid "Enter a valid domain name." +msgstr "Saisissez un nom de domaine valide." + msgid "Enter a valid URL." msgstr "Saisissez une URL valide." @@ -372,14 +375,18 @@ msgstr "" "Ce champ ne doit contenir que des caractères Unicode, des nombres, des " "tirets bas (_) et des traits d’union." -msgid "Enter a valid IPv4 address." -msgstr "Saisissez une adresse IPv4 valide." +#, python-format +msgid "Enter a valid %(protocol)s address." +msgstr "Saisissez une adresse %(protocol)s valide." -msgid "Enter a valid IPv6 address." -msgstr "Saisissez une adresse IPv6 valide." +msgid "IPv4" +msgstr "IPv4" -msgid "Enter a valid IPv4 or IPv6 address." -msgstr "Saisissez une adresse IPv4 ou IPv6 valide." +msgid "IPv6" +msgstr "IPv6" + +msgid "IPv4 or IPv6" +msgstr "IPv4 ou IPv6" msgid "Enter only digits separated by commas." msgstr "Saisissez uniquement des chiffres séparés par des virgules." @@ -1181,8 +1188,8 @@ msgstr ", " msgid "%(num)d year" msgid_plural "%(num)d years" msgstr[0] "%(num)d année" -msgstr[1] "%(num)d années" -msgstr[2] "%(num)d années" +msgstr[1] "%(num)d ans" +msgstr[2] "%(num)d ans" #, python-format msgid "%(num)d month" @@ -1275,8 +1282,8 @@ msgid "" "them, at least for this site, or for “same-origin” requests." msgstr "" "Si vous avez désactivé l’envoi des cookies par votre navigateur, veuillez " -"les réactiver au moins pour ce site ou pour les requêtes de même origine (« " -"same-origin »)." +"les réactiver au moins pour ce site ou pour les requêtes de même origine " +"(« same-origin »)." msgid "More information is available with DEBUG=True." msgstr "" 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/gl/LC_MESSAGES/django.mo b/django/conf/locale/gl/LC_MESSAGES/django.mo index d4d4dbbeea..4a9d164480 100644 Binary files a/django/conf/locale/gl/LC_MESSAGES/django.mo and b/django/conf/locale/gl/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/gl/LC_MESSAGES/django.po b/django/conf/locale/gl/LC_MESSAGES/django.po index 58f7858566..7ebeab2075 100644 --- a/django/conf/locale/gl/LC_MESSAGES/django.po +++ b/django/conf/locale/gl/LC_MESSAGES/django.po @@ -8,14 +8,14 @@ # Jannis Leidel , 2011 # Leandro Regueiro , 2013 # 948a55bc37dd6d642f1875bb84258fff_07a28cc , 2012 -# X Bello , 2023 +# X Bello , 2023-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: X Bello , 2023\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-08-07 06:49+0000\n" +"Last-Translator: X Bello , 2023-2024\n" "Language-Team: Galician (http://app.transifex.com/django/django/language/" "gl/)\n" "MIME-Version: 1.0\n" @@ -350,6 +350,9 @@ msgstr "Esa páxina non contén resultados" msgid "Enter a valid value." msgstr "Insira un valor válido." +msgid "Enter a valid domain name." +msgstr "Introduza un nome de dominio válido." + msgid "Enter a valid URL." msgstr "Insira un URL válido." @@ -373,14 +376,18 @@ msgstr "" "Insira un “slug” valido composto por letras Unicode, números, guións baixos " "ou medios." -msgid "Enter a valid IPv4 address." -msgstr "Insira unha dirección IPv4 válida." +#, python-format +msgid "Enter a valid %(protocol)s address." +msgstr "Introduza unha dirección %(protocol)s válida." -msgid "Enter a valid IPv6 address." -msgstr "Insira unha dirección IPv6 válida" +msgid "IPv4" +msgstr "IPv4" -msgid "Enter a valid IPv4 or IPv6 address." -msgstr "Insira unha dirección IPv4 ou IPv6 válida" +msgid "IPv6" +msgstr "IPv6" + +msgid "IPv4 or IPv6" +msgstr "IPv4 ou IPv6" msgid "Enter only digits separated by commas." msgstr "Insira só díxitos separados por comas." 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/id/LC_MESSAGES/django.mo b/django/conf/locale/id/LC_MESSAGES/django.mo index 4a2eb2bb4c..b1815ce52d 100644 Binary files a/django/conf/locale/id/LC_MESSAGES/django.mo and b/django/conf/locale/id/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/id/LC_MESSAGES/django.po b/django/conf/locale/id/LC_MESSAGES/django.po index f0027b7f02..56340389e0 100644 --- a/django/conf/locale/id/LC_MESSAGES/django.po +++ b/django/conf/locale/id/LC_MESSAGES/django.po @@ -2,6 +2,7 @@ # # Translators: # Adiyat Mubarak , 2017 +# Bayu Satiyo , 2024 # Claude Paroz , 2018 # Fery Setiawan , 2015-2019,2021-2023 # Jannis Leidel , 2011 @@ -9,15 +10,15 @@ # oon arfiandwi (OonID) , 2016,2020 # rodin , 2011 # rodin , 2013-2016 -# sag᠎e , 2018-2019 +# sag​e , 2018-2019 # Sutrisno Efendi , 2015,2017 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: Fery Setiawan , 2015-2019,2021-2023\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2013-04-25 06:49+0000\n" +"Last-Translator: Bayu Satiyo , 2024\n" "Language-Team: Indonesian (http://app.transifex.com/django/django/language/" "id/)\n" "MIME-Version: 1.0\n" @@ -303,7 +304,7 @@ msgid "Udmurt" msgstr "Udmurt" msgid "Uyghur" -msgstr "" +msgstr "Uyghur" msgid "Ukrainian" msgstr "Ukrainia" @@ -352,6 +353,9 @@ msgstr "Tidak ada hasil untuk halaman tersebut" msgid "Enter a valid value." msgstr "Masukkan nilai yang valid." +msgid "Enter a valid domain name." +msgstr "" + msgid "Enter a valid URL." msgstr "Masukkan URL yang valid." @@ -375,14 +379,18 @@ msgstr "" "Masukkan sebuah “slug” valid yang terdiri dari huruf, angka, garis bawah, " "atau penghubung Unicode." -msgid "Enter a valid IPv4 address." -msgstr "Masukkan alamat IPv4 yang valid." +#, python-format +msgid "Enter a valid %(protocol)s address." +msgstr "" -msgid "Enter a valid IPv6 address." -msgstr "Masukkan alamat IPv6 yang valid" +msgid "IPv4" +msgstr "" -msgid "Enter a valid IPv4 or IPv6 address." -msgstr "Masukkan alamat IPv4 atau IPv6 yang valid" +msgid "IPv6" +msgstr "" + +msgid "IPv4 or IPv6" +msgstr "" msgid "Enter only digits separated by commas." msgstr "Hanya masukkan angka yang dipisahkan dengan koma." @@ -409,6 +417,9 @@ 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 "" +"Pastikan nilai ini merupakan kelipatan ukuran langkah %(limit_value)s, " +"dimulai dari %(offset)s, misalnya %(offset)s, %(valid_value1)s, " +"%(valid_value2)s, dan seterusnya." #, python-format msgid "" diff --git a/django/conf/locale/ja/LC_MESSAGES/django.mo b/django/conf/locale/ja/LC_MESSAGES/django.mo index d7f0f768d2..d7dad0a5ad 100644 Binary files a/django/conf/locale/ja/LC_MESSAGES/django.mo and b/django/conf/locale/ja/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/ja/LC_MESSAGES/django.po b/django/conf/locale/ja/LC_MESSAGES/django.po index 591cdf3080..ca8a726ffb 100644 --- a/django/conf/locale/ja/LC_MESSAGES/django.po +++ b/django/conf/locale/ja/LC_MESSAGES/django.po @@ -12,8 +12,8 @@ # Masashi SHIBATA , 2017 # Nikita K , 2019 # Shinichi Katsumata , 2019 -# Shinya Okano , 2012-2019,2021,2023 -# Taichi Taniguchi, 2022 +# Shinya Okano , 2012-2019,2021,2023-2024 +# TANIGUCHI Taichi, 2022 # Takuro Onoue , 2020 # Takuya N , 2020 # Tetsuya Morimoto , 2011 @@ -21,9 +21,10 @@ 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: Shinya Okano , 2012-2019,2021,2023\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-08-07 06:49+0000\n" +"Last-Translator: Shinya Okano , " +"2012-2019,2021,2023-2024\n" "Language-Team: Japanese (http://app.transifex.com/django/django/language/" "ja/)\n" "MIME-Version: 1.0\n" @@ -358,6 +359,9 @@ msgstr "このページには結果が含まれていません。" msgid "Enter a valid value." msgstr "値を正しく入力してください。" +msgid "Enter a valid domain name." +msgstr "有効なドメイン名を入力してください。" + msgid "Enter a valid URL." msgstr "URLを正しく入力してください。" @@ -380,14 +384,18 @@ msgstr "" "ユニコード文字、数字、アンダースコアまたはハイフンで構成された、有効なスラグ" "を入力してください。" -msgid "Enter a valid IPv4 address." -msgstr "有効なIPアドレス (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/ko/LC_MESSAGES/django.mo b/django/conf/locale/ko/LC_MESSAGES/django.mo index 0aaf757398..02c61beb19 100644 Binary files a/django/conf/locale/ko/LC_MESSAGES/django.mo and b/django/conf/locale/ko/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/ko/LC_MESSAGES/django.po b/django/conf/locale/ko/LC_MESSAGES/django.po index 52832dcc51..ef36cf19d9 100644 --- a/django/conf/locale/ko/LC_MESSAGES/django.po +++ b/django/conf/locale/ko/LC_MESSAGES/django.po @@ -4,6 +4,7 @@ # BJ Jang , 2014 # JunGu Kang , 2017 # Jiyoon, Ha , 2016 +# darjeeling , 2024 # DONGHO JEONG , 2020 # Park Hyunwoo , 2017 # Geonho Kim / Leo Kim , 2019 @@ -22,6 +23,7 @@ # Mariusz Felisiak , 2021 # Seho Noh , 2018 # Seoeun(Sun☀️) Hong, 2023 +# 최소영, 2024 # Subin Choi , 2016 # Taesik Yoon , 2015 # 정훈 이, 2021 @@ -29,9 +31,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: Seoeun(Sun☀️) Hong, 2023\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-08-07 06:49+0000\n" +"Last-Translator: darjeeling , 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" @@ -316,7 +318,7 @@ msgid "Udmurt" msgstr "이제프스크" msgid "Uyghur" -msgstr "" +msgstr "위구르" msgid "Ukrainian" msgstr "우크라이나어" @@ -365,6 +367,9 @@ msgstr "해당 페이지에 결과가 없습니다." msgid "Enter a valid value." msgstr "올바른 값을 입력하세요." +msgid "Enter a valid domain name." +msgstr "유효한 도메인 이름을 입력하세요." + msgid "Enter a valid URL." msgstr "올바른 URL을 입력하세요." @@ -386,14 +391,18 @@ msgstr "" "유니코드 문자, 숫자, 언더스코어 또는 하이픈으로 구성된 올바른 내용을 입력하세" "요." -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 "콤마로 구분된 숫자만 입력하세요." @@ -414,13 +423,15 @@ msgstr "%(limit_value)s 이상의 값을 입력해 주세요." #, python-format msgid "Ensure this value is a multiple of step size %(limit_value)s." -msgstr "" +msgstr "단계 크기 %(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 "" +"예를 들어 %(offset)s, %(valid_value1)s, %(valid_value2)s 등, %(offset)s에서 " +"시작하는 %(limit_value)s의 배수를 입력해 주세요." #, python-format msgid "" @@ -529,7 +540,7 @@ msgid "String (up to %(max_length)s)" msgstr "문자열(%(max_length)s 글자까지)" msgid "String (unlimited)" -msgstr "" +msgstr "문자열 (무제한의)" msgid "Comma-separated integers" msgstr "정수(콤마로 구분)" diff --git a/django/conf/locale/lv/LC_MESSAGES/django.mo b/django/conf/locale/lv/LC_MESSAGES/django.mo index 3e4fae85eb..ddbf8c3baa 100644 Binary files a/django/conf/locale/lv/LC_MESSAGES/django.mo and b/django/conf/locale/lv/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/lv/LC_MESSAGES/django.po b/django/conf/locale/lv/LC_MESSAGES/django.po index 91b6f74269..26e5090617 100644 --- a/django/conf/locale/lv/LC_MESSAGES/django.po +++ b/django/conf/locale/lv/LC_MESSAGES/django.po @@ -2,7 +2,7 @@ # # Translators: # edgars , 2011 -# Edgars Voroboks , 2023 +# Edgars Voroboks , 2023-2024 # Edgars Voroboks , 2017,2022 # Edgars Voroboks , 2017-2018 # Jannis Leidel , 2011 @@ -17,9 +17,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: Edgars Voroboks , 2023\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-08-07 06:49+0000\n" +"Last-Translator: Edgars Voroboks , 2023-2024\n" "Language-Team: Latvian (http://app.transifex.com/django/django/language/" "lv/)\n" "MIME-Version: 1.0\n" @@ -355,6 +355,9 @@ msgstr "Lapa nesatur rezultātu" msgid "Enter a valid value." msgstr "Ievadiet korektu vērtību." +msgid "Enter a valid domain name." +msgstr "Ievadiet derīgu domēna vārdu." + msgid "Enter a valid URL." msgstr "Ievadiet korektu URL adresi." @@ -378,14 +381,18 @@ msgstr "" "Ievadiet korektu \"identifikatora\" vērtību, kas satur tikai Unikoda burtus, " "ciparus, apakšsvītras vai defises." -msgid "Enter a valid IPv4 address." -msgstr "Ievadiet korektu IPv4 adresi." +#, python-format +msgid "Enter a valid %(protocol)s address." +msgstr "Ievadiet derīgu %(protocol)s adresi." -msgid "Enter a valid IPv6 address." -msgstr "Ievadiet korektu IPv6 adresi" +msgid "IPv4" +msgstr "IPv4" -msgid "Enter a valid IPv4 or IPv6 address." -msgstr "Ievadiet korektu IPv4 vai IPv6 adresi" +msgid "IPv6" +msgstr "IPv6" + +msgid "IPv4 or IPv6" +msgstr "IPv4 vai IPv6" msgid "Enter only digits separated by commas." msgstr "Ievadiet tikai numurus, atdalītus ar komatiem." diff --git a/django/conf/locale/nl/LC_MESSAGES/django.mo b/django/conf/locale/nl/LC_MESSAGES/django.mo index bd0c7363fb..7bb3da3fc2 100644 Binary files a/django/conf/locale/nl/LC_MESSAGES/django.mo and b/django/conf/locale/nl/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/nl/LC_MESSAGES/django.po b/django/conf/locale/nl/LC_MESSAGES/django.po index 9db19ac023..85fe2403ae 100644 --- a/django/conf/locale/nl/LC_MESSAGES/django.po +++ b/django/conf/locale/nl/LC_MESSAGES/django.po @@ -15,14 +15,14 @@ # Meteor0id, 2019-2020 # 8de006b1b0894aab6aef71979dcd8bd6_5c6b207 , 2014-2015 # Tino de Bruijn , 2013 -# Tonnes , 2017,2019-2020,2022-2023 +# Tonnes , 2017,2019-2020,2022-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: Tonnes , 2017,2019-2020,2022-2023\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-08-07 06:49+0000\n" +"Last-Translator: Tonnes , 2017,2019-2020,2022-2024\n" "Language-Team: Dutch (http://app.transifex.com/django/django/language/nl/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -356,6 +356,9 @@ msgstr "Die pagina bevat geen resultaten" msgid "Enter a valid value." msgstr "Voer een geldige waarde in." +msgid "Enter a valid domain name." +msgstr "Voer een geldige domeinnaam in." + msgid "Enter a valid URL." msgstr "Voer een geldige URL in." @@ -379,14 +382,18 @@ msgstr "" "Voer een geldige ‘slug’ in, bestaande uit Unicode-letters, cijfers, liggende " "streepjes en verbindingsstreepjes." -msgid "Enter a valid IPv4 address." -msgstr "Voer een geldig IPv4-adres in." +#, python-format +msgid "Enter a valid %(protocol)s address." +msgstr "Voer een geldig %(protocol)s-adres in." -msgid "Enter a valid IPv6 address." -msgstr "Voer een geldig IPv6-adres in." +msgid "IPv4" +msgstr "IPv4" -msgid "Enter a valid IPv4 or IPv6 address." -msgstr "Voer een geldig IPv4- of IPv6-adres in." +msgid "IPv6" +msgstr "IPv6" + +msgid "IPv4 or IPv6" +msgstr "IPv4- of IPv6" msgid "Enter only digits separated by commas." msgstr "Voer alleen cijfers in, gescheiden door komma's." diff --git a/django/conf/locale/pl/LC_MESSAGES/django.mo b/django/conf/locale/pl/LC_MESSAGES/django.mo index 3da13804f9..56a969979b 100644 Binary files a/django/conf/locale/pl/LC_MESSAGES/django.mo and b/django/conf/locale/pl/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/pl/LC_MESSAGES/django.po b/django/conf/locale/pl/LC_MESSAGES/django.po index 99d69fae38..7dc17ae2db 100644 --- a/django/conf/locale/pl/LC_MESSAGES/django.po +++ b/django/conf/locale/pl/LC_MESSAGES/django.po @@ -18,7 +18,7 @@ # Maciej Olko , 2016-2021 # Maciej Olko , 2023 # Maciej Olko , 2015 -# Mariusz Felisiak , 2020-2021,2023 +# Mariusz Felisiak , 2020-2021,2023-2024 # Michał Pasternak , 2013 # c10516f0462e552b4c3672569f0745a7_cc5cca2 <841826256cd8f47d0e443806a8e56601_19204>, 2012 # Piotr Meuś , 2014 @@ -33,10 +33,10 @@ 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" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-08-07 06:49+0000\n" "Last-Translator: Mariusz Felisiak , " -"2020-2021,2023\n" +"2020-2021,2023-2024\n" "Language-Team: Polish (http://app.transifex.com/django/django/language/pl/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -372,6 +372,9 @@ msgstr "Ta strona nie zawiera wyników" msgid "Enter a valid value." msgstr "Wpisz poprawną wartość." +msgid "Enter a valid domain name." +msgstr "Wpisz poprawną nazwę domeny." + msgid "Enter a valid URL." msgstr "Wpisz poprawny URL." @@ -394,14 +397,18 @@ msgstr "" "Wpisz poprawny „slug” zawierający litery Unicode, cyfry, podkreślenia i " "myślniki." -msgid "Enter a valid IPv4 address." -msgstr "Wprowadź poprawny adres IPv4." +#, python-format +msgid "Enter a valid %(protocol)s address." +msgstr "Wpisz poprawny adres %(protocol)s. " -msgid "Enter a valid IPv6 address." -msgstr "Wprowadź poprawny adres IPv6." +msgid "IPv4" +msgstr "IPv4" -msgid "Enter a valid IPv4 or IPv6 address." -msgstr "Wprowadź poprawny adres IPv4 lub IPv6." +msgid "IPv6" +msgstr "IPv6" + +msgid "IPv4 or IPv6" +msgstr "IPv4 lub IPv6" msgid "Enter only digits separated by commas." msgstr "Wpisz tylko cyfry oddzielone przecinkami." diff --git a/django/conf/locale/pt_BR/LC_MESSAGES/django.mo b/django/conf/locale/pt_BR/LC_MESSAGES/django.mo index 66c5ac7745..41fb501633 100644 Binary files a/django/conf/locale/pt_BR/LC_MESSAGES/django.mo and b/django/conf/locale/pt_BR/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/pt_BR/LC_MESSAGES/django.po b/django/conf/locale/pt_BR/LC_MESSAGES/django.po index 9e08d6b3cf..11ee807b93 100644 --- a/django/conf/locale/pt_BR/LC_MESSAGES/django.po +++ b/django/conf/locale/pt_BR/LC_MESSAGES/django.po @@ -8,12 +8,13 @@ # Arthur Silva , 2017 # bruno.devpod , 2014 # Camilo B. Moreira , 2017 -# Carlos C. Leite , 2020 -# Carlos C. Leite , 2016,2019 +# Carlos Cadu “Cadu” Leite , 2020 +# Carlos Cadu “Cadu” Leite , 2016,2019 # Filipe Cifali , 2016 # Claudio Rogerio Carvalho Filho , 2020 # dudanogueira , 2012 # dudanogueira , 2019 +# Eduardo Felipe Castegnaro , 2024 # Elyézer Rezende , 2013 # Fábio C. Barrionuevo da Luz , 2014-2015 # Felipe Rodrigues , 2016 @@ -22,6 +23,7 @@ # fa9e10542e458baef0599ae856e43651_13d2225, 2011-2014 # Guilherme , 2022 # Heron Fonsaca, 2022 +# Hugo Tácito , 2024 # Igor Cavalcante , 2017 # Jannis Leidel , 2011 # Jonas Rodrigues, 2023 @@ -42,9 +44,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: Leonardo Gregianin, 2023\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-08-07 06:49+0000\n" +"Last-Translator: Eduardo Felipe Castegnaro , 2024\n" "Language-Team: Portuguese (Brazil) (http://app.transifex.com/django/django/" "language/pt_BR/)\n" "MIME-Version: 1.0\n" @@ -379,6 +381,9 @@ msgstr "Essa página não contém resultados" msgid "Enter a valid value." msgstr "Informe um valor válido." +msgid "Enter a valid domain name." +msgstr "Informe um domínio válido." + msgid "Enter a valid URL." msgstr "Informe uma URL válida." @@ -401,14 +406,18 @@ msgstr "" "Informe um “slug” válido tendo letras em Unicode, números, \"underscores\" e " "hífens." -msgid "Enter a valid IPv4 address." -msgstr "Insira um endereço IPv4 válido." +#, python-format +msgid "Enter a valid %(protocol)s address." +msgstr "Insira um endereço %(protocol)s válido." -msgid "Enter a valid IPv6 address." -msgstr "Insira um endereço IPv6 válido." +msgid "IPv4" +msgstr "IPv4" -msgid "Enter a valid IPv4 or IPv6 address." -msgstr "Insira um endereço IPv4 ou IPv6 válido." +msgid "IPv6" +msgstr "IPv6" + +msgid "IPv4 or IPv6" +msgstr "IPv4 ou IPv6" msgid "Enter only digits separated by commas." msgstr "Insira apenas dígitos separados por vírgulas." diff --git a/django/conf/locale/ru/LC_MESSAGES/django.mo b/django/conf/locale/ru/LC_MESSAGES/django.mo index e4044214a9..138118e11a 100644 Binary files a/django/conf/locale/ru/LC_MESSAGES/django.mo and b/django/conf/locale/ru/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/ru/LC_MESSAGES/django.po b/django/conf/locale/ru/LC_MESSAGES/django.po index a4d56f2026..1766d19521 100644 --- a/django/conf/locale/ru/LC_MESSAGES/django.po +++ b/django/conf/locale/ru/LC_MESSAGES/django.po @@ -19,16 +19,16 @@ # Panasoft, 2021 # Вася Аникин , 2017 # SeryiMysh , 2020 -# Алексей Борискин , 2013-2017,2019-2020,2022-2023 +# Алексей Борискин , 2013-2017,2019-2020,2022-2024 # Bobsans , 2016,2018 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" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-08-07 06:49+0000\n" "Last-Translator: Алексей Борискин , " -"2013-2017,2019-2020,2022-2023\n" +"2013-2017,2019-2020,2022-2024\n" "Language-Team: Russian (http://app.transifex.com/django/django/language/" "ru/)\n" "MIME-Version: 1.0\n" @@ -365,6 +365,9 @@ msgstr "Страница не содержит результатов" msgid "Enter a valid value." msgstr "Введите правильное значение." +msgid "Enter a valid domain name." +msgstr "Введите правильное имя домена." + msgid "Enter a valid URL." msgstr "Введите правильный URL." @@ -388,14 +391,18 @@ msgstr "" "Значение должно состоять только из символов входящих в стандарт Юникод, " "цифр, символов подчёркивания или дефисов." -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/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/sq/LC_MESSAGES/django.mo b/django/conf/locale/sq/LC_MESSAGES/django.mo index 44c1348d3a..b6de2d0425 100644 Binary files a/django/conf/locale/sq/LC_MESSAGES/django.mo and b/django/conf/locale/sq/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/sq/LC_MESSAGES/django.po b/django/conf/locale/sq/LC_MESSAGES/django.po index a58abd152a..37142f53c9 100644 --- a/django/conf/locale/sq/LC_MESSAGES/django.po +++ b/django/conf/locale/sq/LC_MESSAGES/django.po @@ -2,16 +2,16 @@ # # Translators: # Besnik Bleta , 2011-2014 -# Besnik Bleta , 2020-2023 +# Besnik Bleta , 2020-2024 # Besnik Bleta , 2015-2019 # Jannis Leidel , 2011 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: Besnik Bleta , 2020-2023\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-08-07 06:49+0000\n" +"Last-Translator: Besnik Bleta , 2020-2024\n" "Language-Team: Albanian (http://app.transifex.com/django/django/language/" "sq/)\n" "MIME-Version: 1.0\n" @@ -346,6 +346,9 @@ msgstr "Ajo faqe s’përmban përfundime" msgid "Enter a valid value." msgstr "Jepni një vlerë të vlefshme." +msgid "Enter a valid domain name." +msgstr "Jepni një emër të vlefshëm përkatësie." + msgid "Enter a valid URL." msgstr "Jepni një URL të vlefshme." @@ -369,14 +372,18 @@ msgstr "" "Jepni një “slug” të vlefshëm, të përbërë nga shkronja, numra, nënvija ose " "vija ndarëse Unikod." -msgid "Enter a valid IPv4 address." -msgstr "Jepni një adresë IPv4 të vlefshme." +#, python-format +msgid "Enter a valid %(protocol)s address." +msgstr "Jepni një adresë %(protocol)s të vlefshme." -msgid "Enter a valid IPv6 address." -msgstr "Jepni një adresë IPv6 të vlefshme." +msgid "IPv4" +msgstr "IPv4" -msgid "Enter a valid IPv4 or IPv6 address." -msgstr "Jepni një adresë IPv4 ose IPv6 të vlefshme." +msgid "IPv6" +msgstr "IPv6" + +msgid "IPv4 or IPv6" +msgstr "IPv4, ose IPv6" msgid "Enter only digits separated by commas." msgstr "Jepni vetëm shifra të ndara nga presje." diff --git a/django/conf/locale/sr/LC_MESSAGES/django.mo b/django/conf/locale/sr/LC_MESSAGES/django.mo index 4b305d7daa..ce850cd15e 100644 Binary files a/django/conf/locale/sr/LC_MESSAGES/django.mo and b/django/conf/locale/sr/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/sr/LC_MESSAGES/django.po b/django/conf/locale/sr/LC_MESSAGES/django.po index 7d8efcf0af..aee578af78 100644 --- a/django/conf/locale/sr/LC_MESSAGES/django.po +++ b/django/conf/locale/sr/LC_MESSAGES/django.po @@ -2,7 +2,7 @@ # # Translators: # Branko Kokanovic , 2018-2019 -# Igor Jerosimić, 2019-2021,2023 +# Igor Jerosimić, 2019-2021,2023-2024 # Jannis Leidel , 2011 # Janos Guljas , 2011-2012 # Mariusz Felisiak , 2021 @@ -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: Igor Jerosimić, 2019-2021,2023\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-08-07 06:49+0000\n" +"Last-Translator: Igor Jerosimić, 2019-2021,2023-2024\n" "Language-Team: Serbian (http://app.transifex.com/django/django/language/" "sr/)\n" "MIME-Version: 1.0\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 "" "Унесите исправан \"слаг\", који се састоји од Уникод слова, бројки, доњих " "црта или цртица." -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/sr_Latn/LC_MESSAGES/django.mo b/django/conf/locale/sr_Latn/LC_MESSAGES/django.mo index 3e17b5ae9b..4a4ffa80dc 100644 Binary files a/django/conf/locale/sr_Latn/LC_MESSAGES/django.mo and b/django/conf/locale/sr_Latn/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/sr_Latn/LC_MESSAGES/django.po b/django/conf/locale/sr_Latn/LC_MESSAGES/django.po index ab4ac0ff44..5f5bc7790a 100644 --- a/django/conf/locale/sr_Latn/LC_MESSAGES/django.po +++ b/django/conf/locale/sr_Latn/LC_MESSAGES/django.po @@ -1,410 +1,537 @@ # This file is distributed under the same license as the Django package. -# +# # Translators: # Aleksa Cukovic` , 2020 # Danijela Popović, 2022 -# Igor Jerosimić, 2019-2021,2023 +# Igor Jerosimić, 2019-2021,2023-2024 # Jannis Leidel , 2011 # Janos Guljas , 2011-2012 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: Igor Jerosimić, 2019-2021,2023\n" -"Language-Team: Serbian (Latin) (http://app.transifex.com/django/django/" -"language/sr@latin/)\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-08-07 06:49+0000\n" +"Last-Translator: Igor Jerosimić, 2019-2021,2023-2024\n" +"Language-Team: Serbian (Latin) (http://app.transifex.com/django/django/language/sr@latin/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: sr@latin\n" -"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" -"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" +#: conf/global_settings.py:52 msgid "Afrikaans" msgstr "afrikanski" +#: conf/global_settings.py:53 msgid "Arabic" msgstr "arapski" +#: conf/global_settings.py:54 msgid "Algerian Arabic" msgstr "Alžirski arapski" +#: conf/global_settings.py:55 msgid "Asturian" msgstr "asturijski" +#: conf/global_settings.py:56 msgid "Azerbaijani" msgstr "azerbejdžanski" +#: conf/global_settings.py:57 msgid "Bulgarian" msgstr "bugarski" +#: conf/global_settings.py:58 msgid "Belarusian" msgstr "beloruski" +#: conf/global_settings.py:59 msgid "Bengali" msgstr "bengalski" +#: conf/global_settings.py:60 msgid "Breton" msgstr "bretonski" +#: conf/global_settings.py:61 msgid "Bosnian" msgstr "bosanski" +#: conf/global_settings.py:62 msgid "Catalan" msgstr "katalonski" +#: conf/global_settings.py:63 msgid "Central Kurdish (Sorani)" msgstr "centralnokurdski (sorani)" +#: conf/global_settings.py:64 msgid "Czech" msgstr "češki" +#: conf/global_settings.py:65 msgid "Welsh" msgstr "velški" +#: conf/global_settings.py:66 msgid "Danish" msgstr "danski" +#: conf/global_settings.py:67 msgid "German" msgstr "nemački" +#: conf/global_settings.py:68 msgid "Lower Sorbian" msgstr "donjolužičkosrpski" +#: conf/global_settings.py:69 msgid "Greek" msgstr "grčki" +#: conf/global_settings.py:70 msgid "English" msgstr "engleski" +#: conf/global_settings.py:71 msgid "Australian English" msgstr "australijski engleski" +#: conf/global_settings.py:72 msgid "British English" msgstr "britanski engleski" +#: conf/global_settings.py:73 msgid "Esperanto" msgstr "esperanto" +#: conf/global_settings.py:74 msgid "Spanish" msgstr "španski" +#: conf/global_settings.py:75 msgid "Argentinian Spanish" msgstr "argentinski španski" +#: conf/global_settings.py:76 msgid "Colombian Spanish" msgstr "kolumbijski španski" +#: conf/global_settings.py:77 msgid "Mexican Spanish" msgstr "meksički španski" +#: conf/global_settings.py:78 msgid "Nicaraguan Spanish" msgstr "nikaragvanski španski" +#: conf/global_settings.py:79 msgid "Venezuelan Spanish" msgstr "venecuelanski španski" +#: conf/global_settings.py:80 msgid "Estonian" msgstr "estonski" +#: conf/global_settings.py:81 msgid "Basque" msgstr "baskijski" +#: conf/global_settings.py:82 msgid "Persian" msgstr "persijski" +#: conf/global_settings.py:83 msgid "Finnish" msgstr "finski" +#: conf/global_settings.py:84 msgid "French" msgstr "francuski" +#: conf/global_settings.py:85 msgid "Frisian" msgstr "frizijski" +#: conf/global_settings.py:86 msgid "Irish" msgstr "irski" +#: conf/global_settings.py:87 msgid "Scottish Gaelic" msgstr "škotski galski" +#: conf/global_settings.py:88 msgid "Galician" msgstr "galski" +#: conf/global_settings.py:89 msgid "Hebrew" msgstr "hebrejski" +#: conf/global_settings.py:90 msgid "Hindi" msgstr "hindu" +#: conf/global_settings.py:91 msgid "Croatian" msgstr "hrvatski" +#: conf/global_settings.py:92 msgid "Upper Sorbian" msgstr "gornjolužičkosrpski" +#: conf/global_settings.py:93 msgid "Hungarian" msgstr "mađarski" +#: conf/global_settings.py:94 msgid "Armenian" msgstr "jermenski" +#: conf/global_settings.py:95 msgid "Interlingua" msgstr "interlingva" +#: conf/global_settings.py:96 msgid "Indonesian" msgstr "indonežanski" +#: conf/global_settings.py:97 msgid "Igbo" msgstr "Igbo" +#: conf/global_settings.py:98 msgid "Ido" msgstr "ido" +#: conf/global_settings.py:99 msgid "Icelandic" msgstr "islandski" +#: conf/global_settings.py:100 msgid "Italian" msgstr "italijanski" +#: conf/global_settings.py:101 msgid "Japanese" msgstr "japanski" +#: conf/global_settings.py:102 msgid "Georgian" msgstr "gruzijski" +#: conf/global_settings.py:103 msgid "Kabyle" msgstr "kabilski" +#: conf/global_settings.py:104 msgid "Kazakh" msgstr "kazaški" +#: conf/global_settings.py:105 msgid "Khmer" msgstr "kambodijski" +#: conf/global_settings.py:106 msgid "Kannada" msgstr "kanada" +#: conf/global_settings.py:107 msgid "Korean" msgstr "korejski" +#: conf/global_settings.py:108 msgid "Kyrgyz" msgstr "Kirgiski" +#: conf/global_settings.py:109 msgid "Luxembourgish" msgstr "luksemburški" +#: conf/global_settings.py:110 msgid "Lithuanian" msgstr "litvanski" +#: conf/global_settings.py:111 msgid "Latvian" msgstr "latvijski" +#: conf/global_settings.py:112 msgid "Macedonian" msgstr "makedonski" +#: conf/global_settings.py:113 msgid "Malayalam" msgstr "malajalamski" +#: conf/global_settings.py:114 msgid "Mongolian" msgstr "mongolski" +#: conf/global_settings.py:115 msgid "Marathi" msgstr "marathi" +#: conf/global_settings.py:116 msgid "Malay" msgstr "malajski" +#: conf/global_settings.py:117 msgid "Burmese" msgstr "burmanski" +#: conf/global_settings.py:118 msgid "Norwegian Bokmål" msgstr "norveški književni" +#: conf/global_settings.py:119 msgid "Nepali" msgstr "nepalski" +#: conf/global_settings.py:120 msgid "Dutch" msgstr "holandski" +#: conf/global_settings.py:121 msgid "Norwegian Nynorsk" msgstr "norveški novi" +#: conf/global_settings.py:122 msgid "Ossetic" msgstr "osetinski" +#: conf/global_settings.py:123 msgid "Punjabi" msgstr "Pandžabi" +#: conf/global_settings.py:124 msgid "Polish" msgstr "poljski" +#: conf/global_settings.py:125 msgid "Portuguese" msgstr "portugalski" +#: conf/global_settings.py:126 msgid "Brazilian Portuguese" msgstr "brazilski portugalski" +#: conf/global_settings.py:127 msgid "Romanian" msgstr "rumunski" +#: conf/global_settings.py:128 msgid "Russian" msgstr "ruski" +#: conf/global_settings.py:129 msgid "Slovak" msgstr "slovački" +#: conf/global_settings.py:130 msgid "Slovenian" msgstr "slovenački" +#: conf/global_settings.py:131 msgid "Albanian" msgstr "albanski" +#: conf/global_settings.py:132 msgid "Serbian" msgstr "srpski" +#: conf/global_settings.py:133 msgid "Serbian Latin" msgstr "srpski (latinica)" +#: conf/global_settings.py:134 msgid "Swedish" msgstr "švedski" +#: conf/global_settings.py:135 msgid "Swahili" msgstr "svahili" +#: conf/global_settings.py:136 msgid "Tamil" msgstr "tamilski" +#: conf/global_settings.py:137 msgid "Telugu" msgstr "telugu" +#: conf/global_settings.py:138 msgid "Tajik" msgstr "Tadžiki" +#: conf/global_settings.py:139 msgid "Thai" msgstr "tajlandski" +#: conf/global_settings.py:140 msgid "Turkmen" msgstr "Turkmenski" +#: conf/global_settings.py:141 msgid "Turkish" msgstr "turski" +#: conf/global_settings.py:142 msgid "Tatar" msgstr "tatarski" +#: conf/global_settings.py:143 msgid "Udmurt" msgstr "udmurtski" +#: conf/global_settings.py:144 msgid "Uyghur" -msgstr "" +msgstr "Ujgur" +#: conf/global_settings.py:145 msgid "Ukrainian" msgstr "ukrajinski" +#: conf/global_settings.py:146 msgid "Urdu" msgstr "Urdu" +#: conf/global_settings.py:147 msgid "Uzbek" msgstr "Uzbekistanski" +#: conf/global_settings.py:148 msgid "Vietnamese" msgstr "vijetnamski" +#: conf/global_settings.py:149 msgid "Simplified Chinese" msgstr "novokineski" +#: conf/global_settings.py:150 msgid "Traditional Chinese" msgstr "starokineski" +#: contrib/messages/apps.py:15 msgid "Messages" msgstr "Poruke" +#: contrib/sitemaps/apps.py:8 msgid "Site Maps" msgstr "Mape sajta" +#: contrib/staticfiles/apps.py:9 msgid "Static Files" msgstr "Statičke datoteke" +#: contrib/syndication/apps.py:7 msgid "Syndication" msgstr "Udruživanje sadržaja" #. Translators: String used to replace omitted page numbers in elided page #. range generated by paginators, e.g. [1, 2, '…', 5, 6, 7, '…', 9, 10]. +#: core/paginator.py:30 msgid "…" msgstr "…" +#: core/paginator.py:32 msgid "That page number is not an integer" msgstr "Zadati broj strane nije ceo broj" +#: core/paginator.py:33 msgid "That page number is less than 1" msgstr "Zadati broj strane je manji od 1" +#: core/paginator.py:34 msgid "That page contains no results" msgstr "Tražena strana ne sadrži rezultate" +#: core/validators.py:22 msgid "Enter a valid value." msgstr "Unesite ispravnu vrednost." +#: core/validators.py:70 +msgid "Enter a valid domain name." +msgstr "Unesite ispravno ime domena." + +#: core/validators.py:104 forms/fields.py:759 msgid "Enter a valid URL." msgstr "Unesite ispravan URL." +#: core/validators.py:165 msgid "Enter a valid integer." msgstr "Unesite ispravan ceo broj." +#: core/validators.py:176 msgid "Enter a valid email address." msgstr "Unesite ispravnu e-mail adresu." #. Translators: "letters" means latin letters: a-z and A-Z. +#: core/validators.py:259 msgid "" "Enter a valid “slug” consisting of letters, numbers, underscores or hyphens." -msgstr "" -"Unesite isrpavan „slag“, koji se sastoji od slova, brojki, donjih crta ili " -"cirtica." +msgstr "Unesite isrpavan „slag“, koji se sastoji od slova, brojki, donjih crta ili cirtica." +#: core/validators.py:267 msgid "" -"Enter a valid “slug” consisting of Unicode letters, numbers, underscores, or " -"hyphens." -msgstr "" -"Unesite ispravan \"slag\", koji se sastoji od Unikod slova, brojki, donjih " -"crta ili crtica." +"Enter a valid “slug” consisting of Unicode letters, numbers, underscores, or" +" hyphens." +msgstr "Unesite ispravan \"slag\", koji se sastoji od Unikod slova, brojki, donjih crta ili crtica." -msgid "Enter a valid IPv4 address." -msgstr "Unesite ispravnu IPv4 adresu." +#: core/validators.py:327 core/validators.py:336 core/validators.py:350 +#: db/models/fields/__init__.py:2219 +#, python-format +msgid "Enter a valid %(protocol)s address." +msgstr "Unesite ispravnu adresu %(protocol)s." -msgid "Enter a valid IPv6 address." -msgstr "Unesite ispravnu IPv6 adresu." +#: core/validators.py:329 +msgid "IPv4" +msgstr "IPv4" -msgid "Enter a valid IPv4 or IPv6 address." -msgstr "Unesite ispravnu IPv4 ili IPv6 adresu." +#: core/validators.py:338 utils/ipv6.py:30 +msgid "IPv6" +msgstr "IPv6" +#: core/validators.py:352 +msgid "IPv4 or IPv6" +msgstr "IPv4 ili IPv6" + +#: core/validators.py:341 msgid "Enter only digits separated by commas." msgstr "Unesite samo brojke razdvojene zapetama." +#: core/validators.py:347 #, python-format msgid "Ensure this value is %(limit_value)s (it is %(show_value)s)." msgstr "Ovo polje mora da bude %(limit_value)s (trenutno ima %(show_value)s)." +#: core/validators.py:382 #, python-format msgid "Ensure this value is less than or equal to %(limit_value)s." msgstr "Ova vrednost mora da bude manja od %(limit_value)s. ili tačno toliko." +#: core/validators.py:391 #, python-format msgid "Ensure this value is greater than or equal to %(limit_value)s." msgstr "Ova vrednost mora biti veća od %(limit_value)s ili tačno toliko." +#: core/validators.py:400 #, python-format msgid "Ensure this value is a multiple of step size %(limit_value)s." msgstr "Ova vrednost mora da umnožak veličine koraka %(limit_value)s." +#: core/validators.py:407 #, 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 "" +msgstr "Uverite se da je ova vrednost višestruka od veličine koraka %(limit_value)s, počevši od %(offset)s, npr. %(offset)s, %(valid_value1)s, %(valid_value2)s, itd." +#: core/validators.py:439 #, python-format msgid "" "Ensure this value has at least %(limit_value)d character (it has " @@ -412,16 +539,11 @@ msgid "" msgid_plural "" "Ensure this value has at least %(limit_value)d characters (it has " "%(show_value)d)." -msgstr[0] "" -"Ovo polje mora da ima najmanje %(limit_value)d karakter (trenutno ima " -"%(show_value)d)." -msgstr[1] "" -"Ovo polje mora da ima najmanje %(limit_value)d karaktera (trenutno ima " -"%(show_value)d)." -msgstr[2] "" -"Ovo polje mora da ima %(limit_value)d najmanje karaktera (trenutno ima " -"%(show_value)d )." +msgstr[0] "Ovo polje mora da ima najmanje %(limit_value)d karakter (trenutno ima %(show_value)d)." +msgstr[1] "Ovo polje mora da ima najmanje %(limit_value)d karaktera (trenutno ima %(show_value)d)." +msgstr[2] "Ovo polje mora da ima %(limit_value)d najmanje karaktera (trenutno ima %(show_value)d )." +#: core/validators.py:457 #, python-format msgid "" "Ensure this value has at most %(limit_value)d character (it has " @@ -429,19 +551,15 @@ msgid "" msgid_plural "" "Ensure this value has at most %(limit_value)d characters (it has " "%(show_value)d)." -msgstr[0] "" -"Ovo polje ne sme da ima više od %(limit_value)d karaktera (trenutno ima " -"%(show_value)d)." -msgstr[1] "" -"Ovo polje ne sme da ima više od %(limit_value)d karaktera (trenutno ima " -"%(show_value)d)." -msgstr[2] "" -"Ovo polje ne sme da ima više od %(limit_value)d karaktera (trenutno ima " -"%(show_value)d)." +msgstr[0] "Ovo polje ne sme da ima više od %(limit_value)d karaktera (trenutno ima %(show_value)d)." +msgstr[1] "Ovo polje ne sme da ima više od %(limit_value)d karaktera (trenutno ima %(show_value)d)." +msgstr[2] "Ovo polje ne sme da ima više od %(limit_value)d karaktera (trenutno ima %(show_value)d)." +#: core/validators.py:480 forms/fields.py:354 forms/fields.py:393 msgid "Enter a number." msgstr "Unesite broj." +#: core/validators.py:482 #, 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." @@ -449,6 +567,7 @@ msgstr[0] "Ukupno ne može biti više od %(max)s cifre." msgstr[1] "Ukupno ne može biti više od %(max)s cifre." msgstr[2] "Ukupno ne može biti više od %(max)s cifara." +#: core/validators.py:487 #, 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." @@ -456,6 +575,7 @@ msgstr[0] "Ne može biti više od %(max)s decimale." msgstr[1] "Ne može biti više od %(max)s decimale." msgstr[2] "Ne može biti više od %(max)s decimala." +#: core/validators.py:492 #, python-format msgid "" "Ensure that there are no more than %(max)s digit before the decimal point." @@ -465,344 +585,402 @@ msgstr[0] "Ne može biti više od %(max)s cifre pre decimalnog zapisa." msgstr[1] "Ne može biti više od %(max)s cifre pre decimalnog zapisa." msgstr[2] "Ne može biti više od %(max)s cifara pre decimalnog zapisa." +#: core/validators.py:563 #, python-format msgid "" "File extension “%(extension)s” is not allowed. Allowed extensions are: " "%(allowed_extensions)s." -msgstr "" -"Ekstenzija datoteke \"%(extension)s\" nije dozvoljena. Dozvoljene su sledeće " -"ekstenzije: %(allowed_extensions)s." +msgstr "Ekstenzija datoteke \"%(extension)s\" nije dozvoljena. Dozvoljene su sledeće ekstenzije: %(allowed_extensions)s." +#: core/validators.py:624 msgid "Null characters are not allowed." msgstr "'Null' karakteri nisu dozvoljeni." +#: db/models/base.py:1465 forms/models.py:902 msgid "and" msgstr "i" +#: db/models/base.py:1467 #, python-format msgid "%(model_name)s with this %(field_labels)s already exists." msgstr "%(model_name)s sa poljem %(field_labels)s već postoji." +#: db/models/constraints.py:20 #, python-format msgid "Constraint “%(name)s” is violated." msgstr "Ograničenje „%(name)s“ je prekršeno." +#: db/models/fields/__init__.py:128 #, python-format msgid "Value %(value)r is not a valid choice." msgstr "Vrednost %(value)r nije validna." +#: db/models/fields/__init__.py:129 msgid "This field cannot be null." msgstr "Ovo polje ne može da ostane prazno." +#: db/models/fields/__init__.py:130 msgid "This field cannot be blank." msgstr "Ovo polje ne može da ostane prazno." +#: db/models/fields/__init__.py:131 #, python-format msgid "%(model_name)s with this %(field_label)s already exists." msgstr "%(model_name)s sa ovom vrednošću %(field_label)s već postoji." #. Translators: The 'lookup_type' is one of 'date', 'year' or #. 'month'. Eg: "Title must be unique for pub_date year" +#: db/models/fields/__init__.py:135 #, python-format msgid "" "%(field_label)s must be unique for %(date_field_label)s %(lookup_type)s." -msgstr "" -"%(field_label)s mora biti jedinstven(a) za %(date_field_label)s " -"%(lookup_type)s." +msgstr "%(field_label)s mora biti jedinstven(a) za %(date_field_label)s %(lookup_type)s." +#: db/models/fields/__init__.py:174 #, python-format msgid "Field of type: %(field_type)s" msgstr "Polje tipa: %(field_type)s" +#: db/models/fields/__init__.py:1157 #, python-format msgid "“%(value)s” value must be either True or False." msgstr "Vrednost \"%(value)s\" mora biti True ili False." +#: db/models/fields/__init__.py:1158 #, python-format msgid "“%(value)s” value must be either True, False, or None." msgstr "\"%(value)s\" vrednost mora biti True, False ili None." +#: db/models/fields/__init__.py:1160 msgid "Boolean (Either True or False)" msgstr "Bulova vrednost (True ili False)" +#: db/models/fields/__init__.py:1210 #, python-format msgid "String (up to %(max_length)s)" msgstr "String (najviše %(max_length)s znakova)" +#: db/models/fields/__init__.py:1212 msgid "String (unlimited)" msgstr "String (neograničeno)" +#: db/models/fields/__init__.py:1316 msgid "Comma-separated integers" msgstr "Celi brojevi razdvojeni zapetama" +#: db/models/fields/__init__.py:1417 #, python-format msgid "" "“%(value)s” value has an invalid date format. It must be in YYYY-MM-DD " "format." -msgstr "" -"Vrednost \"%(value)s\" nema ispravan format datuma. Mora biti u formatu GGGG-" -"MM-DD." +msgstr "Vrednost \"%(value)s\" nema ispravan format datuma. Mora biti u formatu GGGG-MM-DD." +#: db/models/fields/__init__.py:1421 db/models/fields/__init__.py:1556 #, python-format msgid "" "“%(value)s” value has the correct format (YYYY-MM-DD) but it is an invalid " "date." -msgstr "" -"Vrednost “%(value)s” ima odgovarajući format (GGGG-MM-DD), ali nije validan " -"datum." +msgstr "Vrednost “%(value)s” ima odgovarajući format (GGGG-MM-DD), ali nije validan datum." +#: db/models/fields/__init__.py:1425 msgid "Date (without time)" msgstr "Datum (bez vremena)" +#: db/models/fields/__init__.py:1552 #, python-format msgid "" -"“%(value)s” value has an invalid format. It must be in YYYY-MM-DD HH:MM[:ss[." -"uuuuuu]][TZ] format." -msgstr "" -"Vrednost “%(value)s” je u nevažećem formatu. Mora se uneti u formatu YYYY-MM-" -"DD HH:MM[:ss[.uuuuuu]][TZ]." +"“%(value)s” value has an invalid format. It must be in YYYY-MM-DD " +"HH:MM[:ss[.uuuuuu]][TZ] format." +msgstr "Vrednost “%(value)s” je u nevažećem formatu. Mora se uneti u formatu YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]." +#: db/models/fields/__init__.py:1560 #, 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 "" -"Vrednost “%(value)s” je u odgovarajućem formatu (YYYY-MM-DD HH:MM[:ss[." -"uuuuuu]][TZ]), ali nije validna vrednost za datum i vreme." +"“%(value)s” value has the correct format (YYYY-MM-DD " +"HH:MM[:ss[.uuuuuu]][TZ]) but it is an invalid date/time." +msgstr "Vrednost “%(value)s” je u odgovarajućem formatu (YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]), ali nije validna vrednost za datum i vreme." +#: db/models/fields/__init__.py:1565 msgid "Date (with time)" msgstr "Datum (sa vremenom)" +#: db/models/fields/__init__.py:1689 #, python-format msgid "“%(value)s” value must be a decimal number." msgstr "Vrednost “%(value)s” mora biti decimalni broj." +#: db/models/fields/__init__.py:1691 msgid "Decimal number" msgstr "Decimalni broj" +#: db/models/fields/__init__.py:1852 #, python-format msgid "" -"“%(value)s” value has an invalid format. It must be in [DD] [[HH:]MM:]ss[." -"uuuuuu] format." -msgstr "" -"Vrednost “%(value)s” nije u odgovarajućem formatu. Mora biti u formatu [DD] " -"[[HH:]MM:]ss[.uuuuuu]." +"“%(value)s” value has an invalid format. It must be in [DD] " +"[[HH:]MM:]ss[.uuuuuu] format." +msgstr "Vrednost “%(value)s” nije u odgovarajućem formatu. Mora biti u formatu [DD] [[HH:]MM:]ss[.uuuuuu]." +#: db/models/fields/__init__.py:1856 msgid "Duration" msgstr "Vremenski interval" +#: db/models/fields/__init__.py:1908 msgid "Email address" msgstr "Imejl adresa" +#: db/models/fields/__init__.py:1933 msgid "File path" msgstr "Putanja fajla" +#: db/models/fields/__init__.py:2011 #, python-format msgid "“%(value)s” value must be a float." msgstr "Vrednost “%(value)s” value mora biti tipa float." +#: db/models/fields/__init__.py:2013 msgid "Floating point number" msgstr "Broj sa pokrenom zapetom" +#: db/models/fields/__init__.py:2053 #, python-format msgid "“%(value)s” value must be an integer." msgstr "Vrednost “%(value)s” mora biti ceo broj." +#: db/models/fields/__init__.py:2055 msgid "Integer" msgstr "Ceo broj" +#: db/models/fields/__init__.py:2151 msgid "Big (8 byte) integer" msgstr "Veliki ceo broj" +#: db/models/fields/__init__.py:2168 msgid "Small integer" msgstr "Mali ceo broj" +#: db/models/fields/__init__.py:2176 msgid "IPv4 address" msgstr "IPv4 adresa" +#: db/models/fields/__init__.py:2207 msgid "IP address" msgstr "IP adresa" +#: db/models/fields/__init__.py:2300 db/models/fields/__init__.py:2301 #, python-format msgid "“%(value)s” value must be either None, True or False." msgstr "Vrednost “%(value)s” mora biti None, True ili False." +#: db/models/fields/__init__.py:2303 msgid "Boolean (Either True, False or None)" msgstr "Bulova vrednost (True, False ili None)" +#: db/models/fields/__init__.py:2354 msgid "Positive big integer" msgstr "Velik pozitivan celi broj" +#: db/models/fields/__init__.py:2369 msgid "Positive integer" msgstr "Pozitivan ceo broj" +#: db/models/fields/__init__.py:2384 msgid "Positive small integer" msgstr "Pozitivan mali ceo broj" +#: db/models/fields/__init__.py:2400 #, python-format msgid "Slug (up to %(max_length)s)" msgstr "Slag (ne duži od %(max_length)s)" +#: db/models/fields/__init__.py:2436 msgid "Text" msgstr "Tekst" +#: db/models/fields/__init__.py:2511 #, python-format msgid "" "“%(value)s” value has an invalid format. It must be in HH:MM[:ss[.uuuuuu]] " "format." -msgstr "" -"Vrednost “%(value)s” nije u odgovarajućem formatu. Mora biti u formatu HH:" -"MM[:ss[.uuuuuu]]." +msgstr "Vrednost “%(value)s” nije u odgovarajućem formatu. Mora biti u formatu HH:MM[:ss[.uuuuuu]]." +#: db/models/fields/__init__.py:2515 #, python-format msgid "" "“%(value)s” value has the correct format (HH:MM[:ss[.uuuuuu]]) but it is an " "invalid time." -msgstr "" -"Vrednost “%(value)s” je u odgovarajućem formatu (HH:MM[:ss[.uuuuuu]]), ali " -"nije validna vrednost za vreme." +msgstr "Vrednost “%(value)s” je u odgovarajućem formatu (HH:MM[:ss[.uuuuuu]]), ali nije validna vrednost za vreme." +#: db/models/fields/__init__.py:2519 msgid "Time" msgstr "Vreme" +#: db/models/fields/__init__.py:2627 msgid "URL" msgstr "URL" +#: db/models/fields/__init__.py:2651 msgid "Raw binary data" msgstr "Sirovi binarni podaci" +#: db/models/fields/__init__.py:2716 #, python-format msgid "“%(value)s” is not a valid UUID." msgstr "Vrednost “%(value)s” nije validan UUID (jedinstveni ID)." +#: db/models/fields/__init__.py:2718 msgid "Universally unique identifier" msgstr "Univerzalno jedinstveni identifikator" +#: db/models/fields/files.py:232 msgid "File" msgstr "Fajl" +#: db/models/fields/files.py:393 msgid "Image" msgstr "Slika" +#: db/models/fields/json.py:26 msgid "A JSON object" msgstr "JSON objekat" +#: db/models/fields/json.py:28 msgid "Value must be valid JSON." msgstr "Vrednost mora biti ispravni JSON." +#: db/models/fields/related.py:939 #, python-format msgid "%(model)s instance with %(field)s %(value)r does not exist." msgstr "Instanca modela %(model)s sa vrednošću %(field)s %(value)r ne postoji." +#: db/models/fields/related.py:941 msgid "Foreign Key (type determined by related field)" msgstr "Strani ključ (tip određuje referentno polje)" +#: db/models/fields/related.py:1235 msgid "One-to-one relationship" msgstr "Relacija jedan na jedan" +#: db/models/fields/related.py:1292 #, python-format msgid "%(from)s-%(to)s relationship" msgstr "Relacija %(from)s-%(to)s" +#: db/models/fields/related.py:1294 #, python-format msgid "%(from)s-%(to)s relationships" msgstr "Relacije %(from)s-%(to)s" +#: db/models/fields/related.py:1342 msgid "Many-to-many relationship" msgstr "Relacija više na više" #. Translators: If found as last label character, these punctuation #. characters will prevent the default label_suffix to be appended to the #. label +#: forms/boundfield.py:185 msgid ":?.!" msgstr ":?.!" +#: forms/fields.py:94 msgid "This field is required." msgstr "Ovo polje se mora popuniti." +#: forms/fields.py:303 msgid "Enter a whole number." msgstr "Unesite ceo broj." +#: forms/fields.py:474 forms/fields.py:1246 msgid "Enter a valid date." msgstr "Unesite ispravan datum." +#: forms/fields.py:497 forms/fields.py:1247 msgid "Enter a valid time." msgstr "Unesite ispravno vreme" +#: forms/fields.py:524 msgid "Enter a valid date/time." msgstr "Unesite ispravan datum/vreme." +#: forms/fields.py:558 msgid "Enter a valid duration." msgstr "Unesite ispravno trajanje." +#: forms/fields.py:559 #, python-brace-format msgid "The number of days must be between {min_days} and {max_days}." msgstr "Broj dana mora biti između {min_days} i {max_days}." +#: forms/fields.py:628 msgid "No file was submitted. Check the encoding type on the form." msgstr "Fajl nije prebačen. Proverite tip enkodiranja formulara." +#: forms/fields.py:629 msgid "No file was submitted." msgstr "Fajl nije prebačen." +#: forms/fields.py:630 msgid "The submitted file is empty." msgstr "Prebačen fajl je prazan." +#: forms/fields.py:632 #, python-format -msgid "Ensure this filename has at most %(max)d character (it has %(length)d)." +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] "" -"Ime fajla ne može imati više od %(max)d karaktera (trenutno ima %(length)d)." -msgstr[1] "" -"Ime fajla ne može imati više od %(max)d karaktera (trenutno ima %(length)d)." -msgstr[2] "" -"Ime fajla ne može imati više od %(max)d karaktera (trenutno ima %(length)d)." +msgstr[0] "Ime fajla ne može imati više od %(max)d karaktera (trenutno ima %(length)d)." +msgstr[1] "Ime fajla ne može imati više od %(max)d karaktera (trenutno ima %(length)d)." +msgstr[2] "Ime fajla ne može imati više od %(max)d karaktera (trenutno ima %(length)d)." +#: forms/fields.py:637 msgid "Please either submit a file or check the clear checkbox, not both." msgstr "Može se samo poslati fajl ili izbrisati, ne oba." +#: forms/fields.py:701 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." -msgstr "" -"Prebacite ispravan fajl. Fajl koji je prebačen ili nije slika, ili je " -"oštećen." +msgstr "Prebacite ispravan fajl. Fajl koji je prebačen ili nije slika, ili je oštećen." +#: forms/fields.py:868 forms/fields.py:954 forms/models.py:1581 #, python-format msgid "Select a valid choice. %(value)s is not one of the available choices." -msgstr "" -"%(value)s nije među ponuđenim vrednostima. Odaberite jednu od ponuđenih." +msgstr "%(value)s nije među ponuđenim vrednostima. Odaberite jednu od ponuđenih." +#: forms/fields.py:956 forms/fields.py:1075 forms/models.py:1579 msgid "Enter a list of values." msgstr "Unesite listu vrednosti." +#: forms/fields.py:1076 msgid "Enter a complete value." msgstr "Unesite kompletnu vrednost." +#: forms/fields.py:1315 msgid "Enter a valid UUID." msgstr "Unesite ispravan UUID." +#: forms/fields.py:1345 msgid "Enter a valid JSON." msgstr "Unesite ispravan JSON." #. Translators: This is the default suffix added to form field labels +#: forms/forms.py:94 msgid ":" msgstr ":" +#: forms/forms.py:231 #, python-format msgid "(Hidden field %(name)s) %(error)s" msgstr "(Skriveno polje %(name)s) %(error)s" +#: forms/formsets.py:61 #, python-format 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 "" -"Podaci od ManagementForm nedostaju ili su pokvareni. Polja koja nedostaju: " -"%(field_names)s. Možda će biti potrebno da prijavite grešku ako se problem " -"nastavi." +msgstr "Podaci od ManagementForm nedostaju ili su pokvareni. Polja koja nedostaju: %(field_names)s. Možda će biti potrebno da prijavite grešku ako se problem nastavi." +#: forms/formsets.py:65 #, python-format msgid "Please submit at most %(num)d form." msgid_plural "Please submit at most %(num)d forms." @@ -810,6 +988,7 @@ msgstr[0] "Molim prosledite najviše %(num)d formular." msgstr[1] "Molim prosledite najviše %(num)d formulara." msgstr[2] "Molim prosledite najviše %(num)d formulara." +#: forms/formsets.py:70 #, python-format msgid "Please submit at least %(num)d form." msgid_plural "Please submit at least %(num)d forms." @@ -817,72 +996,86 @@ msgstr[0] " Molim prosledite najmanje %(num)d formular." msgstr[1] " Molim prosledite najmanje %(num)d formulara." msgstr[2] " Molim prosledite najmanje %(num)d formulara." +#: forms/formsets.py:484 forms/formsets.py:491 msgid "Order" msgstr "Redosled" +#: forms/formsets.py:499 msgid "Delete" msgstr "Obriši" +#: forms/models.py:895 #, python-format msgid "Please correct the duplicate data for %(field)s." msgstr "Ispravite dupliran sadržaj za polja: %(field)s." +#: forms/models.py:900 #, python-format msgid "Please correct the duplicate data for %(field)s, which must be unique." -msgstr "" -"Ispravite dupliran sadržaj za polja: %(field)s, koji mora da bude jedinstven." +msgstr "Ispravite dupliran sadržaj za polja: %(field)s, koji mora da bude jedinstven." +#: forms/models.py:907 #, python-format msgid "" "Please correct the duplicate data for %(field_name)s which must be unique " "for the %(lookup)s in %(date_field)s." -msgstr "" -"Ispravite dupliran sadržaj za polja: %(field_name)s, koji mora da bude " -"jedinstven za %(lookup)s u %(date_field)s." +msgstr "Ispravite dupliran sadržaj za polja: %(field_name)s, koji mora da bude jedinstven za %(lookup)s u %(date_field)s." +#: forms/models.py:916 msgid "Please correct the duplicate values below." msgstr "Ispravite duplirane vrednosti dole." +#: forms/models.py:1353 msgid "The inline value did not match the parent instance." msgstr "Direktno uneta vrednost ne odgovara instanci roditelja." -msgid "Select a valid choice. That choice is not one of the available choices." +#: forms/models.py:1444 +msgid "" +"Select a valid choice. That choice is not one of the available choices." msgstr "Odabrana vrednost nije među ponuđenima. Odaberite jednu od ponuđenih." +#: forms/models.py:1583 #, python-format msgid "“%(pk)s” is not a valid value." msgstr "\"%(pk)s\" nije ispravna vrednost." +#: forms/utils.py:227 #, 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 "" -"Vreme %(datetime)s se ne može protumačiti u vremenskoj zoni " -"%(current_timezone)s; možda je dvosmisleno ili ne postoji." +msgstr "Vreme %(datetime)s se ne može protumačiti u vremenskoj zoni %(current_timezone)s; možda je dvosmisleno ili ne postoji." +#: forms/widgets.py:457 msgid "Clear" msgstr "Očisti" +#: forms/widgets.py:458 msgid "Currently" msgstr "Trenutno" +#: forms/widgets.py:459 msgid "Change" msgstr "Izmeni" +#: forms/widgets.py:796 msgid "Unknown" msgstr "Nepoznato" +#: forms/widgets.py:797 msgid "Yes" msgstr "Da" +#: forms/widgets.py:798 msgid "No" msgstr "Ne" #. Translators: Please do not add spaces around commas. +#: template/defaultfilters.py:875 msgid "yes,no,maybe" msgstr "da,ne,možda" +#: template/defaultfilters.py:905 template/defaultfilters.py:922 #, python-format msgid "%(size)d byte" msgid_plural "%(size)d bytes" @@ -890,269 +1083,347 @@ msgstr[0] "%(size)d bajt" msgstr[1] "%(size)d bajta" msgstr[2] "%(size)d bajtova" +#: template/defaultfilters.py:924 #, python-format msgid "%s KB" msgstr "%s KB" +#: template/defaultfilters.py:926 #, python-format msgid "%s MB" msgstr "%s MB" +#: template/defaultfilters.py:928 #, python-format msgid "%s GB" msgstr "%s GB" +#: template/defaultfilters.py:930 #, python-format msgid "%s TB" msgstr "%s TB" +#: template/defaultfilters.py:932 #, python-format msgid "%s PB" msgstr "%s PB" +#: utils/dateformat.py:73 msgid "p.m." msgstr "po p." +#: utils/dateformat.py:74 msgid "a.m." msgstr "pre p." +#: utils/dateformat.py:79 msgid "PM" msgstr "PM" +#: utils/dateformat.py:80 msgid "AM" msgstr "AM" +#: utils/dateformat.py:152 msgid "midnight" msgstr "ponoć" +#: utils/dateformat.py:154 msgid "noon" msgstr "podne" +#: utils/dates.py:7 msgid "Monday" msgstr "ponedeljak" +#: utils/dates.py:8 msgid "Tuesday" msgstr "utorak" +#: utils/dates.py:9 msgid "Wednesday" msgstr "sreda" +#: utils/dates.py:10 msgid "Thursday" msgstr "četvrtak" +#: utils/dates.py:11 msgid "Friday" msgstr "petak" +#: utils/dates.py:12 msgid "Saturday" msgstr "subota" +#: utils/dates.py:13 msgid "Sunday" msgstr "nedelja" +#: utils/dates.py:16 msgid "Mon" msgstr "pon." +#: utils/dates.py:17 msgid "Tue" msgstr "uto." +#: utils/dates.py:18 msgid "Wed" msgstr "sre." +#: utils/dates.py:19 msgid "Thu" msgstr "čet." +#: utils/dates.py:20 msgid "Fri" msgstr "pet." +#: utils/dates.py:21 msgid "Sat" msgstr "sub." +#: utils/dates.py:22 msgid "Sun" msgstr "ned." +#: utils/dates.py:25 msgid "January" msgstr "januar" +#: utils/dates.py:26 msgid "February" msgstr "februar" +#: utils/dates.py:27 msgid "March" msgstr "mart" +#: utils/dates.py:28 msgid "April" msgstr "april" +#: utils/dates.py:29 msgid "May" msgstr "maj" +#: utils/dates.py:30 msgid "June" msgstr "jun" +#: utils/dates.py:31 msgid "July" msgstr "jul" +#: utils/dates.py:32 msgid "August" msgstr "avgust" +#: utils/dates.py:33 msgid "September" msgstr "septembar" +#: utils/dates.py:34 msgid "October" msgstr "oktobar" +#: utils/dates.py:35 msgid "November" msgstr "novembar" +#: utils/dates.py:36 msgid "December" msgstr "decembar" +#: utils/dates.py:39 msgid "jan" msgstr "jan." +#: utils/dates.py:40 msgid "feb" msgstr "feb." +#: utils/dates.py:41 msgid "mar" msgstr "mar." +#: utils/dates.py:42 msgid "apr" msgstr "apr." +#: utils/dates.py:43 msgid "may" msgstr "maj." +#: utils/dates.py:44 msgid "jun" msgstr "jun." +#: utils/dates.py:45 msgid "jul" msgstr "jul." +#: utils/dates.py:46 msgid "aug" msgstr "aug." +#: utils/dates.py:47 msgid "sep" msgstr "sep." +#: utils/dates.py:48 msgid "oct" msgstr "okt." +#: utils/dates.py:49 msgid "nov" msgstr "nov." +#: utils/dates.py:50 msgid "dec" msgstr "dec." +#: utils/dates.py:53 msgctxt "abbrev. month" msgid "Jan." msgstr "Jan." +#: utils/dates.py:54 msgctxt "abbrev. month" msgid "Feb." msgstr "Feb." +#: utils/dates.py:55 msgctxt "abbrev. month" msgid "March" msgstr "Mart" +#: utils/dates.py:56 msgctxt "abbrev. month" msgid "April" msgstr "April" +#: utils/dates.py:57 msgctxt "abbrev. month" msgid "May" msgstr "Maj" +#: utils/dates.py:58 msgctxt "abbrev. month" msgid "June" msgstr "Jun" +#: utils/dates.py:59 msgctxt "abbrev. month" msgid "July" msgstr "Jul" +#: utils/dates.py:60 msgctxt "abbrev. month" msgid "Aug." msgstr "Avg." +#: utils/dates.py:61 msgctxt "abbrev. month" msgid "Sept." msgstr "Sept." +#: utils/dates.py:62 msgctxt "abbrev. month" msgid "Oct." msgstr "Okt." +#: utils/dates.py:63 msgctxt "abbrev. month" msgid "Nov." msgstr "Nov." +#: utils/dates.py:64 msgctxt "abbrev. month" msgid "Dec." msgstr "Dec." +#: utils/dates.py:67 msgctxt "alt. month" msgid "January" msgstr "Januar" +#: utils/dates.py:68 msgctxt "alt. month" msgid "February" msgstr "Februar" +#: utils/dates.py:69 msgctxt "alt. month" msgid "March" msgstr "Mart" +#: utils/dates.py:70 msgctxt "alt. month" msgid "April" msgstr "April" +#: utils/dates.py:71 msgctxt "alt. month" msgid "May" msgstr "Maj" +#: utils/dates.py:72 msgctxt "alt. month" msgid "June" msgstr "Jun" +#: utils/dates.py:73 msgctxt "alt. month" msgid "July" msgstr "Jul" +#: utils/dates.py:74 msgctxt "alt. month" msgid "August" msgstr "Avgust" +#: utils/dates.py:75 msgctxt "alt. month" msgid "September" msgstr "Septembar" +#: utils/dates.py:76 msgctxt "alt. month" msgid "October" msgstr "Oktobar" +#: utils/dates.py:77 msgctxt "alt. month" msgid "November" msgstr "Novembar" +#: utils/dates.py:78 msgctxt "alt. month" msgid "December" msgstr "Decembar" +#: utils/ipv6.py:8 msgid "This is not a valid IPv6 address." msgstr "Ovo nije ispravna IPv6 adresa." +#: utils/text.py:70 #, python-format msgctxt "String to return when truncating text" msgid "%(truncated_text)s…" msgstr "%(truncated_text)s..." +#: utils/text.py:255 msgid "or" msgstr "ili" #. Translators: This string is used as a separator between list elements +#: utils/text.py:274 utils/timesince.py:135 msgid ", " msgstr "," +#: utils/timesince.py:8 #, python-format msgid "%(num)d year" msgid_plural "%(num)d years" @@ -1160,6 +1431,7 @@ msgstr[0] "%(num)d godina" msgstr[1] "%(num)d godine" msgstr[2] "%(num)d godina" +#: utils/timesince.py:9 #, python-format msgid "%(num)d month" msgid_plural "%(num)d months" @@ -1167,6 +1439,7 @@ msgstr[0] "%(num)d mesec" msgstr[1] "%(num)d meseca" msgstr[2] "%(num)d meseci" +#: utils/timesince.py:10 #, python-format msgid "%(num)d week" msgid_plural "%(num)d weeks" @@ -1174,6 +1447,7 @@ msgstr[0] "%(num)d nedelja" msgstr[1] "%(num)d nedelje" msgstr[2] "%(num)d nedelja" +#: utils/timesince.py:11 #, python-format msgid "%(num)d day" msgid_plural "%(num)d days" @@ -1181,6 +1455,7 @@ msgstr[0] "%(num)d dan" msgstr[1] "%(num)d dana" msgstr[2] "%(num)d dana" +#: utils/timesince.py:12 #, python-format msgid "%(num)d hour" msgid_plural "%(num)d hours" @@ -1188,6 +1463,7 @@ msgstr[0] "%(num)d sat" msgstr[1] "%(num)d sata" msgstr[2] "%(num)d sati" +#: utils/timesince.py:13 #, python-format msgid "%(num)d minute" msgid_plural "%(num)d minutes" @@ -1195,159 +1471,168 @@ msgstr[0] "%(num)d minut" msgstr[1] "%(num)d minuta" msgstr[2] "%(num)d minuta" +#: views/csrf.py:29 msgid "Forbidden" msgstr "Zabranjeno" +#: views/csrf.py:30 msgid "CSRF verification failed. Request aborted." msgstr "CSRF verifikacija nije prošla. Zahtev odbijen." +#: views/csrf.py:34 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 " "required for security reasons, to ensure that your browser is not being " "hijacked by third parties." -msgstr "" -"Ova poruka je prikazana jer ovaj HTTPS sajt zahteva da \"Referer header\" " -"bude poslat od strane vašeg internet pregledača, što trenutno nije slučaj. " -"Pomenuto zaglavlje je potrebno iz bezbedonosnih razloga, da bi se osiguralo " -"da vaš pregledač nije pod kontrolom trećih lica." +msgstr "Ova poruka je prikazana jer ovaj HTTPS sajt zahteva da \"Referer header\" bude poslat od strane vašeg internet pregledača, što trenutno nije slučaj. Pomenuto zaglavlje je potrebno iz bezbedonosnih razloga, da bi se osiguralo da vaš pregledač nije pod kontrolom trećih lica." +#: views/csrf.py:40 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 "" -"Ako ste podesili internet pregledač da ne šalje \"Referer\" zaglavlja, " -"ponovo ih uključite, barem za ovaj sajt, ili za HTTPS konekcije, ili za " -"\"same-origin\" zahteve." +msgstr "Ako ste podesili internet pregledač da ne šalje \"Referer\" zaglavlja, ponovo ih uključite, barem za ovaj sajt, ili za HTTPS konekcije, ili za \"same-origin\" zahteve." +#: views/csrf.py:45 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 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." -msgstr "" -"Ako koristite tag ili " -"\"Referrer-Policy: no-referrer\" zaglavlje, molimo da ih uklonite. CSRF " -"zaštita zahteva \"Referer\" zaglavlje da bi se obavila striktna \"referrer\" " -"provera. Ukoliko vas brine privatnost, koristite alternative kao za linkove ka drugim sajtovima." +msgstr "Ako koristite tag ili \"Referrer-Policy: no-referrer\" zaglavlje, molimo da ih uklonite. CSRF zaštita zahteva \"Referer\" zaglavlje da bi se obavila striktna \"referrer\" provera. Ukoliko vas brine privatnost, koristite alternative kao za linkove ka drugim sajtovima." +#: views/csrf.py:54 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 "" -"Ova poruka je prikazana jer ovaj sajt zahteva CSRF kuki kada se prosleđuju " -"podaci iz formi. Ovaj kuki je potreban iz sigurnosnih razloga, da bi se " -"osiguralo da vaš pretraživač nije pod kontrolom trećih lica." +msgstr "Ova poruka je prikazana jer ovaj sajt zahteva CSRF kuki kada se prosleđuju podaci iz formi. Ovaj kuki je potreban iz sigurnosnih razloga, da bi se osiguralo da vaš pretraživač nije pod kontrolom trećih lica." +#: views/csrf.py:60 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 "" -"Ako je vaš internet pregedač podešen da onemogući kolačiće, molimo da ih " -"uključite, barem za ovaj sajt, ili za \"same-origin\" zahteve." +msgstr "Ako je vaš internet pregedač podešen da onemogući kolačiće, molimo da ih uključite, barem za ovaj sajt, ili za \"same-origin\" zahteve." +#: views/csrf.py:66 msgid "More information is available with DEBUG=True." msgstr "Više informacija je dostupno sa DEBUG=True." +#: views/generic/dates.py:44 msgid "No year specified" msgstr "Godina nije naznačena" +#: views/generic/dates.py:64 views/generic/dates.py:115 +#: views/generic/dates.py:214 msgid "Date out of range" msgstr "Datum van opsega" +#: views/generic/dates.py:94 msgid "No month specified" msgstr "Mesec nije naznačen" +#: views/generic/dates.py:147 msgid "No day specified" msgstr "Dan nije naznačen" +#: views/generic/dates.py:194 msgid "No week specified" msgstr "Nedelja nije naznačena" +#: views/generic/dates.py:349 views/generic/dates.py:380 #, python-format msgid "No %(verbose_name_plural)s available" msgstr "Nedostupni objekti %(verbose_name_plural)s" +#: views/generic/dates.py:652 #, python-format msgid "" -"Future %(verbose_name_plural)s not available because %(class_name)s." -"allow_future is False." -msgstr "" -"Opcija „future“ nije dostupna za „%(verbose_name_plural)s“ jer " -"%(class_name)s.allow_future ima vrednost False." +"Future %(verbose_name_plural)s not available because " +"%(class_name)s.allow_future is False." +msgstr "Opcija „future“ nije dostupna za „%(verbose_name_plural)s“ jer %(class_name)s.allow_future ima vrednost False." +#: views/generic/dates.py:692 #, python-format msgid "Invalid date string “%(datestr)s” given format “%(format)s”" msgstr "Neispravan datum \"%(datestr)s\" za format \"%(format)s\"" +#: views/generic/detail.py:56 #, python-format msgid "No %(verbose_name)s found matching the query" msgstr "Nijedan objekat klase %(verbose_name)s nije nađen datim upitom." +#: views/generic/list.py:70 msgid "Page is not “last”, nor can it be converted to an int." msgstr "Stranica nije poslednja, niti može biti konvertovana u tip \"int\"." +#: views/generic/list.py:77 #, python-format msgid "Invalid page (%(page_number)s): %(message)s" msgstr "Neispravna strana (%(page_number)s): %(message)s" +#: views/generic/list.py:169 #, python-format msgid "Empty list and “%(class_name)s.allow_empty” is False." msgstr "Prazna lista i „%(class_name)s.allow_empty“ ima vrednost False." +#: views/static.py:48 msgid "Directory indexes are not allowed here." msgstr "Indeksi direktorijuma nisu dozvoljeni ovde." +#: views/static.py:50 #, python-format msgid "“%(path)s” does not exist" msgstr "„%(path)s“ ne postoji" +#: views/static.py:67 views/templates/directory_index.html:8 +#: views/templates/directory_index.html:11 #, python-format msgid "Index of %(directory)s" msgstr "Indeks direktorijuma %(directory)s" +#: views/templates/default_urlconf.html:7 +#: views/templates/default_urlconf.html:220 msgid "The install worked successfully! Congratulations!" msgstr "Instalacija je prošla uspešno. Čestitke!" +#: views/templates/default_urlconf.html:206 #, python-format msgid "" "View release notes for Django %(version)s" -msgstr "" -"Pogledajte napomene uz izdanje za Đango " -"%(version)s" +msgstr "Pogledajte napomene uz izdanje za Đango %(version)s" +#: views/templates/default_urlconf.html:221 #, python-format msgid "" -"You are seeing this page because DEBUG=True is in your settings file and you have not " -"configured any URLs." -msgstr "" -"Ova strana je prikazana jer je DEBUG=True u vašim podešavanjima i niste konfigurisali " -"nijedan URL." +"You are seeing this page because DEBUG=True is in your settings file " +"and you have not configured any URLs." +msgstr "Ova strana je prikazana jer je DEBUG=True u vašim podešavanjima i niste konfigurisali nijedan URL." +#: views/templates/default_urlconf.html:229 msgid "Django Documentation" msgstr "Đango dokumentacija" +#: views/templates/default_urlconf.html:230 msgid "Topics, references, & how-to’s" msgstr "Teme, reference, & kako-da" +#: views/templates/default_urlconf.html:238 msgid "Tutorial: A Polling App" msgstr "Uputstvo: aplikacija za glasanje" +#: views/templates/default_urlconf.html:239 msgid "Get started with Django" msgstr "Počnite sa Đangom" +#: views/templates/default_urlconf.html:247 msgid "Django Community" msgstr "Đango zajednica" +#: views/templates/default_urlconf.html:248 msgid "Connect, get help, or contribute" msgstr "Povežite se, potražite pomoć ili dajte doprinos" diff --git a/django/conf/locale/sv/LC_MESSAGES/django.mo b/django/conf/locale/sv/LC_MESSAGES/django.mo index 6685971077..060ef0963c 100644 Binary files a/django/conf/locale/sv/LC_MESSAGES/django.mo and b/django/conf/locale/sv/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/sv/LC_MESSAGES/django.po b/django/conf/locale/sv/LC_MESSAGES/django.po index 897dde5f11..d2f455d514 100644 --- a/django/conf/locale/sv/LC_MESSAGES/django.po +++ b/django/conf/locale/sv/LC_MESSAGES/django.po @@ -10,6 +10,7 @@ # Gustaf Hansen , 2015 # Jannis Leidel , 2011 # Jonathan Lindén, 2015 +# Jörgen Olofsson, 2024 # Ken Lewerentz, 2022 # Jonathan Lindén, 2014 # Mattias Hansson , 2016 @@ -23,10 +24,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: Anders Hovmöller , 2023\n" -"Language-Team: Swedish (http://www.transifex.com/django/django/language/" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-08-07 06:49+0000\n" +"Last-Translator: Jörgen Olofsson, 2024\n" +"Language-Team: Swedish (http://app.transifex.com/django/django/language/" "sv/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -310,6 +311,9 @@ msgstr "Tatariska" msgid "Udmurt" msgstr "Udmurtiska" +msgid "Uyghur" +msgstr "Uiguriska" + msgid "Ukrainian" msgstr "Ukrainska" @@ -357,6 +361,9 @@ msgstr "Sidan innehåller inga resultat" msgid "Enter a valid value." msgstr "Fyll i ett giltigt värde." +msgid "Enter a valid domain name." +msgstr "Fyll i ett giltigt domännamn." + msgid "Enter a valid URL." msgstr "Fyll i en giltig URL." @@ -380,14 +387,18 @@ msgstr "" "Fyll i en giltig 'slug', beståendes av bokstäver, siffror, understreck eller " "bindestreck i Unicode." -msgid "Enter a valid IPv4 address." -msgstr "Fyll i en giltig IPv4-adress." +#, python-format +msgid "Enter a valid %(protocol)s address." +msgstr "Fyll i en giltig %(protocol)s adress." -msgid "Enter a valid IPv6 address." -msgstr "Ange en giltig IPv6-adress." +msgid "IPv4" +msgstr "IPv4" -msgid "Enter a valid IPv4 or IPv6 address." -msgstr "Ange en giltig IPv4- eller IPv6-adress." +msgid "IPv6" +msgstr "IPv6" + +msgid "IPv4 or IPv6" +msgstr "IPv4 eller IPv6" msgid "Enter only digits separated by commas." msgstr "Fyll enbart i siffror separerade med kommatecken." @@ -412,6 +423,15 @@ msgid "Ensure this value is a multiple of step size %(limit_value)s." msgstr "" "Kontrollera att detta värde är multipel av stegstorlek %(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 "" +"Kontrollera att detta värde är en multipel med stegstorlek %(limit_value)s, " +"med början från %(offset)s, t ex. %(offset)s, %(valid_value1)s, " +"%(valid_value2)s och så vidare" + #, python-format msgid "" "Ensure this value has at least %(limit_value)d character (it has " diff --git a/django/conf/locale/tk/LC_MESSAGES/django.mo b/django/conf/locale/tk/LC_MESSAGES/django.mo index cb4590d191..2858350fa1 100644 Binary files a/django/conf/locale/tk/LC_MESSAGES/django.mo and b/django/conf/locale/tk/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/tk/LC_MESSAGES/django.po b/django/conf/locale/tk/LC_MESSAGES/django.po index 0d0f398b1e..ad0002618b 100644 --- a/django/conf/locale/tk/LC_MESSAGES/django.po +++ b/django/conf/locale/tk/LC_MESSAGES/django.po @@ -3,15 +3,15 @@ # Translators: # Mariusz Felisiak , 2020-2021 # Resul , 2020 -# Resul , 2022-2023 +# Resul , 2022-2024 # Welbeck Garli , 2020 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: Resul , 2022-2023\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-08-07 06:49+0000\n" +"Last-Translator: Resul , 2022-2024\n" "Language-Team: Turkmen (http://app.transifex.com/django/django/language/" "tk/)\n" "MIME-Version: 1.0\n" @@ -297,7 +297,7 @@ msgid "Udmurt" msgstr "Udmurt" msgid "Uyghur" -msgstr "" +msgstr "Uýgur" msgid "Ukrainian" msgstr "Ukrainçe" @@ -346,6 +346,9 @@ msgstr "Ol sahypada hiç hili netije ýok" msgid "Enter a valid value." msgstr "Dogry baha giriziň." +msgid "Enter a valid domain name." +msgstr "Dogry domen adyny giriziň." + msgid "Enter a valid URL." msgstr "Dogry URL giriziň." @@ -369,14 +372,18 @@ msgstr "" "Unikod harplaryndan, sanlardan, aşaky çyzyklardan ýa-da defislerden ybarat " "dogry “slug” giriziň." -msgid "Enter a valid IPv4 address." -msgstr "Dogry IPv4 salgysyny giriziň." +#, python-format +msgid "Enter a valid %(protocol)s address." +msgstr "Dogry %(protocol)s adresi giriziň." -msgid "Enter a valid IPv6 address." -msgstr "Dogry IPv6 salgysyny giriziň." +msgid "IPv4" +msgstr "IPv4" -msgid "Enter a valid IPv4 or IPv6 address." -msgstr "Dogry IPv4 ýa-da IPv6 adresi giriziň." +msgid "IPv6" +msgstr "IPv6" + +msgid "IPv4 or IPv6" +msgstr "IPv4 ýa IPv6" msgid "Enter only digits separated by commas." msgstr "Diňe otur bilen aýrylan sanlary giriziň." diff --git a/django/conf/locale/tr/LC_MESSAGES/django.mo b/django/conf/locale/tr/LC_MESSAGES/django.mo index dd8be7f237..70f6520400 100644 Binary files a/django/conf/locale/tr/LC_MESSAGES/django.mo and b/django/conf/locale/tr/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/tr/LC_MESSAGES/django.po b/django/conf/locale/tr/LC_MESSAGES/django.po index e9cee0499f..be5fa56bcb 100644 --- a/django/conf/locale/tr/LC_MESSAGES/django.po +++ b/django/conf/locale/tr/LC_MESSAGES/django.po @@ -2,7 +2,7 @@ # # Translators: # Ahmet Emre Aladağ , 2013 -# BouRock, 2015-2023 +# BouRock, 2015-2024 # BouRock, 2014-2015 # Caner Başaran , 2013 # Cihad GÜNDOĞDU , 2012 @@ -17,9 +17,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: BouRock, 2015-2023\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-08-07 06:49+0000\n" +"Last-Translator: BouRock, 2015-2024\n" "Language-Team: Turkish (http://app.transifex.com/django/django/language/" "tr/)\n" "MIME-Version: 1.0\n" @@ -354,6 +354,9 @@ msgstr "Bu sayfa hiç sonuç içermiyor" msgid "Enter a valid value." msgstr "Geçerli bir değer girin." +msgid "Enter a valid domain name." +msgstr "Geçerli bir etki alanı adı girin." + msgid "Enter a valid URL." msgstr "Geçerli bir URL girin." @@ -377,14 +380,18 @@ msgstr "" "Evrensel kod harflerden, sayılardan, altçizgilerden veya tirelerden oluşan " "geçerli bir “kısaltma” girin." -msgid "Enter a valid IPv4 address." -msgstr "Geçerli bir IPv4 adresi girin." +#, python-format +msgid "Enter a valid %(protocol)s address." +msgstr "Geçerli bir %(protocol)s adresi girin." -msgid "Enter a valid IPv6 address." -msgstr "Geçerli bir IPv6 adresi girin." +msgid "IPv4" +msgstr "IPv4" -msgid "Enter a valid IPv4 or IPv6 address." -msgstr "Geçerli bir IPv4 veya IPv6 adresi girin." +msgid "IPv6" +msgstr "IPv6" + +msgid "IPv4 or IPv6" +msgstr "IPv4 veya IPv6" msgid "Enter only digits separated by commas." msgstr "Sadece virgülle ayrılmış rakamlar girin." 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/locale/zh_Hant/LC_MESSAGES/django.mo b/django/conf/locale/zh_Hant/LC_MESSAGES/django.mo index b6726c5585..c9be56e3c5 100644 Binary files a/django/conf/locale/zh_Hant/LC_MESSAGES/django.mo and b/django/conf/locale/zh_Hant/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/zh_Hant/LC_MESSAGES/django.po b/django/conf/locale/zh_Hant/LC_MESSAGES/django.po index 61d827a15b..1671ebd942 100644 --- a/django/conf/locale/zh_Hant/LC_MESSAGES/django.po +++ b/django/conf/locale/zh_Hant/LC_MESSAGES/django.po @@ -2,23 +2,26 @@ # # Translators: # Chen Chun-Chia , 2015 +# yubike, 2024 # Eric Ho , 2013 # ilay , 2012 # Jannis Leidel , 2011 # mail6543210 , 2013 -# ming hsien tzang , 2011 +# 0a3cb7bfd0810218facdfb511e592a6d_8d19d07 , 2011 # tcc , 2011 # Tzu-ping Chung , 2016-2017 +# YAO WEN LIANG, 2024 # Yeh-Yung , 2013 +# yubike, 2024 # Yeh-Yung , 2012 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-09-27 22:40+0200\n" -"PO-Revision-Date: 2019-11-05 00:38+0000\n" -"Last-Translator: Ramiro Morales\n" -"Language-Team: Chinese (Taiwan) (http://www.transifex.com/django/django/" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-08-07 06:49+0000\n" +"Last-Translator: YAO WEN LIANG, 2024\n" +"Language-Team: Chinese (Taiwan) (http://app.transifex.com/django/django/" "language/zh_TW/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -32,6 +35,9 @@ msgstr "南非語" msgid "Arabic" msgstr "阿拉伯語" +msgid "Algerian Arabic" +msgstr "阿爾及利亞式阿拉伯語" + msgid "Asturian" msgstr "阿斯圖里亞斯語" @@ -56,6 +62,9 @@ msgstr "波士尼亞語" msgid "Catalan" msgstr "加泰隆語" +msgid "Central Kurdish (Sorani)" +msgstr "中部庫爾德語 (Sorani)" + msgid "Czech" msgstr "捷克語" @@ -147,7 +156,7 @@ msgid "Hungarian" msgstr "匈牙利語" msgid "Armenian" -msgstr "" +msgstr "亞美尼亞語" msgid "Interlingua" msgstr "國際語" @@ -155,6 +164,9 @@ msgstr "國際語" msgid "Indonesian" msgstr "印尼語" +msgid "Igbo" +msgstr "伊博語" + msgid "Ido" msgstr "伊多語" @@ -185,6 +197,9 @@ msgstr "康納達語" msgid "Korean" msgstr "韓語" +msgid "Kyrgyz" +msgstr "吉爾吉斯語" + msgid "Luxembourgish" msgstr "盧森堡語" @@ -206,6 +221,9 @@ msgstr "蒙古語" msgid "Marathi" msgstr "馬拉提語" +msgid "Malay" +msgstr "馬來語" + msgid "Burmese" msgstr "緬甸語" @@ -269,9 +287,15 @@ msgstr "坦米爾語" msgid "Telugu" msgstr "泰盧固語" +msgid "Tajik" +msgstr "塔吉克語" + msgid "Thai" msgstr "泰語" +msgid "Turkmen" +msgstr "土庫曼語" + msgid "Turkish" msgstr "土耳其語" @@ -281,6 +305,9 @@ msgstr "韃靼語" msgid "Udmurt" msgstr "烏德穆爾特語" +msgid "Uyghur" +msgstr "維吾爾語" + msgid "Ukrainian" msgstr "烏克蘭語" @@ -288,7 +315,7 @@ msgid "Urdu" msgstr "烏爾都語" msgid "Uzbek" -msgstr "" +msgstr "烏茲別克語" msgid "Vietnamese" msgstr "越南語" @@ -311,6 +338,11 @@ msgstr "靜態文件" msgid "Syndication" msgstr "聯播" +#. 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 "該頁碼並非整數" @@ -323,6 +355,9 @@ msgstr "該頁未包含任何內容" msgid "Enter a valid value." msgstr "請輸入有效的值。" +msgid "Enter a valid domain name." +msgstr "輸入有效的網域名稱。" + msgid "Enter a valid URL." msgstr "請輸入有效的 URL。" @@ -335,21 +370,25 @@ msgstr "請輸入有效的電子郵件地址。" #. Translators: "letters" means latin letters: a-z and A-Z. msgid "" "Enter a valid “slug” consisting of letters, numbers, underscores or hyphens." -msgstr "" +msgstr "輸入合適的 \"slug\" 字串,由字母、數字、底線與連字號組成。" msgid "" "Enter a valid “slug” consisting of Unicode letters, numbers, underscores, or " "hyphens." -msgstr "" +msgstr "輸入合適的 \"slug\" 字串,內含 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 "請輸入以逗號分隔的數字。" @@ -366,6 +405,18 @@ msgstr "請確認此數值是否小於或等於 %(limit_value)s。" msgid "Ensure this value is greater than or equal to %(limit_value)s." msgstr "請確認此數值是否大於或等於 %(limit_value)s。" +#, python-format +msgid "Ensure this value is a multiple of step size %(limit_value)s." +msgstr "請確認此數值是 %(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 "" +"請確認此數值是 %(limit_value)s的倍數。從 %(offset)s 起始,例如 %(offset)s, " +"%(valid_value1)s, %(valid_value2)s,以此類推。" + #, python-format msgid "" "Ensure this value has at least %(limit_value)d character (it has " @@ -397,20 +448,20 @@ msgstr[0] "請確認數字全長不超過 %(max)s 位。" #, 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] "請確認十進位數字不多於 %(max)s 位。" +msgstr[0] "確認小數不超過 %(max)s 位。" #, python-format msgid "" "Ensure that there are no more than %(max)s digit before the decimal point." msgid_plural "" "Ensure that there are no more than %(max)s digits before the decimal point." -msgstr[0] "請確認小數點前不多於 %(max)s 位。" +msgstr[0] "請確認小數點前不超過 %(max)s 位。" #, python-format msgid "" "File extension “%(extension)s” is not allowed. Allowed extensions are: " "%(allowed_extensions)s." -msgstr "" +msgstr "不允許副檔名為 “%(extension)s” 。可用的像是: %(allowed_extensions)s。" msgid "Null characters are not allowed." msgstr "不允許空(null)字元。" @@ -420,24 +471,28 @@ msgstr "和" #, python-format msgid "%(model_name)s with this %(field_labels)s already exists." -msgstr "這個 %(field_labels)s 在 %(model_name)s 已經存在。" +msgstr "包含 %(field_labels)s 的 %(model_name)s 已經存在。" + +#, python-format +msgid "Constraint “%(name)s” is violated." +msgstr "約束條件 “%(name)s” 被違反。" #, python-format msgid "Value %(value)r is not a valid choice." -msgstr "數值 %(value)r 不是有效的選擇。" +msgstr "數值 %(value)r 不是有效的選項。" msgid "This field cannot be null." msgstr "這個值不能是 null。" msgid "This field cannot be blank." -msgstr "這個欄位不能留白。" +msgstr "這個欄位不能為空。" #, python-format msgid "%(model_name)s with this %(field_label)s already exists." -msgstr "這個 %(field_label)s 在 %(model_name)s 已經存在。" +msgstr "包含 %(field_label)s 的 %(model_name)s 已經存在。" -#. 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." @@ -445,15 +500,15 @@ msgstr "%(field_label)s 在 %(date_field_label)s %(lookup_type)s 上必須唯一 #, python-format msgid "Field of type: %(field_type)s" -msgstr "欄位型態: %(field_type)s" +msgstr "欄位類型: %(field_type)s" #, python-format msgid "“%(value)s” value must be either True or False." -msgstr "" +msgstr "“%(value)s” 只能是 True 或 False。" #, python-format msgid "“%(value)s” value must be either True, False, or None." -msgstr "" +msgstr "“%(value)s” 只能是 True 或 False 或 None 。" msgid "Boolean (Either True or False)" msgstr "布林值 (True 或 False)" @@ -462,6 +517,9 @@ msgstr "布林值 (True 或 False)" msgid "String (up to %(max_length)s)" msgstr "字串 (至多 %(max_length)s 個字)" +msgid "String (unlimited)" +msgstr "字串 (無限)" + msgid "Comma-separated integers" msgstr "逗號分隔的整數" @@ -469,13 +527,13 @@ msgstr "逗號分隔的整數" msgid "" "“%(value)s” value has an invalid date format. It must be in YYYY-MM-DD " "format." -msgstr "" +msgstr "“%(value)s” 的日期格式錯誤。應該是 YYYY-MM-DD 才對。" #, python-format msgid "" "“%(value)s” value has the correct format (YYYY-MM-DD) but it is an invalid " "date." -msgstr "" +msgstr "“%(value)s” 的格式正確 (YYYY-MM-DD) 但日期有誤。" msgid "Date (without time)" msgstr "日期 (不包括時間)" @@ -485,19 +543,21 @@ msgid "" "“%(value)s” value has an invalid format. It must be in YYYY-MM-DD HH:MM[:ss[." "uuuuuu]][TZ] format." msgstr "" +"“%(value)s” 的格式錯誤。應該是 YYYY-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 "" +"“%(value)s” 的格式正確 (YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]) 但日期/時間有誤。" msgid "Date (with time)" msgstr "日期 (包括時間)" #, python-format msgid "“%(value)s” value must be a decimal number." -msgstr "" +msgstr "“%(value)s” 必須是十進位數。" msgid "Decimal number" msgstr "十進位數" @@ -506,10 +566,10 @@ msgstr "十進位數" msgid "" "“%(value)s” value has an invalid format. It must be in [DD] [[HH:]MM:]ss[." "uuuuuu] format." -msgstr "" +msgstr "“%(value)s” 的格式錯誤。格式必須為 [DD] [[HH:]MM:]ss[.uuuuuu] 。" msgid "Duration" -msgstr "時間長" +msgstr "時長" msgid "Email address" msgstr "電子郵件地址" @@ -519,20 +579,23 @@ msgstr "檔案路徑" #, python-format msgid "“%(value)s” value must be a float." -msgstr "" +msgstr "“%(value)s” 必須是浮點小數。" msgid "Floating point number" msgstr "浮點數" #, python-format msgid "“%(value)s” value must be an integer." -msgstr "" +msgstr "“%(value)s” 必須是整數。" msgid "Integer" msgstr "整數" msgid "Big (8 byte) integer" -msgstr "大整數 (8 位元組)" +msgstr "大整數 (8位元組)" + +msgid "Small integer" +msgstr "小整數" msgid "IPv4 address" msgstr "IPv4 地址" @@ -542,11 +605,14 @@ msgstr "IP 位址" #, python-format msgid "“%(value)s” value must be either None, True or False." -msgstr "" +msgstr "“%(value)s” 必須是 None, True 或 False。" msgid "Boolean (Either True, False or None)" msgstr "布林值 (True, False 或 None)" +msgid "Positive big integer" +msgstr "大的正整數" + msgid "Positive integer" msgstr "正整數" @@ -555,10 +621,7 @@ msgstr "正小整數" #, python-format msgid "Slug (up to %(max_length)s)" -msgstr "可讀網址 (長度最多 %(max_length)s)" - -msgid "Small integer" -msgstr "小整數" +msgstr "Slug (最多 %(max_length)s個字)" msgid "Text" msgstr "文字" @@ -567,42 +630,48 @@ msgstr "文字" msgid "" "“%(value)s” value has an invalid format. It must be in HH:MM[:ss[.uuuuuu]] " "format." -msgstr "" +msgstr "“%(value)s” 格式錯誤。格式必須為 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 "" +msgstr "“%(value)s” 的格式正確 (HH:MM[:ss[.uuuuuu]]),但時間有誤。" msgid "Time" msgstr "時間" msgid "URL" -msgstr "URL" +msgstr "網址" msgid "Raw binary data" msgstr "原始二進制數據" #, python-format msgid "“%(value)s” is not a valid UUID." -msgstr "" +msgstr "“%(value)s” 不是有效的 UUID。" msgid "Universally unique identifier" -msgstr "" +msgstr "通用唯一識別碼" msgid "File" msgstr "檔案" msgid "Image" -msgstr "影像" +msgstr "圖像" + +msgid "A JSON object" +msgstr "JSON 物件" + +msgid "Value must be valid JSON." +msgstr "必須是有效的 JSON 值" #, python-format msgid "%(model)s instance with %(field)s %(value)r does not exist." msgstr "%(field)s 為 %(value)r 的 %(model)s 物件不存在。" msgid "Foreign Key (type determined by related field)" -msgstr "外鍵 (型態由關連欄位決定)" +msgstr "外鍵(類型由相關欄位決定)" msgid "One-to-one relationship" msgstr "一對一關連" @@ -644,7 +713,7 @@ msgstr "輸入有效的時間長。" #, python-brace-format msgid "The number of days must be between {min_days} and {max_days}." -msgstr "" +msgstr "天數必須介於 {min_days} 到 {max_days} 天" msgid "No file was submitted. Check the encoding type on the form." msgstr "沒有檔案被送出。請檢查表單的編碼類型。" @@ -662,16 +731,16 @@ msgid_plural "" msgstr[0] "請確認這個檔名至多包含 %(max)d 個字 (目前為 %(length)d)。" msgid "Please either submit a file or check the clear checkbox, not both." -msgstr "請提交一個檔案或確認清除核可項, 不能兩者都做。" +msgstr "請提交一個檔案或勾選清除選項,但不能同時進行。" msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." -msgstr "上傳一個有效的圖檔。你上傳的檔案為非圖片,不然就是損壞的圖檔。" +msgstr "請上傳一個有效的圖檔。你上傳的檔案不是圖片或是已損壞的圖檔。" #, python-format msgid "Select a valid choice. %(value)s is not one of the available choices." -msgstr "請選擇有效的項目, %(value)s 不是一個可用的選擇。" +msgstr "請選擇有效的選項, %(value)s 不是一個可用的選項。" msgid "Enter a list of values." msgstr "請輸入一個列表的值。" @@ -682,6 +751,9 @@ msgstr "請輸入完整的值。" msgid "Enter a valid UUID." msgstr "請輸入有效的 UUID。" +msgid "Enter a valid JSON." +msgstr "輸入有效的 JSON。" + #. Translators: This is the default suffix added to form field labels msgid ":" msgstr ":" @@ -690,18 +762,23 @@ msgstr ":" msgid "(Hidden field %(name)s) %(error)s" msgstr "(隱藏欄位 %(name)s) %(error)s" -msgid "ManagementForm data is missing or has been tampered with" -msgstr "ManagementForm 資料缺失或遭竄改" +#, python-format +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 "" +"ManagementForm 資料遺失或被篡改。缺少欄位:%(field_names)s。如果問題持續存" +"在,您可能需要提交錯誤報告。" #, python-format -msgid "Please submit %d or fewer forms." -msgid_plural "Please submit %d or fewer forms." -msgstr[0] "請送出不多於 %d 個表單。" +msgid "Please submit at most %(num)d form." +msgid_plural "Please submit at most %(num)d forms." +msgstr[0] "請送出最多 %(num)d 個表單。" #, python-format -msgid "Please submit %d or more forms." -msgid_plural "Please submit %d or more forms." -msgstr[0] "請送出多於 %d 個表單。" +msgid "Please submit at least %(num)d form." +msgid_plural "Please submit at least %(num)d forms." +msgstr[0] "請送出最少 %(num)d 個表單。" msgid "Order" msgstr "排序" @@ -735,13 +812,15 @@ msgstr "選擇有效的選項: 此選擇不在可用的選項中。" #, python-format msgid "“%(pk)s” is not a valid value." -msgstr "" +msgstr "“%(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 "" +"%(datetime)s 無法被轉換成 %(current_timezone)s時區格式; 可能內容有誤或不存" +"在。" msgid "Clear" msgstr "清除" @@ -761,15 +840,7 @@ msgstr "是" msgid "No" msgstr "否" -msgid "Year" -msgstr "" - -msgid "Month" -msgstr "" - -msgid "Day" -msgstr "" - +#. Translators: Please do not add spaces around commas. msgid "yes,no,maybe" msgstr "是、否、也許" @@ -1032,7 +1103,7 @@ msgstr "這是無效的 IPv6 位址。" #, python-format msgctxt "String to return when truncating text" msgid "%(truncated_text)s…" -msgstr "" +msgstr "%(truncated_text)s…" msgid "or" msgstr "或" @@ -1042,37 +1113,34 @@ msgid ", " msgstr ", " #, python-format -msgid "%d year" -msgid_plural "%d years" -msgstr[0] "%d 年" +msgid "%(num)d year" +msgid_plural "%(num)d years" +msgstr[0] "%(num)d 年" #, python-format -msgid "%d month" -msgid_plural "%d months" -msgstr[0] "%d 月" +msgid "%(num)d month" +msgid_plural "%(num)d months" +msgstr[0] "%(num)d 月" #, python-format -msgid "%d week" -msgid_plural "%d weeks" -msgstr[0] "%d 週" +msgid "%(num)d week" +msgid_plural "%(num)d weeks" +msgstr[0] "%(num)d 週" #, python-format -msgid "%d day" -msgid_plural "%d days" -msgstr[0] "%d 日" +msgid "%(num)d day" +msgid_plural "%(num)d days" +msgstr[0] "%(num)d 日" #, python-format -msgid "%d hour" -msgid_plural "%d hours" -msgstr[0] "%d 時" +msgid "%(num)d hour" +msgid_plural "%(num)d hours" +msgstr[0] "%(num)d 小時" #, python-format -msgid "%d minute" -msgid_plural "%d minutes" -msgstr[0] "%d 分" - -msgid "0 minutes" -msgstr "0 分" +msgid "%(num)d minute" +msgid_plural "%(num)d minutes" +msgstr[0] "%(num)d 分" msgid "Forbidden" msgstr "禁止" @@ -1082,24 +1150,32 @@ msgstr "CSRF 驗證失敗。已中止請求。" 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 "" +"您看到此消息是因為這個 HTTPS 網站要求您的網路瀏覽器發送一個“Referer header”," +"但並沒有被發送。出於安全原因,需要此標頭來確保您的瀏覽器沒有被第三方劫持。" 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 "" +"若您的瀏覽器設定為將「Referer」標頭關閉,請重新為這個網站、HTTPS 連線、或" +"「same-origin」請求啟用它。" 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 "" +"若您使用 標籤或包含" +"「Referrer-Policy: no-referrer」標頭,請將其移除。 CSRF 保護要求「Referer」標" +"頭進行嚴格參照檢查。若你擔心隱私問題,可使用如 來連" +"結到第三方網站。" msgid "" "You are seeing this message because this site requires a CSRF cookie when " @@ -1113,6 +1189,8 @@ 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 "" +"若你的瀏覽器設定為將 cookie 關閉,請重新為這個網站或「same-origin」請求啟用" +"它。" msgid "More information is available with DEBUG=True." msgstr "設定 DEBUG=True 以獲得更多資訊。" @@ -1124,13 +1202,13 @@ msgid "Date out of range" msgstr "日期超過範圍" msgid "No month specified" -msgstr "不指定月份" +msgstr "沒有指定月份" msgid "No day specified" -msgstr "不指定日期" +msgstr "沒有指定日期" msgid "No week specified" -msgstr "不指定週數" +msgstr "沒有指定週數" #, python-format msgid "No %(verbose_name_plural)s available" @@ -1141,19 +1219,19 @@ msgid "" "Future %(verbose_name_plural)s not available because %(class_name)s." "allow_future is False." msgstr "" -"未來的 %(verbose_name_plural)s 不可用,因 %(class_name)s.allow_future 為 " -"False." +"未來的 %(verbose_name_plural)s 不可用,因為 %(class_name)s.allow_future 設置" +"為 False." #, python-format msgid "Invalid date string “%(datestr)s” given format “%(format)s”" -msgstr "" +msgstr "日期字串 “%(datestr)s” 不符合 “%(format)s” 格式。" #, python-format msgid "No %(verbose_name)s found matching the query" msgstr "無 %(verbose_name)s 符合本次搜尋" msgid "Page is not “last”, nor can it be converted to an int." -msgstr "" +msgstr "頁面不是最後一頁,也無法被轉換為整數。" #, python-format msgid "Invalid page (%(page_number)s): %(message)s" @@ -1161,49 +1239,46 @@ msgstr "無效的頁面 (%(page_number)s): %(message)s" #, python-format msgid "Empty list and “%(class_name)s.allow_empty” is False." -msgstr "" +msgstr "列表是空的,並且 “%(class_name)s.allow_empty” 是 False 。" msgid "Directory indexes are not allowed here." msgstr "這裡不允許目錄索引。" #, python-format msgid "“%(path)s” does not exist" -msgstr "" +msgstr "“%(path)s” 不存在。" #, python-format msgid "Index of %(directory)s" msgstr "%(directory)s 的索引" -msgid "Django: the Web framework for perfectionists with deadlines." -msgstr "Django:為有時間壓力的完美主義者設計的網站框架。" - -#, python-format -msgid "" -"View release notes for Django %(version)s" -msgstr "" -"查看 Django %(version)s 的發行筆記" - msgid "The install worked successfully! Congratulations!" msgstr "安裝成功!恭喜!" #, python-format msgid "" -"You are seeing this page because DEBUG=True is in your settings file and you have not configured any " -"URLs." +"View release notes for Django %(version)s" msgstr "" -"你看到這個訊息,是因為你在 Django 設定檔中包含 DEBUG = True,且尚未配置任何網址。開始工作吧!" +"查看 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." +msgstr "" +"你現在看到這個頁面,是因為在設定檔中設置了 DEBUG = True,且尚未配置任何網址。" msgid "Django Documentation" msgstr "Django 文件" msgid "Topics, references, & how-to’s" -msgstr "" +msgstr "主題、參考、教學" msgid "Tutorial: A Polling App" msgstr "教學:投票應用" @@ -1215,4 +1290,4 @@ msgid "Django Community" msgstr "Django 社群" msgid "Connect, get help, or contribute" -msgstr "聯繫、求助、貢獻" +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/actions.py b/django/contrib/admin/actions.py index eefb63837e..865c16aff2 100644 --- a/django/contrib/admin/actions.py +++ b/django/contrib/admin/actions.py @@ -61,7 +61,7 @@ def delete_selected(modeladmin, request, queryset): if perms_needed or protected: title = _("Cannot delete %(name)s") % {"name": objects_name} else: - title = _("Are you sure?") + title = _("Delete multiple objects") context = { **modeladmin.admin_site.each_context(request), diff --git a/django/contrib/admin/checks.py b/django/contrib/admin/checks.py index 94e700cf68..a4d7066d10 100644 --- a/django/contrib/admin/checks.py +++ b/django/contrib/admin/checks.py @@ -1184,7 +1184,7 @@ class ModelAdminChecks(BaseModelAdminChecks): ) ] else: - if not isinstance(field, (models.DateField, models.DateTimeField)): + if field.get_internal_type() not in {"DateField", "DateTimeField"}: return must_be( "a DateField or DateTimeField", option="date_hierarchy", diff --git a/django/contrib/admin/locale/az/LC_MESSAGES/django.mo b/django/contrib/admin/locale/az/LC_MESSAGES/django.mo index 356192130d..7c6990af6c 100644 Binary files a/django/contrib/admin/locale/az/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/az/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/az/LC_MESSAGES/django.po b/django/contrib/admin/locale/az/LC_MESSAGES/django.po index 1a028a3789..929b2945ba 100644 --- a/django/contrib/admin/locale/az/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/az/LC_MESSAGES/django.po @@ -5,15 +5,17 @@ # Emin Mastizada , 2016 # Konul Allahverdiyeva , 2016 # Nicat Məmmədov , 2022 +# Nijat Mammadov, 2024 +# Sevdimali , 2024 # Zulfugar Ismayilzadeh , 2017 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: Nicat Məmmədov \n" -"Language-Team: Azerbaijani (http://www.transifex.com/django/django/language/" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-08-07 07:05+0000\n" +"Last-Translator: Sevdimali , 2024\n" +"Language-Team: Azerbaijani (http://app.transifex.com/django/django/language/" "az/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -23,7 +25,7 @@ msgstr "" #, python-format msgid "Delete selected %(verbose_name_plural)s" -msgstr "Seçilmiş %(verbose_name_plural)s-ləri sil" +msgstr "Seçilmiş \"%(verbose_name_plural)s\"ləri/ları sil" #, python-format msgid "Successfully deleted %(count)d %(items)s." @@ -31,10 +33,10 @@ msgstr "%(count)d %(items)s uğurla silindi." #, python-format msgid "Cannot delete %(name)s" -msgstr "%(name)s silinmir" +msgstr "%(name)s silinə bilməz" msgid "Are you sure?" -msgstr "Əminsiniz?" +msgstr "Əminsinizmi?" msgid "Administration" msgstr "Administrasiya" @@ -49,7 +51,7 @@ msgid "No" msgstr "Yox" msgid "Unknown" -msgstr "Bilinmir" +msgstr "Naməlum" msgid "Any date" msgstr "İstənilən tarix" @@ -83,8 +85,8 @@ msgid "" "Please enter the correct %(username)s and password for a staff account. Note " "that both fields may be case-sensitive." msgstr "" -"Lütfən, istifadəçi hesabı üçün doğru %(username)s və parol daxil olun. " -"Nəzərə alın ki, hər iki sahə böyük/kiçik hərflərə həssasdırlar." +"Lütfən, istifadəçi hesabı üçün doğru %(username)s və şifrə daxil edin. " +"Nəzərə alın ki, hər iki xana böyük-kiçik hərflərə həssasdırlar." msgid "Action:" msgstr "Əməliyyat:" @@ -123,7 +125,7 @@ msgid "object repr" msgstr "obyekt repr" msgid "action flag" -msgstr "bayraq" +msgstr "əməliyyat bayrağı" msgid "change message" msgstr "dəyişmə mesajı" @@ -175,13 +177,16 @@ msgid "No fields changed." msgstr "Heç bir sahə dəyişmədi." msgid "None" -msgstr "Heç nə" +msgstr "Heç biri" msgid "Hold down “Control”, or “Command” on a Mac, to select more than one." msgstr "" "Birdən çox seçmək üçün “Control” və ya Mac üçün “Command” düyməsini basılı " "tutun." +msgid "Select this object for an action - {}" +msgstr "Əməliyyat üçün bu obyekti seçin - {}" + #, python-brace-format msgid "The {name} “{obj}” was added successfully." msgstr "{name} “{obj}” uğurla əlavə edildi." @@ -202,11 +207,6 @@ msgid "" msgstr "" "{name} “{obj}” uğurla dəyişdirildi. Təkrar aşağıdan dəyişdirə bilərsiz." -#, python-brace-format -msgid "The {name} “{obj}” was added successfully. You may edit it again below." -msgstr "" -"{name} “{obj}” uğurla əlavə edildi. Bunu təkrar aşağıdan dəyişdirə bilərsiz." - #, python-brace-format msgid "" "The {name} “{obj}” was changed successfully. You may add another {name} " @@ -223,8 +223,8 @@ msgid "" "Items must be selected in order to perform actions on them. No items have " "been changed." msgstr "" -"Biz elementlər üzərində nəsə əməliyyat aparmaq üçün siz onları seçməlisiniz. " -"Heç bir element dəyişmədi." +"Elementlər üzərində əməliyyat aparmaq üçün, siz onları seçməlisiniz. Heç bir " +"element dəyişmədi." msgid "No action selected." msgstr "Heç bir əməliyyat seçilmədi." @@ -235,7 +235,7 @@ msgstr "%(name)s “%(obj)s” uğurla silindi." #, python-format msgid "%(name)s with ID “%(key)s” doesn’t exist. Perhaps it was deleted?" -msgstr "“%(key)s” ID nömrəli %(name)s mövcud deyil. Silinmiş ola bilər?" +msgstr "“%(key)s” ID nömrəli %(name)s mövcud deyil. Bəlkə silinib?" #, python-format msgid "Add %s" @@ -250,23 +250,23 @@ msgid "View %s" msgstr "%s gör" msgid "Database error" -msgstr "Bazada xəta" +msgstr "Verilənlər bazası xətası" #, python-format msgid "%(count)s %(name)s was changed successfully." msgid_plural "%(count)s %(name)s were changed successfully." msgstr[0] "%(count)s %(name)s uğurlu dəyişdirildi." -msgstr[1] "%(count)s %(name)s uğurlu dəyişdirildi." +msgstr[1] "%(count)s %(name)s uğurla dəyişdirildi." #, python-format msgid "%(total_count)s selected" msgid_plural "All %(total_count)s selected" msgstr[0] "%(total_count)s seçili" -msgstr[1] "Bütün %(total_count)s seçili" +msgstr[1] "Bütün %(total_count)s seçildi" #, python-format msgid "0 of %(cnt)s selected" -msgstr "%(cnt)s-dan 0 seçilib" +msgstr "%(cnt)s-dan/dən 0 seçilib" #, python-format msgid "Change history: %s" @@ -284,8 +284,8 @@ msgid "" "Deleting %(class_name)s %(instance)s would require deleting the following " "protected related objects: %(related_objects)s" msgstr "" -"%(class_name)s %(instance)s silmə əlaqəli qorunmalı obyektləri silməyi tələb " -"edir: %(related_objects)s" +"%(class_name)s %(instance)s silinməsi %(related_objects)s obyektlərinin də " +"silinməsinə gətirib çıxaracaq" msgid "Django site admin" msgstr "Django sayt administratoru" @@ -325,38 +325,41 @@ msgid "" "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 "" -"Xəta baş verdi. Problem sayt administratorlarına epoçt vasitəsi ilə " +"Xəta baş verdi. Problem sayt administratorlarına e-poçt vasitəsi ilə " "bildirildi və qısa bir zamanda həll olunacaq. Anlayışınız üçün təşəkkür " "edirik." msgid "Run the selected action" -msgstr "Seçdiyim əməliyyatı yerinə yetir" +msgstr "Seçilən əməliyyatı yerinə yetir" msgid "Go" -msgstr "Getdik" +msgstr "İrəli" msgid "Click here to select the objects across all pages" -msgstr "Bütün səhifələr üzrə obyektləri seçmək üçün bura tıqlayın" +msgstr "Bütün səhifələr üzrə obyektləri seçmək üçün bura klikləyin" #, python-format msgid "Select all %(total_count)s %(module_name)s" -msgstr "Bütün %(total_count)s sayda %(module_name)s seç" +msgstr "Hamısını seç (%(total_count)s %(module_name)s) " msgid "Clear selection" msgstr "Seçimi təmizlə" +msgid "Breadcrumbs" +msgstr "Menyu sətri" + #, python-format msgid "Models in the %(name)s application" -msgstr "%(name)s proqramındakı modellər" +msgstr "%(name)s tətbiqetməsindəki modellər" msgid "Add" msgstr "Əlavə et" msgid "View" -msgstr "Gör" +msgstr "Bax" msgid "You don’t have permission to view or edit anything." -msgstr "Nəyi isə görmək və ya redaktə etmək icazəniz yoxdur." +msgstr "Heç nəyə baxmağa və ya dəyişməyə icazəniz yoxdur." msgid "" "First, enter a username and password. Then, you’ll be able to edit more user " @@ -371,24 +374,42 @@ msgstr "İstifadəçi adını və şifrəni daxil edin." msgid "Change password" msgstr "Şifrəni dəyiş" -msgid "Please correct the error below." -msgstr "Lütfən aşağıdakı xətanı düzəldin." +msgid "Set password" +msgstr "Şifrə təyin et" -msgid "Please correct the errors below." -msgstr "Lütfən aşağıdakı səhvləri düzəldin." +msgid "Please correct the error below." +msgid_plural "Please correct the errors below." +msgstr[0] "Lütfən, aşağıdakı xətanı düzəldin." +msgstr[1] "Lütfən, aşağıdakı xətaları düzəldin." #, python-format msgid "Enter a new password for the user %(username)s." -msgstr "%(username)s üçün yeni şifrə daxil edin." +msgstr "%(username)s istifadəçisi üçün yeni şifrə daxil edin." + +msgid "" +"This action will enable password-based authentication for " +"this user." +msgstr "" +"Bu əməliyyat bu istifadəçi üçün şifrə əsaslı autentifikasiyanı aktiv " +"edəcək." + +msgid "Disable password-based authentication" +msgstr "Şifrə əsaslı autentifikasiyanı ləğv elə." + +msgid "Enable password-based authentication" +msgstr "Şifrə əsaslı autentifikasiyanı aktivləşdir." + +msgid "Skip to main content" +msgstr "Əsas məzmuna keç" msgid "Welcome," msgstr "Xoş gördük," msgid "View site" -msgstr "Saytı ziyarət et" +msgstr "Sayta bax" msgid "Documentation" -msgstr "Sənədləşdirmə" +msgstr "Dokumentasiya" msgid "Log out" msgstr "Çıx" @@ -401,11 +422,17 @@ msgid "History" msgstr "Tarix" msgid "View on site" -msgstr "Saytda göstər" +msgstr "Saytda bax" msgid "Filter" msgstr "Süzgəc" +msgid "Hide counts" +msgstr "Sayı gizlət" + +msgid "Show counts" +msgstr "Sayı göstər" + msgid "Clear all filters" msgstr "Bütün filterləri təmizlə" @@ -419,6 +446,15 @@ msgstr "Sıralama prioriteti: %(priority_number)s" msgid "Toggle sorting" msgstr "Sıralamanı çevir" +msgid "Toggle theme (current theme: auto)" +msgstr "Görünüşü dəyiş (halhazırkı: avtomatik)" + +msgid "Toggle theme (current theme: light)" +msgstr "Görünüşü dəyiş (halhazırkı: aydın)" + +msgid "Toggle theme (current theme: dark)" +msgstr "Görünüşü dəyiş (halhazırkı: qaranlıq)" + msgid "Delete" msgstr "Sil" @@ -455,7 +491,7 @@ msgid "Yes, I’m sure" msgstr "Bəli, əminəm" msgid "No, take me back" -msgstr "Xeyr, məni geri götür" +msgstr "Xeyr, geri qayıt" msgid "Delete multiple objects" msgstr "Bir neçə obyekt sil" @@ -487,7 +523,7 @@ msgstr "" "obyektlər və ona bağlı digər obyektlər də silinəcək:" msgid "Delete?" -msgstr "Silək?" +msgstr "Silinsin?" #, python-format msgid " By %(filter_title)s " @@ -505,14 +541,26 @@ msgstr "Mənim əməliyyatlarım" msgid "None available" msgstr "Heç nə yoxdur" +msgid "Added:" +msgstr "Əlavə olunub:" + +msgid "Changed:" +msgstr "Dəyişdirilib:" + +msgid "Deleted:" +msgstr "Silinib:" + msgid "Unknown content" -msgstr "Naməlum" +msgstr "Naməlum məzmun" msgid "" "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 "" +"Verilənlər bazanızın quraşdırılması ilə bağlı problem var. Müvafiq " +"cədvəllərinin yaradıldığından və verilənlər bazasının müvafiq istifadəçi " +"tərəfindən oxuna biləcəyindən əmin olun." #, python-format msgid "" @@ -526,13 +574,16 @@ msgid "Forgotten your password or username?" msgstr "Şifrə və ya istifadəçi adını unutmusuz?" msgid "Toggle navigation" -msgstr "" +msgstr "Naviqasiyanı dəyiş" + +msgid "Sidebar" +msgstr "Yan panel" msgid "Start typing to filter…" msgstr "Filterləmək üçün yazın..." msgid "Filter navigation items" -msgstr "" +msgstr "Naviqasiya elementlərini filterlə" msgid "Date/time" msgstr "Tarix/vaxt" @@ -544,15 +595,16 @@ msgid "Action" msgstr "Əməliyyat" msgid "entry" -msgstr "" - -msgid "entries" -msgstr "" +msgid_plural "entries" +msgstr[0] "daxiletmə" +msgstr[1] "daxiletmələr" msgid "" "This object doesn’t have a change history. It probably wasn’t added via this " "admin site." msgstr "" +"Bu obyektin dəyişiklik tarixçəsi yoxdur. Yəqin ki, bu admin saytı vasitəsilə " +"əlavə olunmayıb." msgid "Show all" msgstr "Hamısını göstər" @@ -561,7 +613,7 @@ msgid "Save" msgstr "Yadda saxla" msgid "Popup closing…" -msgstr "Qəfil pəncərə qapatılır…" +msgstr "Qəfil pəncərə qapadılır…" msgid "Search" msgstr "Axtar" @@ -586,10 +638,10 @@ msgid "Save and continue editing" msgstr "Yadda saxla və redaktəyə davam et" msgid "Save and view" -msgstr "Saxla və gör" +msgstr "Yadda saxla və bax" msgid "Close" -msgstr "Qapat" +msgstr "Bağla" #, python-format msgid "Change selected %(model)s" @@ -605,10 +657,10 @@ msgstr "Seçilmiş %(model)s sil" #, python-format msgid "View selected %(model)s" -msgstr "" +msgstr "Bax: seçilmiş %(model)s " msgid "Thanks for spending some quality time with the web site today." -msgstr "" +msgstr "Bu gün veb saytla keyfiyyətli vaxt keçirdiyiniz üçün təşəkkür edirik." msgid "Log in again" msgstr "Yenidən daxil ol" @@ -623,6 +675,8 @@ msgid "" "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 "" +"Zəhmət olmasa təhlükəsizlik naminə köhnə şifrənizi daxil edin və sonra yeni " +"şifrənizi iki dəfə daxil edin ki, düzgün daxil yazdığınızı yoxlaya bilək." msgid "Change my password" msgstr "Şifrəmi dəyiş" @@ -658,7 +712,7 @@ msgid "" "We’ve emailed you instructions for setting your password, if an account " "exists with the email you entered. You should receive them shortly." msgstr "" -"Şifrəni təyin etmək üçün lazım olan addımlar sizə göndərildi (əgər bu epoçt " +"Şifrəni təyin etmək üçün lazım olan addımlar sizə göndərildi (əgər bu e-poçt " "ünvanı ilə hesab varsa təbii ki). Elektron məktub qısa bir müddət ərzində " "sizə çatacaq." @@ -666,6 +720,8 @@ msgid "" "If you don’t receive an email, please make sure you’ve entered the address " "you registered with, and check your spam folder." msgstr "" +"E-poçt gəlməsə, qeydiyyatdan keçdiyiniz e-poçt ünvanını doğru daxil " +"etdiyinizə əmin olun və spam qovluğunuzu yoxlayın." #, python-format msgid "" @@ -701,6 +757,9 @@ msgstr "E-poçt:" msgid "Reset my password" msgstr "Şifrəmi sıfırla" +msgid "Select all objects on this page for an action" +msgstr "Əməliyyat üçün bu səhifədəki bütün obyektləri seçin" + msgid "All dates" msgstr "Bütün tarixlərdə" diff --git a/django/contrib/admin/locale/be/LC_MESSAGES/django.mo b/django/contrib/admin/locale/be/LC_MESSAGES/django.mo index e591705bb2..f23565c180 100644 Binary files a/django/contrib/admin/locale/be/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/be/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/be/LC_MESSAGES/django.po b/django/contrib/admin/locale/be/LC_MESSAGES/django.po index 8ce3066509..4904d355c2 100644 --- a/django/contrib/admin/locale/be/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/be/LC_MESSAGES/django.po @@ -2,14 +2,15 @@ # # Translators: # Viktar Palstsiuk , 2015 -# znotdead , 2016-2017,2019-2021,2023 +# znotdead , 2016-2017,2019-2021,2023-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: znotdead , 2016-2017,2019-2021,2023\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-08-07 07:05+0000\n" +"Last-Translator: znotdead , " +"2016-2017,2019-2021,2023-2024\n" "Language-Team: Belarusian (http://app.transifex.com/django/django/language/" "be/)\n" "MIME-Version: 1.0\n" @@ -201,10 +202,6 @@ msgid "" "The {name} “{obj}” was changed successfully. You may edit it again below." 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} " @@ -374,6 +371,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] "Калі ласка, выпраўце памылкy, адзначаную ніжэй." @@ -385,6 +385,19 @@ msgstr[3] "Калі ласка, выпраўце памылкі, адзнача 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/locale/bg/LC_MESSAGES/django.mo b/django/contrib/admin/locale/bg/LC_MESSAGES/django.mo index 56a37d2239..3e89f3e437 100644 Binary files a/django/contrib/admin/locale/bg/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/bg/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/bg/LC_MESSAGES/django.po b/django/contrib/admin/locale/bg/LC_MESSAGES/django.po index a49191498e..d8e8dc8a1e 100644 --- a/django/contrib/admin/locale/bg/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/bg/LC_MESSAGES/django.po @@ -1,7 +1,7 @@ # This file is distributed under the same license as the Django package. # # Translators: -# arneatec , 2022-2023 +# arneatec , 2022-2024 # Boris Chervenkov , 2012 # Claude Paroz , 2014 # Jannis Leidel , 2011 @@ -13,9 +13,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: arneatec , 2022-2023\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-08-07 07:05+0000\n" +"Last-Translator: arneatec , 2022-2024\n" "Language-Team: Bulgarian (http://app.transifex.com/django/django/language/" "bg/)\n" "MIME-Version: 1.0\n" @@ -185,7 +185,7 @@ msgstr "" "Задръжте “Control”, или “Command” на Mac, за да изберете повече от едно." msgid "Select this object for an action - {}" -msgstr "" +msgstr "Изберете този обект за действие - {}" #, python-brace-format msgid "The {name} “{obj}” was added successfully." @@ -208,12 +208,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} " @@ -381,6 +375,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] "Моля, поправете грешката по-долу." @@ -390,6 +387,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 "Пропуснете към основното съдържание" @@ -419,10 +429,10 @@ msgid "Filter" msgstr "Филтър" msgid "Hide counts" -msgstr "" +msgstr "Скрий брояча" msgid "Show counts" -msgstr "" +msgstr "Покажи брояча" msgid "Clear all filters" msgstr "Изчисти всички филтри" @@ -532,13 +542,13 @@ msgid "None available" msgstr "Няма налични" msgid "Added:" -msgstr "" +msgstr "Добавени:" msgid "Changed:" -msgstr "" +msgstr "Променени:" msgid "Deleted:" -msgstr "" +msgstr "Изтрити:" msgid "Unknown content" msgstr "Неизвестно съдържание" @@ -748,7 +758,7 @@ msgid "Reset my password" msgstr "Задай новата ми парола" msgid "Select all objects on this page for an action" -msgstr "" +msgstr "Изберете всички обекти на този страница за действие" msgid "All dates" msgstr "Всички дати" diff --git a/django/contrib/admin/locale/bg/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/bg/LC_MESSAGES/djangojs.mo index 31a2ae6934..a3eab438aa 100644 Binary files a/django/contrib/admin/locale/bg/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/bg/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/bg/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/bg/LC_MESSAGES/djangojs.po index 7716b0fcdc..4ec3a50a15 100644 --- a/django/contrib/admin/locale/bg/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/bg/LC_MESSAGES/djangojs.po @@ -1,16 +1,16 @@ # This file is distributed under the same license as the Django package. # # Translators: -# arneatec , 2022-2023 +# arneatec , 2022-2024 # Jannis Leidel , 2011 # Venelin Stoykov , 2015-2016 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-09-18 15:04-0300\n" -"PO-Revision-Date: 2023-12-04 07:59+0000\n" -"Last-Translator: arneatec , 2022-2023\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-08-07 07:59+0000\n" +"Last-Translator: arneatec , 2022-2024\n" "Language-Team: Bulgarian (http://app.transifex.com/django/django/language/" "bg/)\n" "MIME-Version: 1.0\n" @@ -243,53 +243,53 @@ msgid "Dec" msgstr "дек." msgid "Sunday" -msgstr "" +msgstr "неделя" msgid "Monday" -msgstr "" +msgstr "понеделник" msgid "Tuesday" -msgstr "" +msgstr "вторник" msgid "Wednesday" -msgstr "" +msgstr "сряда" msgid "Thursday" -msgstr "" +msgstr "четвъртък" msgid "Friday" -msgstr "" +msgstr "петък" msgid "Saturday" -msgstr "" +msgstr "събота" msgctxt "abbrev. day Sunday" msgid "Sun" -msgstr "" +msgstr "нед" msgctxt "abbrev. day Monday" msgid "Mon" -msgstr "" +msgstr "пон" msgctxt "abbrev. day Tuesday" msgid "Tue" -msgstr "" +msgstr "вт" msgctxt "abbrev. day Wednesday" msgid "Wed" -msgstr "" +msgstr "ср" msgctxt "abbrev. day Thursday" msgid "Thur" -msgstr "" +msgstr "чет" msgctxt "abbrev. day Friday" msgid "Fri" -msgstr "" +msgstr "пет" msgctxt "abbrev. day Saturday" msgid "Sat" -msgstr "" +msgstr "съб" msgctxt "one letter Sunday" msgid "S" @@ -318,9 +318,3 @@ msgstr "П" msgctxt "one letter Saturday" msgid "S" msgstr "С" - -msgid "Show" -msgstr "Покажи" - -msgid "Hide" -msgstr "Скрий" diff --git a/django/contrib/admin/locale/ckb/LC_MESSAGES/django.mo b/django/contrib/admin/locale/ckb/LC_MESSAGES/django.mo index e4625809b2..686f0925f3 100644 Binary files a/django/contrib/admin/locale/ckb/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/ckb/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/ckb/LC_MESSAGES/django.po b/django/contrib/admin/locale/ckb/LC_MESSAGES/django.po index f6b80d6d80..b3157f5b2e 100644 --- a/django/contrib/admin/locale/ckb/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/ckb/LC_MESSAGES/django.po @@ -4,15 +4,15 @@ # Abdulla Dlshad, 2023 # Bakhtawar Barzan, 2021 # Bakhtawar Barzan, 2021 -# kosar tofiq , 2020 +# Kosar Tofiq Saeed , 2020 # pejar hewrami , 2020 # Swara , 2022,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: 2013-04-25 07:05+0000\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-08-07 07:05+0000\n" "Last-Translator: Swara , 2022,2024\n" "Language-Team: Central Kurdish (http://app.transifex.com/django/django/" "language/ckb/)\n" @@ -207,12 +207,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} " @@ -379,6 +373,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] "تکایە ئەم هەڵەیەی خوارەوە ڕاست بکەرەوە." @@ -388,6 +385,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/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/da/LC_MESSAGES/django.mo b/django/contrib/admin/locale/da/LC_MESSAGES/django.mo index 7eaa256703..f909706469 100644 Binary files a/django/contrib/admin/locale/da/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/da/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/da/LC_MESSAGES/django.po b/django/contrib/admin/locale/da/LC_MESSAGES/django.po index effde32e3f..293030a1c9 100644 --- a/django/contrib/admin/locale/da/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/da/LC_MESSAGES/django.po @@ -3,7 +3,7 @@ # Translators: # Christian Joergensen , 2012 # Dimitris Glezos , 2012 -# Erik Ramsgaard Wognsen , 2020-2023 +# Erik Ramsgaard Wognsen , 2020-2024 # Erik Ramsgaard Wognsen , 2013,2015-2020 # Finn Gruwier Larsen, 2011 # Jannis Leidel , 2011 @@ -12,9 +12,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: Erik Ramsgaard Wognsen , 2020-2023\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-08-07 07:05+0000\n" +"Last-Translator: Erik Ramsgaard Wognsen , 2020-2024\n" "Language-Team: Danish (http://app.transifex.com/django/django/language/da/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -202,10 +202,6 @@ msgid "" "The {name} “{obj}” was changed successfully. You may edit it again below." msgstr "{name} “{obj}” blev ændret. Du kan redigere den/det igen herunder." -#, python-brace-format -msgid "The {name} “{obj}” was added successfully. You may edit it again below." -msgstr "{name} “{obj}” blev tilføjet. Du kan redigere den/det igen herunder." - #, python-brace-format msgid "" "The {name} “{obj}” was changed successfully. You may add another {name} " @@ -372,6 +368,9 @@ msgstr "Indtast et brugernavn og en adgangskode." msgid "Change password" msgstr "Skift adgangskode" +msgid "Set password" +msgstr "Sæt adgangskode" + msgid "Please correct the error below." msgid_plural "Please correct the errors below." msgstr[0] "Ret venligst fejlen herunder." @@ -381,6 +380,19 @@ msgstr[1] "Ret venligst fejlene herunder." msgid "Enter a new password for the user %(username)s." msgstr "Indtast en ny adgangskode for brugeren %(username)s." +msgid "" +"This action will enable password-based authentication for " +"this user." +msgstr "" +"Denne handling vil aktivere adgangskodebaseret " +"autentificering for denne bruger." + +msgid "Disable password-based authentication" +msgstr "Deaktivér adgangskodebaseret autentificering." + +msgid "Enable password-based authentication" +msgstr "Aktivér adgangskodebaseret autentificering." + msgid "Skip to main content" msgstr "Gå til hovedindhold" @@ -553,7 +565,7 @@ msgstr "" "denne site. Vil du logge ind med en anden brugerkonto?" msgid "Forgotten your password or username?" -msgstr "Har du glemt dit password eller brugernavn?" +msgstr "Har du glemt din adgangskode eller dit brugernavn?" msgid "Toggle navigation" msgstr "Vis/skjul navigation" 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 7ab92b04ee..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 2e3461b104..76348412b8 100644 --- a/django/contrib/admin/locale/es/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/es/LC_MESSAGES/django.po @@ -10,6 +10,7 @@ # Ignacio José Lizarán Rus , 2019 # Igor Támara , 2013 # Jannis Leidel , 2011 +# Jorge Andres Bravo Meza, 2024 # Jorge Puente Sarrín , 2014-2015 # José Luis , 2016 # Josue Naaman Nistal Guerra , 2014 @@ -17,17 +18,18 @@ # Marc Garcia , 2011 # Miguel Angel Tribaldos , 2017 # Miguel Gonzalez , 2023 +# 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: 2023-09-18 11:41-0300\n" -"PO-Revision-Date: 2023-12-04 07:05+0000\n" -"Last-Translator: Uriel Medina , 2020-2023\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\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" @@ -221,12 +223,6 @@ msgstr "" "El {name} “{obj}” se cambió correctamente. Puede editarlo nuevamente a " "continuación." -#, python-brace-format -msgid "The {name} “{obj}” was added successfully. You may edit it again below." -msgstr "" -"El {name} “{obj}” se agregó correctamente. Puede editarlo nuevamente a " -"continuación." - #, python-brace-format msgid "" "The {name} “{obj}” was changed successfully. You may add another {name} " @@ -395,6 +391,9 @@ msgstr "Introduzca un nombre de usuario y 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." @@ -407,6 +406,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 habilitará la autenticación basada en " +"contraseña para este usuario." + +msgid "Disable password-based authentication" +msgstr "Deshabilitar la autenticación basada en contraseña" + +msgid "Enable password-based authentication" +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/et/LC_MESSAGES/django.mo b/django/contrib/admin/locale/et/LC_MESSAGES/django.mo index 6154110df0..0a51b4a9b7 100644 Binary files a/django/contrib/admin/locale/et/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/et/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/et/LC_MESSAGES/django.po b/django/contrib/admin/locale/et/LC_MESSAGES/django.po index 920652d28a..a689289e7a 100644 --- a/django/contrib/admin/locale/et/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/et/LC_MESSAGES/django.po @@ -2,10 +2,10 @@ # # Translators: # eallik , 2011 -# Erlend , 2020 +# Erlend Eelmets , 2020 # Jannis Leidel , 2011 # Janno Liivak , 2013-2015 -# Martin , 2015,2022-2023 +# Martin , 2015,2022-2024 # Martin , 2016,2019-2020 # Marti Raudsepp , 2016 # Ragnar Rebase , 2019 @@ -13,9 +13,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 , 2015,2022-2023\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-08-07 07:05+0000\n" +"Last-Translator: Martin , 2015,2022-2024\n" "Language-Team: Estonian (http://app.transifex.com/django/django/language/" "et/)\n" "MIME-Version: 1.0\n" @@ -204,10 +204,6 @@ msgid "" "The {name} “{obj}” was changed successfully. You may edit it again below." msgstr "{name} “{obj}” muutmine õnnestus. Allpool saate seda uuesti muuta." -#, python-brace-format -msgid "The {name} “{obj}” was added successfully. You may edit it again below." -msgstr "{name} “{obj}” lisamine õnnestus. Allpool saate seda uuesti muuta." - #, python-brace-format msgid "" "The {name} “{obj}” was changed successfully. You may add another {name} " @@ -372,6 +368,9 @@ msgstr "Sisestage kasutajanimi ja salasõna." msgid "Change password" msgstr "Muuda salasõna" +msgid "Set password" +msgstr "Määra parool" + msgid "Please correct the error below." msgid_plural "Please correct the errors below." msgstr[0] "Palun parandage allolev viga." @@ -381,6 +380,17 @@ msgstr[1] "Palun parandage allolevad vead." msgid "Enter a new password for the user %(username)s." msgstr "Sisestage uus salasõna kasutajale %(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 "Liigu põhisisu juurde" @@ -738,7 +748,7 @@ msgid "Email address:" msgstr "E-posti aadress:" msgid "Reset my password" -msgstr "Reseti parool" +msgstr "Lähtesta mu parool" msgid "Select all objects on this page for an action" msgstr "Vali toiminguks kõik objektid sellel lehel" diff --git a/django/contrib/admin/locale/fr/LC_MESSAGES/django.mo b/django/contrib/admin/locale/fr/LC_MESSAGES/django.mo index e35ee4648e..2607e2777c 100644 Binary files a/django/contrib/admin/locale/fr/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/fr/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/fr/LC_MESSAGES/django.po b/django/contrib/admin/locale/fr/LC_MESSAGES/django.po index 36e5a2a5ad..2e768e33aa 100644 --- a/django/contrib/admin/locale/fr/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/fr/LC_MESSAGES/django.po @@ -2,16 +2,16 @@ # # Translators: # Bruno Brouard , 2021 -# Claude Paroz , 2013-2023 +# Claude Paroz , 2013-2024 # Claude Paroz , 2011,2013 # Jannis Leidel , 2011 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: Claude Paroz , 2013-2023\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-08-07 07:05+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" "Content-Type: text/plain; charset=UTF-8\n" @@ -205,12 +205,6 @@ msgstr "" "L’objet {name} « {obj} » a été modifié avec succès. Vous pouvez l’éditer à " "nouveau ci-dessous." -#, python-brace-format -msgid "The {name} “{obj}” was added successfully. You may edit it again below." -msgstr "" -"L’objet {name} « {obj} » a été ajouté avec succès. Vous pouvez l’éditer à " -"nouveau ci-dessous." - #, python-brace-format msgid "" "The {name} “{obj}” was changed successfully. You may add another {name} " @@ -382,6 +376,9 @@ msgstr "Saisissez un nom d’utilisateur et un mot de passe." msgid "Change password" msgstr "Modifier le mot de passe" +msgid "Set password" +msgstr "Définir un mot de passe" + msgid "Please correct the error below." msgid_plural "Please correct the errors below." msgstr[0] "Corrigez l’erreur ci-dessous." @@ -394,6 +391,19 @@ msgstr "" "Saisissez un nouveau mot de passe pour l’utilisateur %(username)s." +msgid "" +"This action will enable password-based authentication for " +"this user." +msgstr "" +"Cette action va activer l'authentification par mot de passe " +"pour cet utilisateur." + +msgid "Disable password-based authentication" +msgstr "Désactiver l'authentification par mot de passe" + +msgid "Enable password-based authentication" +msgstr "Activer l'authentification par mot de passe" + msgid "Skip to main content" msgstr "Passer au contenu 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/gl/LC_MESSAGES/django.mo b/django/contrib/admin/locale/gl/LC_MESSAGES/django.mo index 4b36d3d62a..daddcd3ea4 100644 Binary files a/django/contrib/admin/locale/gl/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/gl/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/gl/LC_MESSAGES/django.po b/django/contrib/admin/locale/gl/LC_MESSAGES/django.po index 8d556628f3..0e4facab49 100644 --- a/django/contrib/admin/locale/gl/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/gl/LC_MESSAGES/django.po @@ -9,14 +9,14 @@ # Leandro Regueiro , 2013 # 948a55bc37dd6d642f1875bb84258fff_07a28cc , 2011-2012 # Pablo, 2015 -# X Bello , 2023 +# X Bello , 2023-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: X Bello , 2023\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-08-07 07:05+0000\n" +"Last-Translator: X Bello , 2023-2024\n" "Language-Team: Galician (http://app.transifex.com/django/django/language/" "gl/)\n" "MIME-Version: 1.0\n" @@ -207,10 +207,6 @@ msgid "" "The {name} “{obj}” was changed successfully. You may edit it again below." msgstr "Modificouse correctamente {name} “{obj}”. Pode editalo de novo abaixo." -#, python-brace-format -msgid "The {name} “{obj}” was added successfully. You may edit it again below." -msgstr "Engadiuse correctamente {name} “{obj}”. Pode editalo de novo abaixo." - #, python-brace-format msgid "" "The {name} “{obj}” was changed successfully. You may add another {name} " @@ -376,6 +372,9 @@ msgstr "Introduza un nome de usuario e contrasinal." msgid "Change password" msgstr "Cambiar contrasinal" +msgid "Set password" +msgstr "Configurar contrasinal" + msgid "Please correct the error below." msgid_plural "Please correct the errors below." msgstr[0] "Por favor corrixa o erro de abaixo." @@ -386,6 +385,19 @@ msgid "Enter a new password for the user %(username)s." msgstr "" "Insira un novo contrasinal para o usuario %(username)s." +msgid "" +"This action will enable password-based authentication for " +"this user." +msgstr "" +"Esta acción vai habilitar a autentificación basada en " +"contrasinal para este usuario." + +msgid "Disable password-based authentication" +msgstr "Deshabilitar a autentificación basada en contrasinal" + +msgid "Enable password-based authentication" +msgstr "Habilitar a autentificación basada en contrasinal" + msgid "Skip to main content" msgstr "Saltar ó contido principal" 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 3371e26ba6..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 9808809406..791412ed6a 100644 --- a/django/contrib/admin/locale/id/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/id/LC_MESSAGES/django.po @@ -1,22 +1,23 @@ # This file is distributed under the same license as the Django package. # # 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 # rodin , 2011-2013 # rodin , 2013-2017 -# sag᠎e , 2019 +# sag​e , 2019 # Sutrisno Efendi , 2015 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: Fery Setiawan , 2015-2019,2021-2023\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\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" @@ -186,7 +187,7 @@ msgstr "" "Tekan “Control”, atau “Command” pada Mac, untuk memilih lebih dari satu." msgid "Select this object for an action - {}" -msgstr "" +msgstr "Pilih objek ini untuk suatu aksi - {}" #, python-brace-format msgid "The {name} “{obj}” was added successfully." @@ -208,11 +209,6 @@ msgid "" msgstr "" "{name} “{obj}” berhasil diubah. Anda dapat mengeditnya kembali di bawah." -#, python-brace-format -msgid "The {name} “{obj}” was added successfully. You may edit it again below." -msgstr "" -"{name} “{obj}” berhasil ditambahkan. Anda dapat mengeditnya kembali di bawah." - #, python-brace-format msgid "" "The {name} “{obj}” was changed successfully. You may add another {name} " @@ -375,6 +371,9 @@ msgstr "Masukkan nama pengguna dan sandi." msgid "Change password" msgstr "Ganti sandi" +msgid "Set password" +msgstr "Setel sandi" + msgid "Please correct the error below." msgid_plural "Please correct the errors below." msgstr[0] "Harap perbaiki kesalahan dibawah." @@ -383,6 +382,19 @@ msgstr[0] "Harap perbaiki kesalahan dibawah." msgid "Enter a new password for the user %(username)s." msgstr "Masukkan sandi baru untuk pengguna %(username)s." +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 "Tiadakan autentifikasu berdasarkan-sandi" + +msgid "Enable password-based authentication" +msgstr "Adakan autentifikasu berdasarkan-sandi" + msgid "Skip to main content" msgstr "Lewati ke isi utama" @@ -412,10 +424,10 @@ msgid "Filter" msgstr "Filter" msgid "Hide counts" -msgstr "" +msgstr "Sembunyikan hitungan" msgid "Show counts" -msgstr "" +msgstr "Tampilkan hitungan" msgid "Clear all filters" msgstr "Hapus semua penyaringan" @@ -431,13 +443,13 @@ msgid "Toggle sorting" msgstr "Ubah pengurutan" msgid "Toggle theme (current theme: auto)" -msgstr "" +msgstr "Ganti tema (tema saat ini: otomatis)" msgid "Toggle theme (current theme: light)" -msgstr "" +msgstr "Ganti tema (tema saat ini: terang)" msgid "Toggle theme (current theme: dark)" -msgstr "" +msgstr "Ganti tema (tema saat ini: gelap)" msgid "Delete" msgstr "Hapus" @@ -526,13 +538,13 @@ msgid "None available" msgstr "Tidak ada yang tersedia" msgid "Added:" -msgstr "" +msgstr "Ditambahkan:" msgid "Changed:" -msgstr "" +msgstr "Berubah:" msgid "Deleted:" -msgstr "" +msgstr "Dihapus:" msgid "Unknown content" msgstr "Konten tidak diketahui" @@ -744,7 +756,7 @@ msgid "Reset my password" msgstr "Setel ulang sandi saya" msgid "Select all objects on this page for an action" -msgstr "" +msgstr "Pilih semua objek di halaman ini untuk suatu aksi" msgid "All dates" msgstr "Semua tanggal" diff --git a/django/contrib/admin/locale/ja/LC_MESSAGES/django.mo b/django/contrib/admin/locale/ja/LC_MESSAGES/django.mo index dd076b723a..ec8a378297 100644 Binary files a/django/contrib/admin/locale/ja/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/ja/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/ja/LC_MESSAGES/django.po b/django/contrib/admin/locale/ja/LC_MESSAGES/django.po index 55ea9d857c..ac42d389fe 100644 --- a/django/contrib/admin/locale/ja/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/ja/LC_MESSAGES/django.po @@ -9,18 +9,19 @@ # Masaya, 2023 # Shinichi Katsumata , 2019 # Shinya Okano , 2012-2018,2021,2023 -# Taichi Taniguchi, 2022 +# TANIGUCHI Taichi, 2022 # Takuro Onoue , 2020 # Takuya N , 2020 # Tetsuya Morimoto , 2011 # 上田慶祐 , 2015 +# 余田大輝, 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: Shinya Okano , 2012-2018,2021,2023\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-08-07 07:05+0000\n" +"Last-Translator: 余田大輝, 2024\n" "Language-Team: Japanese (http://app.transifex.com/django/django/language/" "ja/)\n" "MIME-Version: 1.0\n" @@ -210,10 +211,6 @@ msgid "" "The {name} “{obj}” was changed successfully. You may edit it again below." 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 +373,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 +385,18 @@ 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/locale/ko/LC_MESSAGES/django.mo b/django/contrib/admin/locale/ko/LC_MESSAGES/django.mo index f734d93b1b..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 88f02363bd..717f2d5995 100644 --- a/django/contrib/admin/locale/ko/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/ko/LC_MESSAGES/django.po @@ -3,7 +3,7 @@ # Translators: # Jiyoon, Ha , 2016 # DONGHO JEONG , 2020 -# 코딩 영, 2021 +# Dummy Iam, 2021 # Geonho Kim / Leo Kim , 2019 # Gihun Ham , 2018 # Hang Park , 2019 @@ -13,21 +13,24 @@ # Jannis Leidel , 2011 # Jay Oh , 2020 # Le Tartuffe , 2014,2016 +# Juyoung Lim, 2024 # LEE Hwanyong , 2023 # Seho Noh , 2018 # Seacbyul Lee , 2017 +# 최소영, 2024 # Taesik Yoon , 2015 # 정훈 이, 2021 # 박태진, 2021 # Yang Chan Woo , 2019 +# Youngkwang Yang, 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: 2023-04-25 07:05+0000\n" -"Last-Translator: LEE Hwanyong , 2023\n" -"Language-Team: Korean (http://www.transifex.com/django/django/language/ko/)\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\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" "Content-Transfer-Encoding: 8bit\n" @@ -195,6 +198,9 @@ msgstr "" "하나 이상을 선택하려면 \"Control\" 키를 누른 채로 선택해주세요. Mac의 경우에" "는 \"Command\" 키를 눌러주세요." +msgid "Select this object for an action - {}" +msgstr "작업에 대한 객체를 선택합니다." + #, python-brace-format msgid "The {name} “{obj}” was added successfully." msgstr "{name} \"{obj}\"가 성공적으로 추가되었습니다." @@ -216,12 +222,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} " @@ -359,6 +359,9 @@ msgstr "%(total_count)s개의 %(module_name)s 모두를 선택합니다." msgid "Clear selection" msgstr "선택 해제" +msgid "Breadcrumbs" +msgstr "사용자 위치" + #, python-format msgid "Models in the %(name)s application" msgstr "%(name)s 애플리케이션의 모델" @@ -385,14 +388,28 @@ msgstr "사용자 이름과 비밀번호를 입력하세요." msgid "Change password" msgstr "비밀번호 변경" +msgid "Set password" +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." 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 "메인 콘텐츠로 이동" @@ -408,9 +425,6 @@ msgstr "문서" msgid "Log out" msgstr "로그아웃" -msgid "Breadcrumbs" -msgstr "사용자 위치" - #, python-format msgid "Add %(name)s" msgstr "%(name)s 추가" @@ -424,6 +438,12 @@ msgstr "사이트에서 보기" msgid "Filter" msgstr "필터" +msgid "Hide counts" +msgstr "개수 숨기기" + +msgid "Show counts" +msgstr "개수 표시" + msgid "Clear all filters" msgstr "모든 필터 삭제" @@ -531,6 +551,15 @@ msgstr "나의 활동" msgid "None available" msgstr "이용할 수 없습니다." +msgid "Added:" +msgstr "추가되었습니다:" + +msgid "Changed:" +msgstr "변경:" + +msgid "Deleted:" +msgstr "삭제:" + msgid "Unknown content" msgstr "알 수 없는 형식입니다." @@ -737,6 +766,9 @@ msgstr "이메일 주소:" msgid "Reset my password" msgstr "비밀번호 초기화" +msgid "Select all objects on this page for an action" +msgstr "작업에 대한 이 페이지의 모든 객체를 선택합니다." + msgid "All dates" msgstr "언제나" diff --git a/django/contrib/admin/locale/lv/LC_MESSAGES/django.mo b/django/contrib/admin/locale/lv/LC_MESSAGES/django.mo index 9f93c9536c..15ac7ec8ab 100644 Binary files a/django/contrib/admin/locale/lv/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/lv/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/lv/LC_MESSAGES/django.po b/django/contrib/admin/locale/lv/LC_MESSAGES/django.po index e15c84137c..e4ac00a5ad 100644 --- a/django/contrib/admin/locale/lv/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/lv/LC_MESSAGES/django.po @@ -2,7 +2,7 @@ # # Translators: # edgars , 2011 -# Edgars Voroboks , 2023 +# Edgars Voroboks , 2023-2024 # Edgars Voroboks , 2017,2022 # Edgars Voroboks , 2018 # Jannis Leidel , 2011 @@ -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 07:05+0000\n" -"Last-Translator: Edgars Voroboks , 2023\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-08-07 07:05+0000\n" +"Last-Translator: Edgars Voroboks , 2023-2024\n" "Language-Team: Latvian (http://app.transifex.com/django/django/language/" "lv/)\n" "MIME-Version: 1.0\n" @@ -207,10 +207,6 @@ msgid "" "The {name} “{obj}” was changed successfully. You may edit it again below." msgstr "{name} “{obj}” tika veiksmīgi mainīts. Zemāk varat to labot vēlreiz." -#, python-brace-format -msgid "The {name} “{obj}” was added successfully. You may edit it again below." -msgstr "{name} “{obj}” veiksmīgi pievienots. Zemāk to varat atkal labot." - #, python-brace-format msgid "" "The {name} “{obj}” was changed successfully. You may add another {name} " @@ -378,6 +374,9 @@ msgstr "Ievadi lietotājvārdu un paroli." msgid "Change password" msgstr "Paroles maiņa" +msgid "Set password" +msgstr "Iestatīt paroli" + msgid "Please correct the error below." msgid_plural "Please correct the errors below." msgstr[0] "Lūdzu, izlabojiet zemāk norādītās kļūdas." @@ -388,6 +387,19 @@ msgstr[2] "Lūdzu, izlabojiet zemāk norādītās kļūdas." msgid "Enter a new password for the user %(username)s." msgstr "Ievadiet jaunu paroli lietotājam %(username)s." +msgid "" +"This action will enable password-based authentication for " +"this user." +msgstr "" +"Šī darbība šim lietotājam iespējos paroles bāzētu " +"autentifikāciju." + +msgid "Disable password-based authentication" +msgstr "Atspējot paroles bāzētu autentifikāciju" + +msgid "Enable password-based authentication" +msgstr "Iespējot paroles bāzētu autentifikāciju" + msgid "Skip to main content" msgstr "Pāriet uz galveno saturu" diff --git a/django/contrib/admin/locale/nl/LC_MESSAGES/django.mo b/django/contrib/admin/locale/nl/LC_MESSAGES/django.mo index f6344b7912..b2181e2e71 100644 Binary files a/django/contrib/admin/locale/nl/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/nl/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/nl/LC_MESSAGES/django.po b/django/contrib/admin/locale/nl/LC_MESSAGES/django.po index 17a7ff7c57..ee971ddff0 100644 --- a/django/contrib/admin/locale/nl/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/nl/LC_MESSAGES/django.po @@ -12,14 +12,14 @@ # Meteor0id, 2019-2020 # 8de006b1b0894aab6aef71979dcd8bd6_5c6b207 , 2014-2015 # Tino de Bruijn , 2011 -# Tonnes , 2017,2019-2020,2022-2023 +# Tonnes , 2017,2019-2020,2022-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: Tonnes , 2017,2019-2020,2022-2023\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-08-07 07:05+0000\n" +"Last-Translator: Tonnes , 2017,2019-2020,2022-2024\n" "Language-Team: Dutch (http://app.transifex.com/django/django/language/nl/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -212,12 +212,6 @@ msgstr "" "De {name} ‘{obj}’ is met succes gewijzigd. U kunt deze hieronder nogmaals " "bewerken." -#, python-brace-format -msgid "The {name} “{obj}” was added successfully. You may edit it again below." -msgstr "" -"De {name} ‘{obj}’ is met succes toegevoegd. U kunt deze hieronder nogmaals " -"bewerken." - #, python-brace-format msgid "" "The {name} “{obj}” was changed successfully. You may add another {name} " @@ -385,6 +379,9 @@ msgstr "Voer een gebruikersnaam en wachtwoord in." msgid "Change password" msgstr "Wachtwoord wijzigen" +msgid "Set password" +msgstr "Wachtwoord instellen" + msgid "Please correct the error below." msgid_plural "Please correct the errors below." msgstr[0] "Corrigeer de onderstaande fout." @@ -395,6 +392,19 @@ msgid "Enter a new password for the user %(username)s." msgstr "" "Voer een nieuw wachtwoord in voor de gebruiker %(username)s." +msgid "" +"This action will enable password-based authentication for " +"this user." +msgstr "" +"Deze actie schakelt op wachtwoord gebaseerde authenticatie in voor deze gebruiker." + +msgid "Disable password-based authentication" +msgstr "Op wachtwoord gebaseerde authenticatie uitschakelen" + +msgid "Enable password-based authentication" +msgstr "Op wachtwoord gebaseerde authenticatie inschakelen" + msgid "Skip to main content" msgstr "Naar hoofdinhoud" diff --git a/django/contrib/admin/locale/pl/LC_MESSAGES/django.mo b/django/contrib/admin/locale/pl/LC_MESSAGES/django.mo index 5b34de4b23..4507529f5a 100644 Binary files a/django/contrib/admin/locale/pl/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/pl/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/pl/LC_MESSAGES/django.po b/django/contrib/admin/locale/pl/LC_MESSAGES/django.po index bfb5a1a2d7..bb14e7d758 100644 --- a/django/contrib/admin/locale/pl/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/pl/LC_MESSAGES/django.po @@ -11,7 +11,7 @@ # Maciej Olko , 2016-2022 # Maciej Olko , 2023 # Maciej Olko , 2015 -# Mariusz Felisiak , 2020,2022-2023 +# Mariusz Felisiak , 2020,2022-2024 # Ola Sitarska , 2013 # Ola Sitarska , 2013 # Roman Barczyński, 2014 @@ -20,10 +20,10 @@ 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" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-08-07 07:05+0000\n" "Last-Translator: Mariusz Felisiak , " -"2020,2022-2023\n" +"2020,2022-2024\n" "Language-Team: Polish (http://app.transifex.com/django/django/language/pl/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -218,12 +218,6 @@ msgstr "" "{name} „{obj}” został(a)(-ło) pomyślnie zmieniony(-na)(-ne). Można edytować " "go/ją/je ponownie poniżej." -#, python-brace-format -msgid "The {name} “{obj}” was added successfully. You may edit it again below." -msgstr "" -"{name} „{obj}” został(a)(-ło) dodany(-na)(-ne) pomyślnie. Można edytować go/" -"ją/je ponownie poniżej." - #, python-brace-format msgid "" "The {name} “{obj}” was changed successfully. You may add another {name} " @@ -394,6 +388,9 @@ msgstr "Podaj nazwę użytkownika i hasło." msgid "Change password" msgstr "Zmień hasło" +msgid "Set password" +msgstr "Ustaw hasło" + msgid "Please correct the error below." msgid_plural "Please correct the errors below." msgstr[0] "Prosimy poprawić poniższy błąd." @@ -405,6 +402,19 @@ msgstr[3] "Prosimy poprawić poniższe błędy." msgid "Enter a new password for the user %(username)s." msgstr "Podaj nowe hasło dla użytkownika %(username)s." +msgid "" +"This action will enable password-based authentication for " +"this user." +msgstr "" +"To działanie włączy uwierzytelnianie oparte na haśle dla " +"tego użytkownika. " + +msgid "Disable password-based authentication" +msgstr "Wyłącz uwierzytelnianie oparte na haśle" + +msgid "Enable password-based authentication" +msgstr "Włącz uwierzytelnianie oparte na haśle" + msgid "Skip to main content" msgstr "Przejdź do głównej treści" diff --git a/django/contrib/admin/locale/pt_BR/LC_MESSAGES/django.mo b/django/contrib/admin/locale/pt_BR/LC_MESSAGES/django.mo index 95113a5905..1a2a3b986c 100644 Binary files a/django/contrib/admin/locale/pt_BR/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/pt_BR/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/pt_BR/LC_MESSAGES/django.po b/django/contrib/admin/locale/pt_BR/LC_MESSAGES/django.po index ab1f7169e3..8318b3779a 100644 --- a/django/contrib/admin/locale/pt_BR/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/pt_BR/LC_MESSAGES/django.po @@ -4,10 +4,11 @@ # Allisson Azevedo , 2014 # Bruce de Sá , 2019 # bruno.devpod , 2014 -# Carlos C. Leite , 2019 -# Carlos C. Leite , 2019 +# Carlos Cadu “Cadu” Leite , 2019 +# Carlos Cadu “Cadu” Leite , 2019 # Filipe Cifali , 2016 # dudanogueira , 2012 +# Eduardo Felipe Castegnaro , 2024 # Elyézer Rezende , 2013 # Fábio C. Barrionuevo da Luz , 2015 # Fabio Cerqueira , 2019 @@ -36,9 +37,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: Gabriel da Mota , 2023\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-08-07 07:05+0000\n" +"Last-Translator: Eduardo Felipe Castegnaro , 2024\n" "Language-Team: Portuguese (Brazil) (http://app.transifex.com/django/django/" "language/pt_BR/)\n" "MIME-Version: 1.0\n" @@ -57,7 +58,7 @@ msgstr "Removido %(count)d %(items)s com sucesso." #, python-format msgid "Cannot delete %(name)s" -msgstr "Não é possível excluir %(name)s " +msgstr "Não é possível remover %(name)s " msgid "Are you sure?" msgstr "Tem certeza?" @@ -129,7 +130,7 @@ msgid "Change" msgstr "Modificar" msgid "Deletion" -msgstr "Eliminação" +msgstr "Remoção" msgid "action time" msgstr "hora da ação" @@ -170,7 +171,7 @@ msgstr "Alterado “%(object)s” — %(changes)s" #, python-format msgid "Deleted “%(object)s.”" -msgstr "Deletado “%(object)s.”" +msgstr "Removido “%(object)s.”" msgid "LogEntry Object" msgstr "Objeto LogEntry" @@ -195,7 +196,7 @@ msgstr "Alterado {fields}." #, python-brace-format msgid "Deleted {name} “{object}”." -msgstr "Deletado {name} “{object}”." +msgstr "Removido {name} “{object}”." msgid "No fields changed." msgstr "Nenhum campo modificado." @@ -230,12 +231,6 @@ msgstr "" "O {name} “{obj}” foi alterado com sucesso. Você pode alterá-lo novamente " "abaixo." -#, python-brace-format -msgid "The {name} “{obj}” was added successfully. You may edit it again below." -msgstr "" -"O {name} “{obj}” foi adicionado com sucesso. Você pode editá-lo novamente " -"abaixo." - #, python-brace-format msgid "" "The {name} “{obj}” was changed successfully. You may add another {name} " @@ -260,11 +255,11 @@ msgstr "Nenhuma ação selecionada." #, python-format msgid "The %(name)s “%(obj)s” was deleted successfully." -msgstr "O %(name)s “%(obj)s” foi deletado com sucesso." +msgstr "O %(name)s “%(obj)s” foi removido com sucesso." #, python-format msgid "%(name)s with ID “%(key)s” doesn’t exist. Perhaps it was deleted?" -msgstr "O %(name)s com ID “%(key)s” não existe. Talvez tenha sido deletado." +msgstr "O %(name)s com ID “%(key)s” não existe. Talvez tenha sido removido." #, python-format msgid "Add %s" @@ -315,7 +310,7 @@ msgid "" "Deleting %(class_name)s %(instance)s would require deleting the following " "protected related objects: %(related_objects)s" msgstr "" -"Excluir o %(class_name)s %(instance)s exigiria excluir os seguintes objetos " +"Remover o %(class_name)s %(instance)s exigiria remover os seguintes objetos " "protegidos relacionados: %(related_objects)s" msgid "Django site admin" @@ -376,7 +371,7 @@ msgid "Clear selection" msgstr "Limpar seleção" msgid "Breadcrumbs" -msgstr "Migalhas de pão" +msgstr "Marcas de navegação" #, python-format msgid "Models in the %(name)s application" @@ -395,7 +390,7 @@ msgid "" "First, enter a username and password. Then, you’ll be able to edit more user " "options." msgstr "" -"Primeiro, informe seu nome de usuário e senha. Então, você poderá editar " +"Primeiro, informe um nome de usuário e senha. Então, você poderá editar " "outras opções do usuário." msgid "Enter a username and password." @@ -404,6 +399,9 @@ msgstr "Digite um nome de usuário e senha." msgid "Change password" msgstr "Alterar senha" +msgid "Set password" +msgstr "Definir senha" + msgid "Please correct the error below." msgid_plural "Please correct the errors below." msgstr[0] "Por favor corrija o erro abaixo." @@ -414,8 +412,21 @@ msgstr[2] "Por favor corrija os erros abaixo." msgid "Enter a new password for the user %(username)s." msgstr "Informe uma nova senha para o usuário %(username)s." +msgid "" +"This action will enable password-based authentication for " +"this user." +msgstr "" +"Esta ação irá habilitarautenticação baseada em senha para " +"este usuário." + +msgid "Disable password-based authentication" +msgstr "Desabilitar autenticação baseada em senha" + +msgid "Enable password-based authentication" +msgstr "Habilitar autenticação baseada em senha" + msgid "Skip to main content" -msgstr "Pule o conteúdo principal" +msgstr "Ir para o conteúdo principal" msgid "Welcome," msgstr "Bem-vindo(a)," @@ -471,7 +482,7 @@ msgid "Toggle theme (current theme: dark)" msgstr "Alternar tema (tema atual: escuro)" msgid "Delete" -msgstr "Apagar" +msgstr "Remover" #, python-format msgid "" @@ -488,7 +499,7 @@ msgid "" "Deleting the %(object_name)s '%(escaped_object)s' would require deleting the " "following protected related objects:" msgstr "" -"Excluir o %(object_name)s ' %(escaped_object)s ' exigiria excluir os " +"Remover o %(object_name)s ' %(escaped_object)s ' exigiria remover os " "seguintes objetos protegidos relacionados:" #, python-format @@ -517,8 +528,8 @@ msgid "" "objects, but your account doesn't have permission to delete the following " "types of objects:" msgstr "" -"Excluir o %(objects_name)s selecionado pode resultar na remoção de objetos " -"relacionados, mas sua conta não tem permissão para excluir os seguintes " +"Remover o %(objects_name)s selecionado pode resultar na remoção de objetos " +"relacionados, mas sua conta não tem permissão para remover os seguintes " "tipos de objetos:" #, python-format @@ -526,7 +537,7 @@ msgid "" "Deleting the selected %(objects_name)s would require deleting the following " "protected related objects:" msgstr "" -"Excluir o %(objects_name)s selecionado exigiria excluir os seguintes objetos " +"Remover o %(objects_name)s selecionado exigiria remover os seguintes objetos " "relacionados protegidos:" #, python-format @@ -534,11 +545,11 @@ msgid "" "Are you sure you want to delete the selected %(objects_name)s? All of the " "following objects and their related items will be deleted:" msgstr "" -"Tem certeza de que deseja apagar o %(objects_name)s selecionado? Todos os " +"Tem certeza de que deseja remover o %(objects_name)s selecionado? Todos os " "seguintes objetos e seus itens relacionados serão removidos:" msgid "Delete?" -msgstr "Apagar?" +msgstr "Remover?" #, python-format msgid " By %(filter_title)s " @@ -563,7 +574,7 @@ msgid "Changed:" msgstr "Alterado:" msgid "Deleted:" -msgstr "Apagado:" +msgstr "Removido:" msgid "Unknown content" msgstr "Conteúdo desconhecido" @@ -670,7 +681,7 @@ msgstr "Adicionar outro %(model)s" #, python-format msgid "Delete selected %(model)s" -msgstr "Excluir %(model)s selecionado" +msgstr "Remover %(model)s selecionado" #, python-format msgid "View selected %(model)s" diff --git a/django/contrib/admin/locale/pt_BR/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/pt_BR/LC_MESSAGES/djangojs.mo index ca2b002488..e8fe18a5f0 100644 Binary files a/django/contrib/admin/locale/pt_BR/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/pt_BR/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/pt_BR/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/pt_BR/LC_MESSAGES/djangojs.po index 9d7ed9c756..10e5080e02 100644 --- a/django/contrib/admin/locale/pt_BR/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/pt_BR/LC_MESSAGES/djangojs.po @@ -4,6 +4,7 @@ # Allisson Azevedo , 2014 # andrewsmedina , 2016 # Eduardo Cereto Carvalho, 2011 +# Eduardo Felipe Castegnaro , 2024 # Gabriel da Mota , 2023 # fa9e10542e458baef0599ae856e43651_13d2225, 2012 # Jannis Leidel , 2011 @@ -17,9 +18,9 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-09-18 15:04-0300\n" -"PO-Revision-Date: 2023-12-04 07:59+0000\n" -"Last-Translator: Gabriel da Mota , 2023\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-08-07 07:59+0000\n" +"Last-Translator: Eduardo Felipe Castegnaro , 2024\n" "Language-Team: Portuguese (Brazil) (http://app.transifex.com/django/django/" "language/pt_BR/)\n" "MIME-Version: 1.0\n" @@ -118,7 +119,7 @@ msgid "" "button." msgstr "" "Você selecionou uma ação sem fazer mudanças nos campos individuais. Você " -"provavelmente está procurando pelo botão Go ao invés do botão Save." +"provavelmente está procurando pelo botão Ir ao invés do botão Salvar." msgid "Now" msgstr "Agora" @@ -330,9 +331,3 @@ msgstr "S" msgctxt "one letter Saturday" msgid "S" msgstr "S" - -msgid "Show" -msgstr "Mostrar" - -msgid "Hide" -msgstr "Esconder" diff --git a/django/contrib/admin/locale/ru/LC_MESSAGES/django.mo b/django/contrib/admin/locale/ru/LC_MESSAGES/django.mo index 0668fbeead..f95653f5a0 100644 Binary files a/django/contrib/admin/locale/ru/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/ru/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/ru/LC_MESSAGES/django.po b/django/contrib/admin/locale/ru/LC_MESSAGES/django.po index cd387a69e0..c77ffd1b07 100644 --- a/django/contrib/admin/locale/ru/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/ru/LC_MESSAGES/django.po @@ -10,16 +10,16 @@ # Sergey , 2016 # Jannis Leidel , 2011 # SeryiMysh , 2020 -# Алексей Борискин , 2012-2015,2022-2023 +# Алексей Борискин , 2012-2015,2022-2024 # Дмитрий , 2019 # Bobsans , 2018 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: Алексей Борискин , 2012-2015,2022-2023\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-08-07 07:05+0000\n" +"Last-Translator: Алексей Борискин , 2012-2015,2022-2024\n" "Language-Team: Russian (http://app.transifex.com/django/django/language/" "ru/)\n" "MIME-Version: 1.0\n" @@ -213,12 +213,6 @@ msgid "" 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} " @@ -387,6 +381,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] "Пожалуйста, исправьте ошибку ниже." @@ -398,6 +395,19 @@ msgstr[3] "Пожалуйста, исправьте ошибки ниже." 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/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/sl/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/sl/LC_MESSAGES/djangojs.mo index 217e4bf54d..f01b6b48ce 100644 Binary files a/django/contrib/admin/locale/sl/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/sl/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/sl/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/sl/LC_MESSAGES/djangojs.po index 6a5e140fff..08a77f67ec 100644 --- a/django/contrib/admin/locale/sl/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/sl/LC_MESSAGES/djangojs.po @@ -1,7 +1,7 @@ # This file is distributed under the same license as the Django package. # # Translators: -# Andrej Marsetič, 2022 +# Andrej Marsetič, 2022,2024 # Jannis Leidel , 2011 # zejn , 2016 # zejn , 2011-2012 @@ -9,10 +9,10 @@ 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 07:59+0000\n" -"Last-Translator: Andrej Marsetič, 2022\n" -"Language-Team: Slovenian (http://www.transifex.com/django/django/language/" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-08-07 07:59+0000\n" +"Last-Translator: Andrej Marsetič, 2022,2024\n" +"Language-Team: Slovenian (http://app.transifex.com/django/django/language/" "sl/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -67,7 +67,7 @@ msgstr "" #, javascript-format msgid "Type into this box to filter down the list of selected %s." -msgstr "" +msgstr "Vnesite v to polje, da filtrirate seznam izbranih %s ." msgid "Remove all" msgstr "Odstrani vse" @@ -79,10 +79,10 @@ msgstr "Kliknite za odstranitev vseh %s hkrati." #, javascript-format msgid "%s selected option not visible" msgid_plural "%s selected options not visible" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" -msgstr[3] "" +msgstr[0] " %s izbrana možnosti ni vidna" +msgstr[1] " %s izbrani možnosti nista vidni" +msgstr[2] " %s izbrane možnosti niso vidne" +msgstr[3] " %s izbrane možnosti niso vidne" msgid "%(sel)s of %(cnt)s selected" msgid_plural "%(sel)s of %(cnt)s selected" @@ -103,12 +103,16 @@ msgid "" "individual fields yet. Please click OK to save. You’ll need to re-run the " "action." msgstr "" +"Izbrali ste dejanje, vendar še niste shranili sprememb posameznih polj. Za " +"shranjevanje kliknite V redu. Akcijo boste morali ponovno zagnati." 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 " "button." msgstr "" +"Izbrali ste dejanje in niste spremenili posameznih polj. Verjetno iščete " +"gumb Pojdi in ne gumb Shrani." msgid "Now" msgstr "Takoj" @@ -246,6 +250,55 @@ msgctxt "abbrev. month December" msgid "Dec" msgstr "Dec" +msgid "Sunday" +msgstr "Nedelja" + +msgid "Monday" +msgstr "Ponedeljek" + +msgid "Tuesday" +msgstr "Torek" + +msgid "Wednesday" +msgstr "Sreda" + +msgid "Thursday" +msgstr "Četrtek" + +msgid "Friday" +msgstr "Petek" + +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 "Tor" + +msgctxt "abbrev. day Wednesday" +msgid "Wed" +msgstr "Sre" + +msgctxt "abbrev. day Thursday" +msgid "Thur" +msgstr "Čet" + +msgctxt "abbrev. day Friday" +msgid "Fri" +msgstr "Pet" + +msgctxt "abbrev. day Saturday" +msgid "Sat" +msgstr "Sob" + msgctxt "one letter Sunday" msgid "S" msgstr "N" @@ -273,9 +326,3 @@ msgstr "P" msgctxt "one letter Saturday" msgid "S" msgstr "S" - -msgid "Show" -msgstr "Prikaži" - -msgid "Hide" -msgstr "Skrij" diff --git a/django/contrib/admin/locale/sq/LC_MESSAGES/django.mo b/django/contrib/admin/locale/sq/LC_MESSAGES/django.mo index 18e613d4e7..292f2cb3ec 100644 Binary files a/django/contrib/admin/locale/sq/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/sq/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/sq/LC_MESSAGES/django.po b/django/contrib/admin/locale/sq/LC_MESSAGES/django.po index b0cff5939f..9e571d047d 100644 --- a/django/contrib/admin/locale/sq/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/sq/LC_MESSAGES/django.po @@ -2,15 +2,15 @@ # # Translators: # Besnik Bleta , 2011,2015 -# Besnik Bleta , 2020,2022-2023 +# Besnik Bleta , 2020,2022-2024 # Besnik Bleta , 2015,2018-2019 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: Besnik Bleta , 2020,2022-2023\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-08-07 07:05+0000\n" +"Last-Translator: Besnik Bleta , 2020,2022-2024\n" "Language-Team: Albanian (http://app.transifex.com/django/django/language/" "sq/)\n" "MIME-Version: 1.0\n" @@ -203,10 +203,6 @@ msgid "" msgstr "" "{name} “{obj}” u ndryshua me sukses. Mund ta përpunoni sërish më poshtë." -#, python-brace-format -msgid "The {name} “{obj}” was added successfully. You may edit it again below." -msgstr "{name} “{obj}” u shtua me sukses. Mund ta përpunoni sërish më poshtë." - #, python-brace-format msgid "" "The {name} “{obj}” was changed successfully. You may add another {name} " @@ -372,6 +368,9 @@ msgstr "Jepni emër përdoruesi dhe fjalëkalim." msgid "Change password" msgstr "Ndryshoni fjalëkalimin" +msgid "Set password" +msgstr "Caktoni fjalëkalim" + msgid "Please correct the error below." msgid_plural "Please correct the errors below." msgstr[0] "Ju lutemi, ndreqni gabimin më poshtë." @@ -382,6 +381,19 @@ msgid "Enter a new password for the user %(username)s." msgstr "" "Jepni një fjalëkalim të ri për përdoruesin %(username)s." +msgid "" +"This action will enable password-based authentication for " +"this user." +msgstr "" +"Ky veprim do të aktivizojë për këtë përdorues mirëfilltësim " +"me bazë fjalëkalimin." + +msgid "Disable password-based authentication" +msgstr "Çaktivizo mirëfilltësim me bazë fjalëkalimin" + +msgid "Enable password-based authentication" +msgstr "Aktivizo mirëfilltësim me bazë fjalëkalimin" + msgid "Skip to main content" msgstr "Kalo te lënda bazë" diff --git a/django/contrib/admin/locale/sr/LC_MESSAGES/django.mo b/django/contrib/admin/locale/sr/LC_MESSAGES/django.mo index 8dbd5cd747..f794a8ccad 100644 Binary files a/django/contrib/admin/locale/sr/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/sr/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/sr/LC_MESSAGES/django.po b/django/contrib/admin/locale/sr/LC_MESSAGES/django.po index e29cc45c92..1c9d7f945b 100644 --- a/django/contrib/admin/locale/sr/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/sr/LC_MESSAGES/django.po @@ -2,16 +2,16 @@ # # Translators: # Branko Kokanovic , 2018 -# Igor Jerosimić, 2019,2021,2023 +# Igor Jerosimić, 2019,2021,2023-2024 # Jannis Leidel , 2011 # Janos Guljas , 2011-2012 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: Igor Jerosimić, 2019,2021,2023\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-08-07 07:05+0000\n" +"Last-Translator: Igor Jerosimić, 2019,2021,2023-2024\n" "Language-Team: Serbian (http://app.transifex.com/django/django/language/" "sr/)\n" "MIME-Version: 1.0\n" @@ -204,10 +204,6 @@ msgid "" 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 +372,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] "Молимо исправите грешку испод." @@ -386,6 +385,19 @@ msgstr[2] "Молимо исправите грешке испод." 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/locale/sr_Latn/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/sr_Latn/LC_MESSAGES/djangojs.mo index 8ba81dda4b..1535c7199d 100644 Binary files a/django/contrib/admin/locale/sr_Latn/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/sr_Latn/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/sr_Latn/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/sr_Latn/LC_MESSAGES/djangojs.po index a68dcf3a29..ec4e06e6ed 100644 --- a/django/contrib/admin/locale/sr_Latn/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/sr_Latn/LC_MESSAGES/djangojs.po @@ -1,80 +1,89 @@ # This file is distributed under the same license as the Django package. -# +# # Translators: -# Igor Jerosimić, 2019,2021,2023 +# Igor Jerosimić, 2019,2021,2023-2024 # Jannis Leidel , 2011 # Janos Guljas , 2011-2012 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 07:59+0000\n" -"Last-Translator: Igor Jerosimić, 2019,2021,2023\n" -"Language-Team: Serbian (Latin) (http://www.transifex.com/django/django/" -"language/sr@latin/)\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-08-07 07:59+0000\n" +"Last-Translator: Igor Jerosimić, 2019,2021,2023-2024\n" +"Language-Team: Serbian (Latin) (http://app.transifex.com/django/django/language/sr@latin/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: sr@latin\n" -"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " -"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" +#: contrib/admin/static/admin/js/SelectFilter2.js:38 #, javascript-format msgid "Available %s" msgstr "Dostupni %s" +#: contrib/admin/static/admin/js/SelectFilter2.js:44 #, javascript-format msgid "" "This is the list of available %s. You may choose some by selecting them in " "the box below and then clicking the \"Choose\" arrow between the two boxes." -msgstr "" -"Ovo je lista dostupnih „%s“. Možete izabrati elemente tako što ćete ih " -"izabrati u listi i kliknuti na „Izaberi“." +msgstr "Ovo je lista dostupnih „%s“. Možete izabrati elemente tako što ćete ih izabrati u listi i kliknuti na „Izaberi“." +#: contrib/admin/static/admin/js/SelectFilter2.js:60 #, javascript-format msgid "Type into this box to filter down the list of available %s." msgstr "Filtrirajte listu dostupnih elemenata „%s“." +#: contrib/admin/static/admin/js/SelectFilter2.js:65 +#: contrib/admin/static/admin/js/SelectFilter2.js:110 msgid "Filter" msgstr "Filter" +#: contrib/admin/static/admin/js/SelectFilter2.js:69 msgid "Choose all" msgstr "Izaberi sve" +#: contrib/admin/static/admin/js/SelectFilter2.js:69 #, javascript-format msgid "Click to choose all %s at once." msgstr "Izaberite sve „%s“ odjednom." +#: contrib/admin/static/admin/js/SelectFilter2.js:75 msgid "Choose" msgstr "Izaberi" +#: contrib/admin/static/admin/js/SelectFilter2.js:77 msgid "Remove" msgstr "Ukloni" +#: contrib/admin/static/admin/js/SelectFilter2.js:83 #, javascript-format msgid "Chosen %s" msgstr "Izabrano „%s“" +#: contrib/admin/static/admin/js/SelectFilter2.js:89 #, javascript-format msgid "" "This is the list of chosen %s. You may remove some by selecting them in the " "box below and then clicking the \"Remove\" arrow between the two boxes." -msgstr "" -"Ovo je lista izabranih „%s“. Možete ukloniti elemente tako što ćete ih " -"izabrati u listi i kliknuti na „Ukloni“." +msgstr "Ovo je lista izabranih „%s“. Možete ukloniti elemente tako što ćete ih izabrati u listi i kliknuti na „Ukloni“." +#: contrib/admin/static/admin/js/SelectFilter2.js:105 #, javascript-format msgid "Type into this box to filter down the list of selected %s." msgstr "Unesite u ovo polje da biste filtrirali listu izabranih %s." +#: contrib/admin/static/admin/js/SelectFilter2.js:120 msgid "Remove all" msgstr "Ukloni sve" +#: contrib/admin/static/admin/js/SelectFilter2.js:120 #, javascript-format msgid "Click to remove all chosen %s at once." msgstr "Uklonite sve izabrane „%s“ odjednom." +#: contrib/admin/static/admin/js/SelectFilter2.js:211 #, javascript-format msgid "%s selected option not visible" msgid_plural "%s selected options not visible" @@ -82,50 +91,55 @@ msgstr[0] "%s izabrana opcija nije vidljiva" msgstr[1] "%s izabrane opcije nisu vidljive" msgstr[2] "%s izabranih opcija nije vidljivo" +#: contrib/admin/static/admin/js/actions.js:67 msgid "%(sel)s of %(cnt)s selected" msgid_plural "%(sel)s of %(cnt)s selected" msgstr[0] "%(sel)s od %(cnt)s izabran" msgstr[1] "%(sel)s od %(cnt)s izabrana" msgstr[2] "%(sel)s od %(cnt)s izabranih" +#: contrib/admin/static/admin/js/actions.js:161 msgid "" "You have unsaved changes on individual editable fields. If you run an " "action, your unsaved changes will be lost." -msgstr "" -"Imate nesačivane izmene. Ako pokrenete akciju, izmene će biti izgubljene." +msgstr "Imate nesačivane izmene. Ako pokrenete akciju, izmene će biti izgubljene." +#: contrib/admin/static/admin/js/actions.js:174 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 " "action." -msgstr "" -"Izabrali ste akciju, ali niste sačuvali vaše promene u pojedinačna polja. " -"Kliknite na OK da sačuvate promene. Biće neophodno da ponovo pokrenete " -"akciju." +msgstr "Izabrali ste akciju, ali niste sačuvali vaše promene u pojedinačna polja. Kliknite na OK da sačuvate promene. Biće neophodno da ponovo pokrenete akciju." +#: contrib/admin/static/admin/js/actions.js:175 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 " "button." -msgstr "" -"Izabrali ste akciju i niste napravili nijednu promenu na pojedinačnim " -"poljima. Verovatno tražite Kreni dugme umesto Sačuvaj." +msgstr "Izabrali ste akciju i niste napravili nijednu promenu na pojedinačnim poljima. Verovatno tražite Kreni dugme umesto Sačuvaj." +#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:13 +#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:110 msgid "Now" msgstr "Trenutno vreme" +#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:14 msgid "Midnight" msgstr "Ponoć" +#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:15 msgid "6 a.m." msgstr "18č" +#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:16 msgid "Noon" msgstr "Podne" +#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:17 msgid "6 p.m." msgstr "18č" +#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:78 #, javascript-format msgid "Note: You are %s hour ahead of server time." msgid_plural "Note: You are %s hours ahead of server time." @@ -133,6 +147,7 @@ msgstr[0] "Obaveštenje: Vi ste %s sat ispred serverskog vremena." msgstr[1] "Obaveštenje: Vi ste %s sata ispred serverskog vremena." msgstr[2] "Obaveštenje: Vi ste %s sati ispred serverskog vremena." +#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:86 #, javascript-format msgid "Note: You are %s hour behind server time." msgid_plural "Note: You are %s hours behind server time." @@ -140,141 +155,238 @@ msgstr[0] "Obaveštenje: Vi ste %s sat iza serverskog vremena." msgstr[1] "Obaveštenje: Vi ste %s sata iza serverskog vremena." msgstr[2] "Obaveštenje: Vi ste %s sati iza serverskog vremena." +#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:128 msgid "Choose a Time" msgstr "Odaberite vreme" +#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:158 msgid "Choose a time" msgstr "Odabir vremena" +#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:175 +#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:333 msgid "Cancel" msgstr "Poništi" +#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:238 +#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:318 msgid "Today" msgstr "Danas" +#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:255 msgid "Choose a Date" msgstr "Odaberite datum" +#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:312 msgid "Yesterday" msgstr "Juče" +#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:324 msgid "Tomorrow" msgstr "Sutra" +#: contrib/admin/static/admin/js/calendar.js:11 msgid "January" msgstr "Januar" +#: contrib/admin/static/admin/js/calendar.js:12 msgid "February" msgstr "Februar" +#: contrib/admin/static/admin/js/calendar.js:13 msgid "March" msgstr "Mart" +#: contrib/admin/static/admin/js/calendar.js:14 msgid "April" msgstr "April" +#: contrib/admin/static/admin/js/calendar.js:15 msgid "May" msgstr "Maj" +#: contrib/admin/static/admin/js/calendar.js:16 msgid "June" msgstr "Jun" +#: contrib/admin/static/admin/js/calendar.js:17 msgid "July" msgstr "Jul" +#: contrib/admin/static/admin/js/calendar.js:18 msgid "August" msgstr "Avgust" +#: contrib/admin/static/admin/js/calendar.js:19 msgid "September" msgstr "Septembar" +#: contrib/admin/static/admin/js/calendar.js:20 msgid "October" msgstr "Oktobar" +#: contrib/admin/static/admin/js/calendar.js:21 msgid "November" msgstr "Novembar" +#: contrib/admin/static/admin/js/calendar.js:22 msgid "December" msgstr "Decembar" +#: contrib/admin/static/admin/js/calendar.js:25 msgctxt "abbrev. month January" msgid "Jan" msgstr "jan" +#: contrib/admin/static/admin/js/calendar.js:26 msgctxt "abbrev. month February" msgid "Feb" msgstr "feb" +#: contrib/admin/static/admin/js/calendar.js:27 msgctxt "abbrev. month March" msgid "Mar" msgstr "mart" +#: contrib/admin/static/admin/js/calendar.js:28 msgctxt "abbrev. month April" msgid "Apr" msgstr "apr" +#: contrib/admin/static/admin/js/calendar.js:29 msgctxt "abbrev. month May" msgid "May" msgstr "maj" +#: contrib/admin/static/admin/js/calendar.js:30 msgctxt "abbrev. month June" msgid "Jun" msgstr "jun" +#: contrib/admin/static/admin/js/calendar.js:31 msgctxt "abbrev. month July" msgid "Jul" msgstr "jul" +#: contrib/admin/static/admin/js/calendar.js:32 msgctxt "abbrev. month August" msgid "Aug" msgstr "avg" +#: contrib/admin/static/admin/js/calendar.js:33 msgctxt "abbrev. month September" msgid "Sep" msgstr "sep" +#: contrib/admin/static/admin/js/calendar.js:34 msgctxt "abbrev. month October" msgid "Oct" msgstr "okt" +#: contrib/admin/static/admin/js/calendar.js:35 msgctxt "abbrev. month November" msgid "Nov" msgstr "nov" +#: contrib/admin/static/admin/js/calendar.js:36 msgctxt "abbrev. month December" msgid "Dec" msgstr "dec" +#: contrib/admin/static/admin/js/calendar.js:39 +msgid "Sunday" +msgstr "nedelja" + +#: contrib/admin/static/admin/js/calendar.js:40 +msgid "Monday" +msgstr "ponedeljak" + +#: contrib/admin/static/admin/js/calendar.js:41 +msgid "Tuesday" +msgstr "utorak" + +#: contrib/admin/static/admin/js/calendar.js:42 +msgid "Wednesday" +msgstr "sreda" + +#: contrib/admin/static/admin/js/calendar.js:43 +msgid "Thursday" +msgstr "četvrtak" + +#: contrib/admin/static/admin/js/calendar.js:44 +msgid "Friday" +msgstr "petak" + +#: contrib/admin/static/admin/js/calendar.js:45 +msgid "Saturday" +msgstr "subota" + +#: contrib/admin/static/admin/js/calendar.js:48 +msgctxt "abbrev. day Sunday" +msgid "Sun" +msgstr "ned" + +#: contrib/admin/static/admin/js/calendar.js:49 +msgctxt "abbrev. day Monday" +msgid "Mon" +msgstr "pon" + +#: contrib/admin/static/admin/js/calendar.js:50 +msgctxt "abbrev. day Tuesday" +msgid "Tue" +msgstr "uto" + +#: contrib/admin/static/admin/js/calendar.js:51 +msgctxt "abbrev. day Wednesday" +msgid "Wed" +msgstr "sre" + +#: contrib/admin/static/admin/js/calendar.js:52 +msgctxt "abbrev. day Thursday" +msgid "Thur" +msgstr "čet" + +#: contrib/admin/static/admin/js/calendar.js:53 +msgctxt "abbrev. day Friday" +msgid "Fri" +msgstr "pet" + +#: contrib/admin/static/admin/js/calendar.js:54 +msgctxt "abbrev. day Saturday" +msgid "Sat" +msgstr "sub" + +#: contrib/admin/static/admin/js/calendar.js:57 msgctxt "one letter Sunday" msgid "S" msgstr "N" +#: contrib/admin/static/admin/js/calendar.js:58 msgctxt "one letter Monday" msgid "M" msgstr "P" +#: contrib/admin/static/admin/js/calendar.js:59 msgctxt "one letter Tuesday" msgid "T" msgstr "U" +#: contrib/admin/static/admin/js/calendar.js:60 msgctxt "one letter Wednesday" msgid "W" msgstr "S" +#: contrib/admin/static/admin/js/calendar.js:61 msgctxt "one letter Thursday" msgid "T" msgstr "Č" +#: contrib/admin/static/admin/js/calendar.js:62 msgctxt "one letter Friday" msgid "F" msgstr "P" +#: contrib/admin/static/admin/js/calendar.js:63 msgctxt "one letter Saturday" msgid "S" msgstr "S" - -msgid "Show" -msgstr "Pokaži" - -msgid "Hide" -msgstr "Sakrij" diff --git a/django/contrib/admin/locale/sv/LC_MESSAGES/django.mo b/django/contrib/admin/locale/sv/LC_MESSAGES/django.mo index e729444344..44dc92ddcc 100644 Binary files a/django/contrib/admin/locale/sv/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/sv/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/sv/LC_MESSAGES/django.po b/django/contrib/admin/locale/sv/LC_MESSAGES/django.po index c81e13024a..bc85c431dd 100644 --- a/django/contrib/admin/locale/sv/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/sv/LC_MESSAGES/django.po @@ -12,6 +12,7 @@ # Jannis Leidel , 2011 # Johan Rohdin, 2021 # Jonathan Lindén, 2015 +# Jörgen Olofsson, 2024 # Jonathan Lindén, 2014 # metteludwig , 2019 # Mattias Hansson , 2016 @@ -21,9 +22,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: Albin Larsson , 2022-2023\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-08-07 07:05+0000\n" +"Last-Translator: Jörgen Olofsson, 2024\n" "Language-Team: Swedish (http://app.transifex.com/django/django/language/" "sv/)\n" "MIME-Version: 1.0\n" @@ -193,7 +194,7 @@ msgstr "" "Håll inne “Control”, eller “Command” på en Mac, för att välja fler än en." msgid "Select this object for an action - {}" -msgstr "" +msgstr "Välj detta objekt för en åtgärd - {}" #, python-brace-format msgid "The {name} “{obj}” was added successfully." @@ -212,10 +213,6 @@ msgid "" "The {name} “{obj}” was changed successfully. You may edit it again below." msgstr "Ändrade {name} “{obj}”. Du kan göra ytterligare förändringar nedan." -#, python-brace-format -msgid "The {name} “{obj}” was added successfully. You may edit it again below." -msgstr "Lade till {name} “{obj}”. Du kan göra ytterligare förändringar nedan." - #, python-brace-format msgid "" "The {name} “{obj}” was changed successfully. You may add another {name} " @@ -379,6 +376,9 @@ msgstr "Mata in användarnamn och lösenord." msgid "Change password" msgstr "Ändra lösenord" +msgid "Set password" +msgstr "Sätt lösenord" + msgid "Please correct the error below." msgid_plural "Please correct the errors below." msgstr[0] "Var god rätta felet nedan." @@ -388,6 +388,19 @@ msgstr[1] "Vad god rätta felen nedan." msgid "Enter a new password for the user %(username)s." msgstr "Ange nytt lösenord för användare %(username)s." +msgid "" +"This action will enable password-based authentication for " +"this user." +msgstr "" +"Denna åtgärd aktiverar lösenordsbaserad autentisering för " +"denna användare." + +msgid "Disable password-based authentication" +msgstr "Inaktivera lösenordsbaserad autentisering" + +msgid "Enable password-based authentication" +msgstr "Aktivera lösenordsbaserad autentisering" + msgid "Skip to main content" msgstr "Hoppa till huvudinnehållet" @@ -417,10 +430,10 @@ msgid "Filter" msgstr "Filtrera" msgid "Hide counts" -msgstr "" +msgstr "Dölj antal" msgid "Show counts" -msgstr "" +msgstr "Visa antal" msgid "Clear all filters" msgstr "Rensa alla filter" @@ -531,13 +544,13 @@ msgid "None available" msgstr "Inga tillgängliga" msgid "Added:" -msgstr "" +msgstr "Lagt till:" msgid "Changed:" -msgstr "" +msgstr "Ändrade:" msgid "Deleted:" -msgstr "" +msgstr "Raderade:" msgid "Unknown content" msgstr "Okänt innehåll" @@ -749,7 +762,7 @@ msgid "Reset my password" msgstr "Nollställ mitt lösenord" msgid "Select all objects on this page for an action" -msgstr "" +msgstr "Välj alla objekt på denna sida för en åtgärd" msgid "All dates" msgstr "Alla datum" diff --git a/django/contrib/admin/locale/sv/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/sv/LC_MESSAGES/djangojs.mo index daccc9e09b..47d3efe969 100644 Binary files a/django/contrib/admin/locale/sv/LC_MESSAGES/djangojs.mo and b/django/contrib/admin/locale/sv/LC_MESSAGES/djangojs.mo differ diff --git a/django/contrib/admin/locale/sv/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/sv/LC_MESSAGES/djangojs.po index 927f4b13f3..28bd355fbd 100644 --- a/django/contrib/admin/locale/sv/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/sv/LC_MESSAGES/djangojs.po @@ -2,10 +2,11 @@ # # Translators: # Anders Hovmöller , 2023 -# Andreas Pelme , 2012 +# Andreas Pelme , 2012,2024 # Danijel Grujicic, 2023 # Elias Johnstone , 2022 # Jannis Leidel , 2011 +# Jörgen Olofsson, 2024 # Jonathan Lindén, 2014 # Mattias Hansson , 2016 # Mattias Benjaminsson , 2011 @@ -15,9 +16,9 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-09-18 15:04-0300\n" -"PO-Revision-Date: 2023-12-04 07:59+0000\n" -"Last-Translator: Danijel Grujicic, 2023\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-08-07 07:59+0000\n" +"Last-Translator: Jörgen Olofsson, 2024\n" "Language-Team: Swedish (http://app.transifex.com/django/django/language/" "sv/)\n" "MIME-Version: 1.0\n" @@ -85,8 +86,8 @@ msgstr "Klicka för att ta bort alla valda %s på en gång." #, javascript-format msgid "%s selected option not visible" msgid_plural "%s selected options not visible" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "%s valt alternativ inte synligt" +msgstr[1] "%s valda alternativ inte synliga" msgid "%(sel)s of %(cnt)s selected" msgid_plural "%(sel)s of %(cnt)s selected" @@ -250,53 +251,53 @@ msgid "Dec" msgstr "dec" msgid "Sunday" -msgstr "" +msgstr "Söndag" msgid "Monday" -msgstr "" +msgstr "Måndag" msgid "Tuesday" -msgstr "" +msgstr "Tisdag" msgid "Wednesday" -msgstr "" +msgstr "Onsdag" msgid "Thursday" -msgstr "" +msgstr "Torsdag" msgid "Friday" -msgstr "" +msgstr "Fredag" msgid "Saturday" -msgstr "" +msgstr "Lördag" msgctxt "abbrev. day Sunday" msgid "Sun" -msgstr "" +msgstr "sön" msgctxt "abbrev. day Monday" msgid "Mon" -msgstr "" +msgstr "mån" msgctxt "abbrev. day Tuesday" msgid "Tue" -msgstr "" +msgstr "tis" msgctxt "abbrev. day Wednesday" msgid "Wed" -msgstr "" +msgstr "ons" msgctxt "abbrev. day Thursday" msgid "Thur" -msgstr "" +msgstr "tors" msgctxt "abbrev. day Friday" msgid "Fri" -msgstr "" +msgstr "fre" msgctxt "abbrev. day Saturday" msgid "Sat" -msgstr "" +msgstr "lör" msgctxt "one letter Sunday" msgid "S" @@ -325,9 +326,3 @@ msgstr "F" msgctxt "one letter Saturday" msgid "S" msgstr "L" - -msgid "Show" -msgstr "Visa" - -msgid "Hide" -msgstr "Göm" diff --git a/django/contrib/admin/locale/tk/LC_MESSAGES/django.mo b/django/contrib/admin/locale/tk/LC_MESSAGES/django.mo index 0b77d710e7..fd8f0e1f0a 100644 Binary files a/django/contrib/admin/locale/tk/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/tk/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/tk/LC_MESSAGES/django.po b/django/contrib/admin/locale/tk/LC_MESSAGES/django.po index f0d75c5d80..ad73340b02 100644 --- a/django/contrib/admin/locale/tk/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/tk/LC_MESSAGES/django.po @@ -2,15 +2,18 @@ # # Translators: # Mariusz Felisiak , 2022 +# Natalia, 2024 +# Resul , 2024 +# Rovshen Tagangylyjov, 2024 # Welbeck Garli , 2022 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 07:05+0000\n" -"Last-Translator: Mariusz Felisiak , 2022\n" -"Language-Team: Turkmen (http://www.transifex.com/django/django/language/" +"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" +"Language-Team: Turkmen (http://app.transifex.com/django/django/language/" "tk/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -46,7 +49,7 @@ msgid "No" msgstr "Ýok" msgid "Unknown" -msgstr "Nätanyş" +msgstr "Näbelli" msgid "Any date" msgstr "Islendik sene" @@ -58,16 +61,16 @@ msgid "Past 7 days" msgstr "Soňky 7 gün" msgid "This month" -msgstr "Şul aý" +msgstr "Şu aý" msgid "This year" -msgstr "Şul ýyl" +msgstr "Şu ýyl" msgid "No date" msgstr "Senesiz" msgid "Has date" -msgstr "Seneli" +msgstr "Senesi bar" msgid "Empty" msgstr "Boş" @@ -80,7 +83,7 @@ msgid "" "Please enter the correct %(username)s and password for a staff account. Note " "that both fields may be case-sensitive." msgstr "" -"Administratiw bolmadyk hasap üçin dogry %(username)swe parol ulanmagyňyzy " +"Administratiw bolmadyk hasap üçin dogry %(username)s we parol ulanmagyňyzy " "sizden haýyş edýäris. Giriziljek maglumatlaryň harp ýalňyşsyz bolmagyny göz " "öňünde tutmagy unutmaň." @@ -95,7 +98,7 @@ msgid "Remove" msgstr "Aýyr" msgid "Addition" -msgstr "Goşmaça" +msgstr "Goşmak" msgid "Change" msgstr "Üýtget" @@ -110,18 +113,18 @@ msgid "user" msgstr "ulanyjy" msgid "content type" -msgstr "maglumat görnüşi" +msgstr "mazmun görnüşi" msgid "object id" -msgstr "obýekt id'sy" +msgstr "obýekt id-sy" #. Translators: 'repr' means representation #. (https://docs.python.org/library/functions.html#repr) msgid "object repr" -msgstr "obýekt repr'y" +msgstr "obýekt repr-y" msgid "action flag" -msgstr "hereket baýdaklandyryşy" +msgstr "hereket belligi" msgid "change message" msgstr "Habarnamany üýtget" @@ -149,7 +152,7 @@ msgstr "GirişHabarnamasy Obýekty" #, python-brace-format msgid "Added {name} “{object}”." -msgstr "Goşuldy {name} \"{object}\"." +msgstr "Goşuldy {name} “{object}”." msgid "Added." msgstr "Goşuldy." @@ -163,7 +166,7 @@ msgstr "" #, python-brace-format msgid "Changed {fields}." -msgstr "" +msgstr "{fields} üýtgedildi." #, python-brace-format msgid "Deleted {name} “{object}”." @@ -178,6 +181,9 @@ msgstr "" msgid "Hold down “Control”, or “Command” on a Mac, to select more than one." msgstr "" +msgid "Select this object for an action - {}" +msgstr "" + #, python-brace-format msgid "The {name} “{obj}” was added successfully." msgstr "" @@ -195,10 +201,6 @@ msgid "" "The {name} “{obj}” was changed successfully. You may edit it again below." msgstr "" -#, 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} " @@ -328,6 +330,9 @@ msgstr "" msgid "Clear selection" msgstr "" +msgid "Breadcrumbs" +msgstr "" + #, python-format msgid "Models in the %(name)s application" msgstr "" @@ -352,6 +357,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] "" @@ -361,6 +369,17 @@ msgstr[1] "" msgid "Enter a new password for the user %(username)s." msgstr "" +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 "" @@ -376,9 +395,6 @@ msgstr "" msgid "Log out" msgstr "" -msgid "Breadcrumbs" -msgstr "" - #, python-format msgid "Add %(name)s" msgstr "" @@ -392,6 +408,12 @@ msgstr "" msgid "Filter" msgstr "" +msgid "Hide counts" +msgstr "" + +msgid "Show counts" +msgstr "" + msgid "Clear all filters" msgstr "" @@ -486,6 +508,15 @@ msgstr "" msgid "None available" msgstr "" +msgid "Added:" +msgstr "" + +msgid "Changed:" +msgstr "" + +msgid "Deleted:" +msgstr "" + msgid "Unknown content" msgstr "" @@ -673,6 +704,9 @@ msgstr "" msgid "Reset my password" msgstr "" +msgid "Select all objects on this page for an action" +msgstr "" + msgid "All dates" msgstr "" diff --git a/django/contrib/admin/locale/tr/LC_MESSAGES/django.mo b/django/contrib/admin/locale/tr/LC_MESSAGES/django.mo index 3f2ee2536c..75038f36b5 100644 Binary files a/django/contrib/admin/locale/tr/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/tr/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/tr/LC_MESSAGES/django.po b/django/contrib/admin/locale/tr/LC_MESSAGES/django.po index d2428b5a29..be27db296e 100644 --- a/django/contrib/admin/locale/tr/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/tr/LC_MESSAGES/django.po @@ -1,7 +1,7 @@ # This file is distributed under the same license as the Django package. # # Translators: -# BouRock, 2015-2023 +# BouRock, 2015-2024 # BouRock, 2014-2015 # Caner Başaran , 2013 # Cihad GÜNDOĞDU , 2012 @@ -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: BouRock, 2015-2023\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-08-07 07:05+0000\n" +"Last-Translator: BouRock, 2015-2024\n" "Language-Team: Turkish (http://app.transifex.com/django/django/language/" "tr/)\n" "MIME-Version: 1.0\n" @@ -210,11 +210,6 @@ msgstr "" "{name} “{obj}” başarılı olarak değiştirildi. Aşağıda tekrar " "düzenleyebilirsiniz." -#, python-brace-format -msgid "The {name} “{obj}” was added successfully. You may edit it again below." -msgstr "" -"{name} “{obj}” başarılı olarak eklendi. Aşağıda tekrar düzenleyebilirsiniz." - #, python-brace-format msgid "" "The {name} “{obj}” was changed successfully. You may add another {name} " @@ -381,6 +376,9 @@ msgstr "Kullanıcı adı ve parola girin." msgid "Change password" msgstr "Parolayı değiştir" +msgid "Set password" +msgstr "Parola ayarla" + msgid "Please correct the error below." msgid_plural "Please correct the errors below." msgstr[0] "Lütfen aşağıdaki hatayı düzeltin." @@ -390,6 +388,19 @@ msgstr[1] "Lütfen aşağıdaki hataları düzeltin." msgid "Enter a new password for the user %(username)s." msgstr "%(username)s kullanıcısı için yeni bir parola girin." +msgid "" +"This action will enable password-based authentication for " +"this user." +msgstr "" +"Bu eylem, bu kullanıcı için parola tabanlı kimlik doğrulaması " +"etkinleştirecektir." + +msgid "Disable password-based authentication" +msgstr "Parola tabanlı kimlik doğrulamasını etkisizleştir" + +msgid "Enable password-based authentication" +msgstr "Parola tabanlı kimlik doğrulamasını etkinleştir" + msgid "Skip to main content" msgstr "Ana içeriğe atla" 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/locale/zh_Hans/LC_MESSAGES/django.mo b/django/contrib/admin/locale/zh_Hans/LC_MESSAGES/django.mo index 4bee1a8d45..7bec9b7a2c 100644 Binary files a/django/contrib/admin/locale/zh_Hans/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/zh_Hans/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/zh_Hans/LC_MESSAGES/django.po b/django/contrib/admin/locale/zh_Hans/LC_MESSAGES/django.po index abc545310c..c6fcf86e8f 100644 --- a/django/contrib/admin/locale/zh_Hans/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/zh_Hans/LC_MESSAGES/django.po @@ -24,6 +24,7 @@ # yf zhan , 2018 # dykai , 2019 # ced773123cfad7b4e8b79ca80f736af9, 2012 +# 千百度, 2024 # LatteFang <370358679@qq.com>, 2020 # Kevin Sze , 2012 # 考证 李 , 2020 @@ -34,9 +35,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 07:05+0000\n" -"Last-Translator: jack yang, 2023\n" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-08-07 07:05+0000\n" +"Last-Translator: 千百度, 2024\n" "Language-Team: Chinese (China) (http://app.transifex.com/django/django/" "language/zh_CN/)\n" "MIME-Version: 1.0\n" @@ -223,10 +224,6 @@ msgid "" "The {name} “{obj}” was changed successfully. You may edit it again below." 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} " @@ -385,6 +382,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] "请更正以下错误。" @@ -393,6 +393,17 @@ msgstr[0] "请更正以下错误。" 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/locale/zh_Hant/LC_MESSAGES/django.mo b/django/contrib/admin/locale/zh_Hant/LC_MESSAGES/django.mo index a96ef9a02b..da1da7026d 100644 Binary files a/django/contrib/admin/locale/zh_Hant/LC_MESSAGES/django.mo and b/django/contrib/admin/locale/zh_Hant/LC_MESSAGES/django.mo differ diff --git a/django/contrib/admin/locale/zh_Hant/LC_MESSAGES/django.po b/django/contrib/admin/locale/zh_Hant/LC_MESSAGES/django.po index a2a1d9a3a5..89d0a940e2 100644 --- a/django/contrib/admin/locale/zh_Hant/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/zh_Hant/LC_MESSAGES/django.po @@ -5,19 +5,21 @@ # ilay , 2012 # Jannis Leidel , 2011 # mail6543210 , 2013-2014 -# ming hsien tzang , 2011 +# 0a3cb7bfd0810218facdfb511e592a6d_8d19d07 , 2011 # tcc , 2011 # Tzu-ping Chung , 2016-2017 +# YAO WEN LIANG, 2024 # Yeh-Yung , 2013 +# yubike, 2024 # Yeh-Yung , 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-19 16:40+0000\n" -"Last-Translator: Tzu-ping Chung \n" -"Language-Team: Chinese (Taiwan) (http://www.transifex.com/django/django/" +"POT-Creation-Date: 2024-05-22 11:46-0300\n" +"PO-Revision-Date: 2024-08-07 07:05+0000\n" +"Last-Translator: YAO WEN LIANG, 2024\n" +"Language-Team: Chinese (Taiwan) (http://app.transifex.com/django/django/" "language/zh_TW/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -25,6 +27,10 @@ msgstr "" "Language: zh_TW\n" "Plural-Forms: nplurals=1; plural=0;\n" +#, python-format +msgid "Delete selected %(verbose_name_plural)s" +msgstr "刪除所選的 %(verbose_name_plural)s" + #, python-format msgid "Successfully deleted %(count)d %(items)s." msgstr "成功的刪除了 %(count)d 個 %(items)s." @@ -36,10 +42,6 @@ msgstr "無法刪除 %(name)s" msgid "Are you sure?" msgstr "你確定嗎?" -#, python-format -msgid "Delete selected %(verbose_name_plural)s" -msgstr "刪除所選的 %(verbose_name_plural)s" - msgid "Administration" msgstr "管理" @@ -76,11 +78,17 @@ msgstr "沒有日期" msgid "Has date" msgstr "有日期" +msgid "Empty" +msgstr "空的" + +msgid "Not empty" +msgstr "非空的" + #, python-format msgid "" "Please enter the correct %(username)s and password for a staff account. Note " "that both fields may be case-sensitive." -msgstr "請輸入正確的工作人員%(username)s及密碼。請注意兩者皆區分大小寫。" +msgstr "請輸入正確的工作人員帳號%(username)s及密碼。請注意兩者皆區分大小寫。" msgid "Action:" msgstr "動作:" @@ -92,6 +100,15 @@ msgstr "新增其它 %(verbose_name)s" msgid "Remove" msgstr "移除" +msgid "Addition" +msgstr "新增" + +msgid "Change" +msgstr "修改" + +msgid "Deletion" +msgstr "删除" + msgid "action time" msgstr "動作時間" @@ -105,7 +122,7 @@ msgid "object id" msgstr "物件 id" #. Translators: 'repr' means representation -#. (https://docs.python.org/3/library/functions.html#repr) +#. (https://docs.python.org/library/functions.html#repr) msgid "object repr" msgstr "物件 repr" @@ -113,31 +130,31 @@ msgid "action flag" msgstr "動作旗標" msgid "change message" -msgstr "變更訊息" +msgstr "修改訊息" msgid "log entry" -msgstr "紀錄項目" +msgstr "日誌記錄" msgid "log entries" -msgstr "紀錄項目" +msgstr "日誌紀錄" #, python-format -msgid "Added \"%(object)s\"." +msgid "Added “%(object)s”." msgstr "\"%(object)s\" 已新增。" #, python-format -msgid "Changed \"%(object)s\" - %(changes)s" -msgstr "\"%(object)s\" - %(changes)s 已變更。" +msgid "Changed “%(object)s” — %(changes)s" +msgstr "\"%(object)s\" - %(changes)s 已修改。" #, python-format -msgid "Deleted \"%(object)s.\"" +msgid "Deleted “%(object)s.”" msgstr "\"%(object)s\" 已刪除。" msgid "LogEntry Object" -msgstr "紀錄項目" +msgstr "日誌記錄物件" #, python-brace-format -msgid "Added {name} \"{object}\"." +msgid "Added {name} “{object}”." msgstr "{name} \"{object}\" 已新增。" msgid "Added." @@ -147,71 +164,70 @@ msgid "and" msgstr "和" #, python-brace-format -msgid "Changed {fields} for {name} \"{object}\"." -msgstr "{name} \"{object}\" 的 {fields} 已變更。" +msgid "Changed {fields} for {name} “{object}”." +msgstr "{name} \"{object}\" 的 {fields} 已修改。" #, python-brace-format msgid "Changed {fields}." -msgstr "{fields} 已變更。" +msgstr "{fields} 已修改。" #, python-brace-format -msgid "Deleted {name} \"{object}\"." +msgid "Deleted {name} “{object}”." msgstr "{name} \"{object}\" 已刪除。" msgid "No fields changed." -msgstr "沒有欄位被變更。" +msgstr "沒有欄位被修改。" msgid "None" msgstr "無" -msgid "" -"Hold down \"Control\", or \"Command\" on a Mac, to select more than one." -msgstr "按住 \"Control\" 或 \"Command\" (Mac),可選取多個值" +msgid "Hold down “Control”, or “Command” on a Mac, to select more than one." +msgstr "按住 \"Control\", 或者在 Mac 上按 \"Command\", 以選取更多值" + +msgid "Select this object for an action - {}" +msgstr "選擇此對象進行操作 - {}" + +#, python-brace-format +msgid "The {name} “{obj}” was added successfully." +msgstr "{name} \"{obj}\" 已成功新增。" + +msgid "You may edit it again below." +msgstr "您可以在下面再次編輯它." #, 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 added successfully. You may add another {name} " -"below." +"The {name} “{obj}” was added successfully. You may add another {name} below." msgstr "{name} \"{obj}\" 新增成功。你可以在下方加入其他 {name}。" #, python-brace-format -msgid "The {name} \"{obj}\" was added successfully." -msgstr "{name} \"{obj}\" 已成功新增。" +msgid "" +"The {name} “{obj}” was changed successfully. You may edit it again below." +msgstr "{name} \"{obj}\" 修改成功。你可以在下方再次編輯。" #, python-brace-format msgid "" -"The {name} \"{obj}\" was changed 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} " +"The {name} “{obj}” was changed successfully. You may add another {name} " "below." -msgstr "{name} \"{obj}\" 變更成功。你可以在下方加入其他 {name}。" +msgstr "{name} \"{obj}\" 修改成功。你可以在下方加入其他 {name}。" #, python-brace-format -msgid "The {name} \"{obj}\" was changed successfully." -msgstr "{name} \"{obj}\" 已成功變更。" +msgid "The {name} “{obj}” was changed successfully." +msgstr "成功修改了 {name}“{obj}”。" msgid "" "Items must be selected in order to perform actions on them. No items have " "been changed." -msgstr "必須要有項目被選到才能對它們進行動作。沒有項目變更。" +msgstr "必須要有項目被選中才能進行動作。沒有任何項目被修改。" msgid "No action selected." -msgstr "沒有動作被選。" +msgstr "沒有動作被選取。" #, python-format -msgid "The %(name)s \"%(obj)s\" was deleted successfully." -msgstr "%(name)s \"%(obj)s\" 已成功刪除。" +msgid "The %(name)s “%(obj)s” was deleted successfully." +msgstr "成功删除了 %(name)s“%(obj)s”。" #, python-format -msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgid "%(name)s with ID “%(key)s” doesn’t exist. Perhaps it was deleted?" msgstr "不存在 ID 為「%(key)s」的 %(name)s。或許它已被刪除?" #, python-format @@ -220,7 +236,11 @@ msgstr "新增 %s" #, python-format msgid "Change %s" -msgstr "變更 %s" +msgstr "修改 %s" + +#, python-format +msgid "View %s" +msgstr "查看 %s" msgid "Database error" msgstr "資料庫錯誤" @@ -228,23 +248,24 @@ msgstr "資料庫錯誤" #, python-format msgid "%(count)s %(name)s was changed successfully." msgid_plural "%(count)s %(name)s were changed successfully." -msgstr[0] "共 %(count)s %(name)s 已變更成功。" +msgstr[0] "共 %(count)s %(name)s 已修改成功。" #, python-format msgid "%(total_count)s selected" msgid_plural "All %(total_count)s selected" -msgstr[0] "全部 %(total_count)s 個被選" +msgstr[0] "選取了 %(total_count)s 個" #, python-format msgid "0 of %(cnt)s selected" -msgstr "%(cnt)s 中 0 個被選" +msgstr "%(cnt)s 中 0 個被選取" #, python-format msgid "Change history: %s" -msgstr "變更歷史: %s" +msgstr "修改歷史: %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" @@ -274,10 +295,10 @@ msgid "%(app)s administration" msgstr "%(app)s 管理" msgid "Page not found" -msgstr "頁面沒有找到" +msgstr "找不到頁面" -msgid "We're sorry, but the requested page could not be found." -msgstr "很抱歉,請求頁面無法找到。" +msgid "We’re sorry, but the requested page could not be found." +msgstr "很抱歉,請求頁面不存在。" msgid "Home" msgstr "首頁" @@ -292,17 +313,17 @@ msgid "Server Error (500)" msgstr "伺服器錯誤 (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 "" -"存在一個錯誤。已透過電子郵件回報給網站管理員,並且應該很快就會被修正。謝謝你" -"的關心。" +"存在一個錯誤。已透過電子郵件回報給網站管理員,並且應該很快就會被修正。謝謝您" +"的耐心等待。" msgid "Run the selected action" -msgstr "執行選擇的動作" +msgstr "執行選取的動作" msgid "Go" -msgstr "去" +msgstr "執行" msgid "Click here to select the objects across all pages" msgstr "點選這裡可選取全部頁面的物件" @@ -314,27 +335,58 @@ msgstr "選擇全部 %(total_count)s %(module_name)s" msgid "Clear selection" msgstr "清除選擇" +msgid "Breadcrumbs" +msgstr "導覽路徑" + +#, python-format +msgid "Models in the %(name)s application" +msgstr "%(name)s 應用程式中的模型" + +msgid "Add" +msgstr "新增" + +msgid "View" +msgstr "查看" + +msgid "You don’t have permission to view or edit anything." +msgstr "你沒有查看或編輯的權限。" + 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 "首先,輸入一個使用者名稱和密碼。然後你可以編輯更多使用者選項。" +msgstr "輸入使用者名稱和密碼後,你可以編輯更多使用者選項。" msgid "Enter a username and password." -msgstr "輸入一個使用者名稱和密碼。" +msgstr "輸入使用者名稱和密碼。" msgid "Change password" -msgstr "變更密碼" +msgstr "修改密碼" + +msgid "Set password" +msgstr "設定密碼" msgid "Please correct the error below." -msgstr "請更正下面的錯誤。" - -msgid "Please correct the errors below." -msgstr "請修正以下錯誤" +msgid_plural "Please correct the errors below." +msgstr[0] "請修正以下錯誤。" #, python-format 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 "跳到主要內容" + msgid "Welcome," msgstr "歡迎," @@ -360,6 +412,15 @@ msgstr "在網站上檢視" msgid "Filter" msgstr "過濾器" +msgid "Hide counts" +msgstr "隱藏計數" + +msgid "Show counts" +msgstr "顯示計數" + +msgid "Clear all filters" +msgstr "清除所有篩選" + msgid "Remove from sorting" msgstr "從排序中移除" @@ -370,6 +431,15 @@ msgstr "優先排序:%(priority_number)s" msgid "Toggle sorting" msgstr "切換排序" +msgid "Toggle theme (current theme: auto)" +msgstr "切換主題(當前主題:自動)" + +msgid "Toggle theme (current theme: light)" +msgstr "切換主題(當前主題:淺色)" + +msgid "Toggle theme (current theme: dark)" +msgstr "切換主題(當前主題:深色)" + msgid "Delete" msgstr "刪除" @@ -400,11 +470,11 @@ msgstr "" msgid "Objects" msgstr "物件" -msgid "Yes, I'm sure" +msgid "Yes, I’m sure" msgstr "是的,我確定" msgid "No, take me back" -msgstr "不,請帶我回去" +msgstr "不,返回" msgid "Delete multiple objects" msgstr "刪除多個物件" @@ -431,9 +501,6 @@ msgid "" msgstr "" "你是否確定要刪除已選的 %(objects_name)s? 下面全部物件及其相關項目都將被刪除:" -msgid "Change" -msgstr "變更" - msgid "Delete?" msgstr "刪除?" @@ -444,16 +511,6 @@ msgstr " 以 %(filter_title)s" msgid "Summary" msgstr "總結" -#, python-format -msgid "Models in the %(name)s application" -msgstr "%(name)s 應用程式中的Model" - -msgid "Add" -msgstr "新增" - -msgid "You don't have permission to edit anything." -msgstr "你沒有編輯任何東西的權限。" - msgid "Recent actions" msgstr "最近的動作" @@ -461,13 +518,22 @@ msgid "My actions" msgstr "我的動作" msgid "None available" -msgstr "無可用的" +msgstr "無資料" + +msgid "Added:" +msgstr "已新增。" + +msgid "Changed:" +msgstr "已修改:" + +msgid "Deleted:" +msgstr "已刪除:" msgid "Unknown content" msgstr "未知內容" 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 "" @@ -479,10 +545,23 @@ 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 "" -"您已認證為 %(username)s,但並沒有瀏覽此頁面的權限。您是否希望以其他帳號登入?" +"您目前以%(username)s登入,但並沒有瀏覽此頁面的權限。您是否希望以其他帳號登" +"入?" msgid "Forgotten your password or username?" -msgstr "忘了你的密碼或是使用者名稱?" +msgstr "忘記您的密碼或是使用者名稱?" + +msgid "Toggle navigation" +msgstr "切換導航" + +msgid "Sidebar" +msgstr "側邊欄" + +msgid "Start typing to filter…" +msgstr "輸入內容開始篩選..." + +msgid "Filter navigation items" +msgstr "篩選導航項目" msgid "Date/time" msgstr "日期/時間" @@ -493,10 +572,14 @@ msgstr "使用者" msgid "Action" msgstr "動作" +msgid "entry" +msgid_plural "entries" +msgstr[0] "紀錄項目" + 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 "這個物件沒有變更的歷史。它可能不是透過這個管理網站新增的。" +msgstr "該物件沒有修改的歷史紀錄。它可能不是透過此管理網站新增的。" msgid "Show all" msgstr "顯示全部" @@ -504,20 +587,8 @@ msgstr "顯示全部" msgid "Save" msgstr "儲存" -msgid "Popup closing..." -msgstr "關閉彈出視窗中⋯⋯" - -#, python-format -msgid "Change selected %(model)s" -msgstr "變更所選的 %(model)s" - -#, python-format -msgid "Add another %(model)s" -msgstr "新增其它 %(model)s" - -#, python-format -msgid "Delete selected %(model)s" -msgstr "刪除所選的 %(model)s" +msgid "Popup closing…" +msgstr "關閉彈跳視窗中..." msgid "Search" msgstr "搜尋" @@ -540,30 +611,50 @@ msgstr "儲存並新增另一個" msgid "Save and continue editing" msgstr "儲存並繼續編輯" -msgid "Thanks for spending some quality time with the Web site today." -msgstr "感謝你今天花了重要的時間停留在本網站。" +msgid "Save and view" +msgstr "儲存並查看" + +msgid "Close" +msgstr "關閉" + +#, python-format +msgid "Change selected %(model)s" +msgstr "修改所選的 %(model)s" + +#, python-format +msgid "Add another %(model)s" +msgstr "新增其它 %(model)s" + +#, python-format +msgid "Delete selected %(model)s" +msgstr "刪除所選的 %(model)s" + +#, python-format +msgid "View selected %(model)s" +msgstr "查看已選擇的%(model)s" + +msgid "Thanks for spending some quality time with the web site today." +msgstr "感謝您今天在網站上度過了一段美好的時光。" msgid "Log in again" msgstr "重新登入" msgid "Password change" -msgstr "密碼變更" +msgstr "密碼修改" msgid "Your password was changed." -msgstr "你的密碼已變更。" +msgstr "您的密碼已修改。" 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 "" -"為了安全上的考量,請輸入你的舊密碼,再輸入新密碼兩次,讓我們核驗你已正確地輸" -"入。" +msgstr "為了安全上的考量,請輸入你的舊密碼,然後輸入兩次新密碼已確保輸入正確。" msgid "Change my password" -msgstr "變更我的密碼" +msgstr "修改我的密碼" msgid "Password reset" -msgstr "密碼重設" +msgstr "重設密碼" msgid "Your password has been set. You may go ahead and log in now." msgstr "你的密碼已設置,現在可以繼續登入。" @@ -574,7 +665,7 @@ msgstr "密碼重設確認" msgid "" "Please enter your new password twice so we can verify you typed it in " "correctly." -msgstr "請輸入你的新密碼兩次, 這樣我們才能檢查你的輸入是否正確。" +msgstr "請輸入新密碼兩次, 以便系統確認輸入無誤。" msgid "New password:" msgstr "新密碼:" @@ -585,17 +676,17 @@ msgstr "確認密碼:" msgid "" "The password reset link was invalid, possibly because it has already been " "used. Please request a new password reset." -msgstr "密碼重設連結無效,可能因為他已使用。請重新請求密碼重設。" +msgstr "密碼重設連結無效,可能已被使用。請重新申請密碼重設。" 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 "" "若您提交的電子郵件地址存在對應帳號,我們已寄出重設密碼的相關指示。您應該很快" "就會收到。" 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 "" "如果您未收到電子郵件,請確認您輸入的電子郵件地址與您註冊時輸入的一致,並檢查" @@ -605,13 +696,13 @@ msgstr "" msgid "" "You're receiving this email because you requested a password reset for your " "user account at %(site_name)s." -msgstr "這封電子郵件來自 %(site_name)s,因為你要求為帳號重新設定密碼。" +msgstr "這封電子郵件來自 %(site_name)s,因為您要求為帳號重新設定密碼。" msgid "Please go to the following page and choose a new password:" msgstr "請到該頁面選擇一個新的密碼:" -msgid "Your username, in case you've forgotten:" -msgstr "你的使用者名稱,萬一你已經忘記的話:" +msgid "Your username, in case you’ve forgotten:" +msgstr "提醒一下,您的用戶名是:" msgid "Thanks for using our site!" msgstr "感謝使用本網站!" @@ -621,11 +712,10 @@ msgid "The %(site_name)s team" msgstr "%(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 "" -"忘記你的密碼? 請在下面輸入你的電子郵件位址, 然後我們會寄出設定新密碼的操作指" -"示。" +"忘記您的密碼? 請在下面輸入您的電子郵件, 然後我們會寄出設定新密碼的操作指示。" msgid "Email address:" msgstr "電子信箱:" @@ -633,6 +723,9 @@ msgstr "電子信箱:" msgid "Reset my password" msgstr "重設我的密碼" +msgid "Select all objects on this page for an action" +msgstr "選擇此頁面上的所有物件執行操作" + msgid "All dates" msgstr "所有日期" @@ -642,7 +735,11 @@ msgstr "選擇 %s" #, python-format msgid "Select %s to change" -msgstr "選擇 %s 來變更" +msgstr "選擇 %s 來修改" + +#, python-format +msgid "Select %s to view" +msgstr "選擇%s查看" msgid "Date:" msgstr "日期" @@ -651,7 +748,7 @@ msgid "Time:" msgstr "時間" msgid "Lookup" -msgstr "查詢" +msgstr "查找" msgid "Currently:" msgstr "目前:" diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index 2257b3072e..6d5c0708a3 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -2229,7 +2229,7 @@ class ModelAdmin(BaseModelAdmin): if perms_needed or protected: title = _("Cannot delete %(name)s") % {"name": object_name} else: - title = _("Are you sure?") + title = _("Delete") context = { **self.admin_site.each_context(request), diff --git a/django/contrib/admin/static/admin/css/base.css b/django/contrib/admin/static/admin/css/base.css index 769195af13..37910431a0 100644 --- a/django/contrib/admin/static/admin/css/base.css +++ b/django/contrib/admin/static/admin/css/base.css @@ -13,6 +13,7 @@ html[data-theme="light"], --body-fg: #333; --body-bg: #fff; --body-quiet-color: #666; + --body-medium-color: #444; --body-loud-color: #000; --header-color: #ffc; @@ -149,7 +150,6 @@ h1 { margin: 0 0 20px; font-weight: 300; font-size: 1.25rem; - color: var(--body-quiet-color); } h2 { @@ -165,7 +165,7 @@ h2.subhead { h3 { font-size: 0.875rem; margin: .8em 0 .3em 0; - color: var(--body-quiet-color); + color: var(--body-medium-color); font-weight: bold; } @@ -173,6 +173,7 @@ h4 { font-size: 0.75rem; margin: 1em 0 .8em 0; padding-bottom: 3px; + color: var(--body-medium-color); } h5 { @@ -319,7 +320,7 @@ td, th { } th { - font-weight: 600; + font-weight: 500; text-align: left; } @@ -340,7 +341,7 @@ tfoot td { } thead th.required { - color: var(--body-loud-color); + font-weight: bold; } tr.alt { @@ -1120,6 +1121,7 @@ a.deletelink:focus, a.deletelink:hover { margin: 0; border-top: 1px solid var(--hairline-color); width: 100%; + box-sizing: border-box; } .paginator a:link, .paginator a:visited { diff --git a/django/contrib/admin/static/admin/css/dark_mode.css b/django/contrib/admin/static/admin/css/dark_mode.css index 2123be05c4..7e12a81578 100644 --- a/django/contrib/admin/static/admin/css/dark_mode.css +++ b/django/contrib/admin/static/admin/css/dark_mode.css @@ -5,7 +5,8 @@ --body-fg: #eeeeee; --body-bg: #121212; - --body-quiet-color: #e0e0e0; + --body-quiet-color: #d0d0d0; + --body-medium-color: #e0e0e0; --body-loud-color: #ffffff; --breadcrumbs-link-fg: #e0e0e0; @@ -41,7 +42,8 @@ html[data-theme="dark"] { --body-fg: #eeeeee; --body-bg: #121212; - --body-quiet-color: #e0e0e0; + --body-quiet-color: #d0d0d0; + --body-medium-color: #e0e0e0; --body-loud-color: #ffffff; --breadcrumbs-link-fg: #e0e0e0; diff --git a/django/contrib/admin/static/admin/css/forms.css b/django/contrib/admin/static/admin/css/forms.css index 8b24fad39f..c6ce78833e 100644 --- a/django/contrib/admin/static/admin/css/forms.css +++ b/django/contrib/admin/static/admin/css/forms.css @@ -44,7 +44,6 @@ label { .required label, label.required { font-weight: bold; - color: var(--body-fg); } /* RADIO BUTTONS */ @@ -170,6 +169,10 @@ form .aligned select + div.help { padding-left: 10px; } +form .aligned select option:checked { + background-color: var(--selected-row); +} + form .aligned ul li { list-style: none; } @@ -381,7 +384,7 @@ body.popup .submit-row { .inline-related h4, .inline-related:not(.tabular) .collapse summary { margin: 0; - color: var(--body-quiet-color); + color: var(--body-medium-color); padding: 5px; font-size: 0.8125rem; background: var(--darkened-bg); @@ -390,10 +393,6 @@ body.popup .submit-row { border-right-color: var(--darkened-bg); } -.inline-related h3 { - color: var(--body-loud-color); -} - .inline-related h3 span.delete { float: right; } @@ -450,17 +449,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); @@ -474,11 +462,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/SelectFilter2.js b/django/contrib/admin/static/admin/js/SelectFilter2.js index 6957412462..133d809d52 100644 --- a/django/contrib/admin/static/admin/js/SelectFilter2.js +++ b/django/contrib/admin/static/admin/js/SelectFilter2.js @@ -118,7 +118,7 @@ Requires core.js and SelectBox.js. const warning_footer = quickElement('div', selector_chosen, '', 'class', 'list-footer-display'); quickElement('span', warning_footer, '', 'id', field_id + '_list-footer-display-text'); - quickElement('span', warning_footer, ' (click to clear)', 'class', 'list-footer-display__clear'); + quickElement('span', warning_footer, ' ' + gettext('(click to clear)'), 'class', 'list-footer-display__clear'); const clear_all = quickElement('a', selector_chosen, gettext('Remove all'), 'title', interpolate(gettext('Click to remove all chosen %s at once.'), [field_name]), 'href', '#', 'id', field_id + '_remove_all_link'); clear_all.className = 'selector-clearall'; diff --git a/django/contrib/admin/static/admin/js/admin/RelatedObjectLookups.js b/django/contrib/admin/static/admin/js/admin/RelatedObjectLookups.js index bc3accea37..74d17bfc3e 100644 --- a/django/contrib/admin/static/admin/js/admin/RelatedObjectLookups.js +++ b/django/contrib/admin/static/admin/js/admin/RelatedObjectLookups.js @@ -87,7 +87,7 @@ } } - function updateRelatedSelectsOptions(currentSelect, win, objId, newRepr, newId) { + function updateRelatedSelectsOptions(currentSelect, win, objId, newRepr, newId, skipIds = []) { // After create/edit a model from the options next to the current // select (+ or :pencil:) update ForeignKey PK of the rest of selects // in the page. @@ -100,7 +100,7 @@ const selectsRelated = document.querySelectorAll(`[data-model-ref="${modelName}"] [data-context="available-source"]`); selectsRelated.forEach(function(select) { - if (currentSelect === select) { + if (currentSelect === select || skipIds && skipIds.includes(select.id)) { return; } @@ -109,6 +109,11 @@ if (!option) { option = new Option(newRepr, newId); select.options.add(option); + // Update SelectBox cache for related fields. + if (window.SelectBox !== undefined && !SelectBox.cache[currentSelect.id]) { + SelectBox.add_to_cache(select.id, option); + SelectBox.redisplay(select.id); + } return; } @@ -136,9 +141,14 @@ $(elem).trigger('change'); } else { const toId = name + "_to"; + const toElem = document.getElementById(toId); const o = new Option(newRepr, newId); SelectBox.add_to_cache(toId, o); SelectBox.redisplay(toId); + if (toElem && toElem.nodeName.toUpperCase() === 'SELECT') { + const skipIds = [name + "_from"]; + updateRelatedSelectsOptions(toElem, win, null, newRepr, newId, skipIds); + } } const index = relatedWindows.indexOf(win); if (index > -1) { 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/app_list.html b/django/contrib/admin/templates/admin/app_list.html index 3b67b5feab..60d874b2b6 100644 --- a/django/contrib/admin/templates/admin/app_list.html +++ b/django/contrib/admin/templates/admin/app_list.html @@ -7,6 +7,13 @@ {{ app.name }} + + + {% translate 'Model name' %} + {% translate 'Add link' %} + {% translate 'Change or view list link' %} + + {% for model in app.models %} {% with model_name=model.object_name|lower %} 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/auth/user/change_password.html b/django/contrib/admin/templates/admin/auth/user/change_password.html index 6801fe5fa7..2f96a71436 100644 --- a/django/contrib/admin/templates/admin/auth/user/change_password.html +++ b/django/contrib/admin/templates/admin/auth/user/change_password.html @@ -2,6 +2,7 @@ {% load i18n static %} {% load admin_urls %} +{% block title %}{% if form.errors %}{% translate "Error:" %} {% endif %}{{ block.super }}{% endblock %} {% block extrastyle %} {{ block.super }} 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/change_form.html b/django/contrib/admin/templates/admin/change_form.html index 31ff5d6c10..8e7ced9a48 100644 --- a/django/contrib/admin/templates/admin/change_form.html +++ b/django/contrib/admin/templates/admin/change_form.html @@ -1,6 +1,7 @@ {% extends "admin/base_site.html" %} {% load i18n admin_urls static admin_modify %} +{% block title %}{% if errors %}{% translate "Error:" %} {% endif %}{{ block.super }}{% endblock %} {% block extrahead %}{{ block.super }} {{ media }} @@ -47,7 +48,7 @@ {% block field_sets %} {% for fieldset in adminform %} - {% include "admin/includes/fieldset.html" with heading_level=2 id_suffix=forloop.counter0 %} + {% include "admin/includes/fieldset.html" with heading_level=2 prefix="fieldset" id_prefix=0 id_suffix=forloop.counter0 %} {% endfor %} {% endblock %} diff --git a/django/contrib/admin/templates/admin/change_list.html b/django/contrib/admin/templates/admin/change_list.html index 310872b015..b0b4c31619 100644 --- a/django/contrib/admin/templates/admin/change_list.html +++ b/django/contrib/admin/templates/admin/change_list.html @@ -1,6 +1,7 @@ {% extends "admin/base_site.html" %} {% load i18n admin_urls static admin_list %} +{% block title %}{% if cl.formset and cl.formset.errors %}{% translate "Error:" %} {% endif %}{{ block.super }}{% endblock %} {% block extrastyle %} {{ block.super }} diff --git a/django/contrib/admin/templates/admin/edit_inline/stacked.html b/django/contrib/admin/templates/admin/edit_inline/stacked.html index 73f459ee47..a6939f4ea2 100644 --- a/django/contrib/admin/templates/admin/edit_inline/stacked.html +++ b/django/contrib/admin/templates/admin/edit_inline/stacked.html @@ -26,7 +26,7 @@ {% with parent_counter=forloop.counter0 %} {% for fieldset in inline_admin_form %} - {% include "admin/includes/fieldset.html" with heading_level=4 id_prefix=parent_counter id_suffix=forloop.counter0 %} + {% include "admin/includes/fieldset.html" with heading_level=4 prefix=fieldset.formset.prefix id_prefix=parent_counter id_suffix=forloop.counter0 %} {% endfor %} {% endwith %} diff --git a/django/contrib/admin/templates/admin/includes/fieldset.html b/django/contrib/admin/templates/admin/includes/fieldset.html index b4eef47547..8c1830da62 100644 --- a/django/contrib/admin/templates/admin/includes/fieldset.html +++ b/django/contrib/admin/templates/admin/includes/fieldset.html @@ -1,4 +1,4 @@ -{% with prefix=fieldset.formset.prefix|default:"fieldset" id_prefix=id_prefix|default:"0" id_suffix=id_suffix|default:"0" name=fieldset.name|default:""|slugify %} +{% with name=fieldset.name|default:""|slugify %}
{% if name %} {% if fieldset.is_collapsible %}
{% endif %} @@ -27,7 +27,7 @@ {% endif %} {% if field.field.help_text %} -
+
{{ field.field.help_text|safe }}
{% endif %} diff --git a/django/contrib/admin/templates/admin/login.html b/django/contrib/admin/templates/admin/login.html index b61d9ec603..fa0dcbc01d 100644 --- a/django/contrib/admin/templates/admin/login.html +++ b/django/contrib/admin/templates/admin/login.html @@ -1,6 +1,7 @@ {% extends "admin/base_site.html" %} {% load i18n static %} +{% block title %}{% if form.errors %}{% translate "Error:" %} {% endif %}{{ block.super }}{% endblock %} {% block extrastyle %}{{ block.super }} {{ form.media }} {% endblock %} @@ -56,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/admin/widgets/foreign_key_raw_id.html b/django/contrib/admin/templates/admin/widgets/foreign_key_raw_id.html index be93e0581d..a6eba931c4 100644 --- a/django/contrib/admin/templates/admin/widgets/foreign_key_raw_id.html +++ b/django/contrib/admin/templates/admin/widgets/foreign_key_raw_id.html @@ -1,2 +1,2 @@ -{% include 'django/forms/widgets/input.html' %}{% if related_url %}{% endif %}{% if link_label %} -{% if link_url %}{{ link_label }}{% else %}{{ link_label }}{% endif %}{% endif %} +{% if related_url %}
{% endif %}{% include 'django/forms/widgets/input.html' %}{% if related_url %}{% endif %}{% if link_label %} +{% if link_url %}{{ link_label }}{% else %}{{ link_label }}{% endif %}{% endif %}{% if related_url %}
{% endif %} diff --git a/django/contrib/admin/templates/registration/password_change_form.html b/django/contrib/admin/templates/registration/password_change_form.html index fde2373e08..3d66aeb162 100644 --- a/django/contrib/admin/templates/registration/password_change_form.html +++ b/django/contrib/admin/templates/registration/password_change_form.html @@ -1,5 +1,7 @@ {% extends "admin/base_site.html" %} {% load i18n static %} + +{% block title %}{% if form.errors %}{% translate "Error:" %} {% endif %}{{ block.super }}{% endblock %} {% block extrastyle %}{{ block.super }}{% endblock %} {% block userlinks %} {% url 'django-admindocs-docroot' as docsroot %}{% if docsroot %}{% translate 'Documentation' %} / {% endif %} {% translate 'Change password' %} / diff --git a/django/contrib/admin/templates/registration/password_reset_confirm.html b/django/contrib/admin/templates/registration/password_reset_confirm.html index a07645c97a..5e1478be83 100644 --- a/django/contrib/admin/templates/registration/password_reset_confirm.html +++ b/django/contrib/admin/templates/registration/password_reset_confirm.html @@ -1,6 +1,7 @@ {% extends "admin/base_site.html" %} {% load i18n static %} +{% block title %}{% if form.new_password1.errors or form.new_password2.errors %}{% translate "Error:" %} {% endif %}{{ block.super }}{% endblock %} {% block extrastyle %}{{ block.super }}{% endblock %} {% block breadcrumbs %} ' + '', response.rendered_content, ) @@ -407,13 +414,17 @@ class TestInline(TestDataMixin, TestCase): self.assertInHTML( '', + '
0
' + '' + "
", response.rendered_content, ) self.assertInHTML( '', + '
1
' + '' + "", response.rendered_content, ) @@ -448,7 +459,12 @@ class TestInline(TestDataMixin, TestCase): self.assertInHTML( '' 'Name' - 'Position' + 'Position' + '' + "" "Delete?", response.rendered_content, ) @@ -1768,6 +1784,13 @@ class TestInlineWithFieldsets(TestDataMixin, TestCase): def setUp(self): self.client.force_login(self.superuser) + @override_settings(DEBUG=True) + def test_fieldset_context_fully_set(self): + url = reverse("admin:admin_inlines_photographer_add") + with self.assertRaisesMessage(AssertionError, "no logs"): + with self.assertLogs("django.template", "DEBUG"): + self.client.get(url) + def test_inline_headings(self): response = self.client.get(reverse("admin:admin_inlines_photographer_add")) # Page main title. diff --git a/tests/admin_scripts/tests.py b/tests/admin_scripts/tests.py index 2e77f2c97a..29023b74c3 100644 --- a/tests/admin_scripts/tests.py +++ b/tests/admin_scripts/tests.py @@ -25,6 +25,7 @@ from django.core.management import ( color, execute_from_command_line, ) +from django.core.management.base import LabelCommand from django.core.management.commands.loaddata import Command as LoaddataCommand from django.core.management.commands.runserver import Command as RunserverCommand from django.core.management.commands.testserver import Command as TestserverCommand @@ -33,7 +34,7 @@ from django.db.migrations.recorder import MigrationRecorder from django.test import LiveServerTestCase, SimpleTestCase, TestCase, override_settings from django.test.utils import captured_stderr, captured_stdout from django.urls import path -from django.utils.version import PY313 +from django.utils.version import PY313, get_docs_version from django.views.static import serve from . import urls @@ -1597,6 +1598,15 @@ class ManageRunserver(SimpleTestCase): "Starting development server at http://0.0.0.0:8000/", self.output.getvalue(), ) + docs_version = get_docs_version() + self.assertIn( + "WARNING: This is a development server. Do not use it in a " + "production setting. Use a production WSGI or ASGI server instead." + "\nFor more information on production servers see: " + f"https://docs.djangoproject.com/en/{docs_version}/howto/" + "deployment/", + self.output.getvalue(), + ) def test_on_bind(self): self.cmd.addr = "127.0.0.1" @@ -1606,6 +1616,34 @@ class ManageRunserver(SimpleTestCase): "Starting development server at http://127.0.0.1:14437/", self.output.getvalue(), ) + docs_version = get_docs_version() + self.assertIn( + "WARNING: This is a development server. Do not use it in a " + "production setting. Use a production WSGI or ASGI server instead." + "\nFor more information on production servers see: " + f"https://docs.djangoproject.com/en/{docs_version}/howto/" + "deployment/", + self.output.getvalue(), + ) + + @mock.patch.dict(os.environ, {"HIDE_PRODUCTION_WARNING": "true"}) + def test_hide_production_warning_with_environment_variable(self): + self.cmd.addr = "0" + self.cmd._raw_ipv6 = False + self.cmd.on_bind("8000") + self.assertIn( + "Starting development server at http://0.0.0.0:8000/", + self.output.getvalue(), + ) + docs_version = get_docs_version() + self.assertNotIn( + "WARNING: This is a development server. Do not use it in a " + "production setting. Use a production WSGI or ASGI server instead." + "\nFor more information on production servers see: " + f"https://docs.djangoproject.com/en/{docs_version}/howto/" + "deployment/", + self.output.getvalue(), + ) @unittest.skipUnless(socket.has_ipv6, "platform doesn't support IPv6") def test_runner_addrport_ipv6(self): @@ -2243,6 +2281,20 @@ class CommandTypes(AdminScriptTestCase): "('settings', None), ('traceback', False), ('verbosity', 1)]", ) + def test_custom_label_command_custom_missing_args_message(self): + class Command(LabelCommand): + missing_args_message = "Missing argument." + + with self.assertRaisesMessage(CommandError, "Error: Missing argument."): + call_command(Command()) + + def test_custom_label_command_none_missing_args_message(self): + class Command(LabelCommand): + missing_args_message = None + + with self.assertRaisesMessage(CommandError, ""): + call_command(Command()) + def test_suppress_base_options_command_help(self): args = ["suppress_base_options_command", "--help"] out, err = self.run_manage(args) diff --git a/tests/admin_utils/test_logentry.py b/tests/admin_utils/test_logentry.py index 20bbcccb1c..e97441eb2e 100644 --- a/tests/admin_utils/test_logentry.py +++ b/tests/admin_utils/test_logentry.py @@ -240,7 +240,7 @@ class LogEntryTests(TestCase): def test_log_action(self): msg = "LogEntryManager.log_action() is deprecated. Use log_actions() instead." content_type_val = ContentType.objects.get_for_model(Article).pk - with self.assertWarnsMessage(RemovedInDjango60Warning, msg): + with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx: log_entry = LogEntry.objects.log_action( self.user.pk, content_type_val, @@ -250,6 +250,7 @@ class LogEntryTests(TestCase): change_message="Changed something else", ) self.assertEqual(log_entry, LogEntry.objects.latest("id")) + self.assertEqual(ctx.filename, __file__) def test_log_actions(self): queryset = Article.objects.all().order_by("-id") @@ -297,9 +298,12 @@ class LogEntryTests(TestCase): msg = ( "The usage of log_action() is deprecated. Implement log_actions() instead." ) - with self.assertNumQueries(3): - with self.assertWarnsMessage(RemovedInDjango60Warning, msg): - LogEntry.objects2.log_actions(self.user.pk, queryset, DELETION) + with ( + self.assertNumQueries(3), + self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx, + ): + LogEntry.objects2.log_actions(self.user.pk, queryset, DELETION) + self.assertEqual(ctx.filename, __file__) log_values = ( LogEntry.objects.filter(action_flag=DELETION) .order_by("id") diff --git a/tests/admin_views/test_actions.py b/tests/admin_views/test_actions.py index 8e1fc144e4..467fe046ef 100644 --- a/tests/admin_views/test_actions.py +++ b/tests/admin_views/test_actions.py @@ -72,6 +72,7 @@ class AdminActionsTest(TestCase): self.assertContains( confirmation, "Are you sure you want to delete the selected subscribers?" ) + self.assertContains(confirmation, "

Delete multiple objects

") self.assertContains(confirmation, "

Summary

") self.assertContains(confirmation, "
  • Subscribers: 2
  • ") self.assertContains(confirmation, "
  • External subscribers: 1
  • ") diff --git a/tests/admin_views/test_related_object_lookups.py b/tests/admin_views/test_related_object_lookups.py index 761819a50f..4b2171a09f 100644 --- a/tests/admin_views/test_related_object_lookups.py +++ b/tests/admin_views/test_related_object_lookups.py @@ -3,6 +3,8 @@ from django.contrib.auth.models import User from django.test import override_settings from django.urls import reverse +from .models import CamelCaseModel + @override_settings(ROOT_URLCONF="admin_views.urls") class SeleniumTests(AdminSeleniumTestCase): @@ -100,6 +102,8 @@ class SeleniumTests(AdminSeleniumTestCase): self.wait_until(lambda d: len(d.window_handles) == 1, 1) self.selenium.switch_to.window(self.selenium.window_handles[0]) + id_value = CamelCaseModel.objects.get(interesting_name=interesting_name).id + # Check that both the "Available" m2m box and the "Fk" dropdown now # include the newly added CamelCaseModel instance. fk_dropdown = self.selenium.find_element(By.ID, "id_fk") @@ -107,7 +111,7 @@ class SeleniumTests(AdminSeleniumTestCase): fk_dropdown.get_attribute("innerHTML"), f""" - + """, ) # Check the newly added instance is not also added in the "to" box. @@ -117,6 +121,61 @@ class SeleniumTests(AdminSeleniumTestCase): self.assertHTMLEqual( m2m_box.get_attribute("innerHTML"), f""" - + """, ) + + def test_related_object_add_js_actions(self): + from selenium.webdriver.common.by import By + + add_url = reverse("admin:admin_views_camelcaserelatedmodel_add") + self.selenium.get(self.live_server_url + add_url) + m2m_to = self.selenium.find_element(By.ID, "id_m2m_to") + m2m_box = self.selenium.find_element(By.ID, "id_m2m_from") + fk_dropdown = self.selenium.find_element(By.ID, "id_fk") + + # Add new related entry using +. + name = "Bergeron" + self.selenium.find_element(By.ID, "add_id_m2m").click() + self.wait_for_and_switch_to_popup() + self.selenium.find_element(By.ID, "id_interesting_name").send_keys(name) + self.selenium.find_element(By.NAME, "_save").click() + self.wait_until(lambda d: len(d.window_handles) == 1, 1) + self.selenium.switch_to.window(self.selenium.window_handles[0]) + + id_value = CamelCaseModel.objects.get(interesting_name=name).id + + # Check the new value correctly appears in the "to" box. + self.assertHTMLEqual( + m2m_to.get_attribute("innerHTML"), + f"""""", + ) + self.assertHTMLEqual(m2m_box.get_attribute("innerHTML"), "") + self.assertHTMLEqual( + fk_dropdown.get_attribute("innerHTML"), + f""" + + + """, + ) + + # Move the new value to the from box. + self.selenium.find_element(By.XPATH, "//*[@id='id_m2m_to']/option").click() + self.selenium.find_element(By.XPATH, "//*[@id='id_m2m_remove_link']").click() + + self.assertHTMLEqual( + m2m_box.get_attribute("innerHTML"), + f"""""", + ) + self.assertHTMLEqual(m2m_to.get_attribute("innerHTML"), "") + + # Move the new value to the to box. + self.selenium.find_element(By.XPATH, "//*[@id='id_m2m_from']/option").click() + self.selenium.find_element(By.XPATH, "//*[@id='id_m2m_add_link']").click() + + self.assertHTMLEqual(m2m_box.get_attribute("innerHTML"), "") + self.assertHTMLEqual( + m2m_to.get_attribute("innerHTML"), + f"""""", + ) diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py index e0a4926b91..f63a9ca56f 100644 --- a/tests/admin_views/tests.py +++ b/tests/admin_views/tests.py @@ -799,7 +799,9 @@ class AdminViewBasicTest(AdminViewBasicTestCase): reverse("admin:admin_views_complexsortedperson_changelist"), {} ) # Should have 5 columns (including action checkbox col) - self.assertContains(response, '(.*?)') + result_list_table_head = result_list_table_re.search(str(response.content))[0] + self.assertEqual(result_list_table_head.count('(.*?)') + result_list_table_head = result_list_table_re.search(str(response.content))[ + 0 + ] + self.assertEqual(result_list_table_head.count('Change article") self.assertContains(response, "

    Article 2

    ") + def test_error_in_titles(self): + for url, subtitle in [ + ( + reverse("admin:admin_views_article_change", args=(self.a1.pk,)), + "Article 1 | Change article", + ), + (reverse("admin:admin_views_article_add"), "Add article"), + (reverse("admin:login"), "Log in"), + (reverse("admin:password_change"), "Password change"), + ( + reverse("admin:auth_user_password_change", args=(self.superuser.id,)), + "Change password: super", + ), + ]: + with self.subTest(url=url, subtitle=subtitle): + response = self.client.post(url, {}) + self.assertContains(response, f"Error: {subtitle}") + def test_view_subtitle_per_object(self): viewuser = User.objects.create_user( username="viewuser", @@ -1662,7 +1686,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", @@ -3003,6 +3026,7 @@ class AdminViewPermissionsTest(TestCase): response = self.client.get( reverse("admin:admin_views_section_delete", args=(self.s1.pk,)) ) + self.assertContains(response, "<h1>Delete</h1>") self.assertContains(response, "<h2>Summary</h2>") self.assertContains(response, "<li>Articles: 3</li>") # test response contains link to related Article @@ -6151,6 +6175,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.""" @@ -7497,12 +7580,26 @@ class CSSTest(TestCase): # General index page response = self.client.get(reverse("admin:index")) self.assertContains(response, '<div class="app-admin_views module') + self.assertContains( + response, + '<thead class="visually-hidden"><tr><th scope="col">Model name</th>' + '<th scope="col">Add link</th><th scope="col">Change or view list link</th>' + "</tr></thead>", + html=True, + ) self.assertContains(response, '<tr class="model-actor">') self.assertContains(response, '<tr class="model-album">') # App index page response = self.client.get(reverse("admin:app_list", args=("admin_views",))) self.assertContains(response, '<div class="app-admin_views module') + self.assertContains( + response, + '<thead class="visually-hidden"><tr><th scope="col">Model name</th>' + '<th scope="col">Add link</th><th scope="col">Change or view list link</th>' + "</tr></thead>", + html=True, + ) self.assertContains(response, '<tr class="model-actor">') self.assertContains(response, '<tr class="model-album">') @@ -7656,7 +7753,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", diff --git a/tests/admin_widgets/tests.py b/tests/admin_widgets/tests.py index 6f009a6f3f..5da4adf8c9 100644 --- a/tests/admin_widgets/tests.py +++ b/tests/admin_widgets/tests.py @@ -23,6 +23,7 @@ from django.db.models import ( UUIDField, ) from django.test import SimpleTestCase, TestCase, ignore_warnings, override_settings +from django.test.selenium import screenshot_cases from django.test.utils import requires_tz_support from django.urls import reverse from django.utils import translation @@ -462,7 +463,12 @@ class AdminSplitDateTimeWidgetTest(SimpleTestCase): class AdminURLWidgetTest(SimpleTestCase): def test_get_context_validates_url(self): w = widgets.AdminURLFieldWidget() - for invalid in ["", "/not/a/full/url/", 'javascript:alert("Danger XSS!")']: + for invalid in [ + "", + "/not/a/full/url/", + 'javascript:alert("Danger XSS!")', + "http://" + "한.글." * 1_000_000 + "com", + ]: with self.subTest(url=invalid): self.assertFalse(w.get_context("name", invalid, {})["url_valid"]) self.assertTrue(w.get_context("name", "http://example.com", {})["url_valid"]) @@ -684,21 +690,21 @@ class ForeignKeyRawIdWidgetTest(TestCase): w = widgets.ForeignKeyRawIdWidget(rel_uuid, widget_admin_site) self.assertHTMLEqual( w.render("test", band.uuid, attrs={}), - '<input type="text" name="test" value="%(banduuid)s" ' + '<div><input type="text" name="test" value="%(banduuid)s" ' 'class="vForeignKeyRawIdAdminField vUUIDField">' '<a href="/admin_widgets/band/?_to_field=uuid" class="related-lookup" ' 'id="lookup_id_test" title="Lookup"></a> <strong>' '<a href="/admin_widgets/band/%(bandpk)s/change/">Linkin Park</a>' - "</strong>" % {"banduuid": band.uuid, "bandpk": band.pk}, + "</strong></div>" % {"banduuid": band.uuid, "bandpk": band.pk}, ) rel_id = ReleaseEvent._meta.get_field("album").remote_field w = widgets.ForeignKeyRawIdWidget(rel_id, widget_admin_site) self.assertHTMLEqual( w.render("test", None, attrs={}), - '<input type="text" name="test" class="vForeignKeyRawIdAdminField">' + '<div><input type="text" name="test" class="vForeignKeyRawIdAdminField">' '<a href="/admin_widgets/album/?_to_field=id" class="related-lookup" ' - 'id="lookup_id_test" title="Lookup"></a>', + 'id="lookup_id_test" title="Lookup"></a></div>', ) def test_relations_to_non_primary_key(self): @@ -711,12 +717,12 @@ class ForeignKeyRawIdWidgetTest(TestCase): w = widgets.ForeignKeyRawIdWidget(rel, widget_admin_site) self.assertHTMLEqual( w.render("test", core.parent_id, attrs={}), - '<input type="text" name="test" value="86" ' + '<div><input type="text" name="test" value="86" ' 'class="vForeignKeyRawIdAdminField">' '<a href="/admin_widgets/inventory/?_to_field=barcode" ' 'class="related-lookup" id="lookup_id_test" title="Lookup"></a>' ' <strong><a href="/admin_widgets/inventory/%(pk)s/change/">' - "Apple</a></strong>" % {"pk": apple.pk}, + "Apple</a></strong></div>" % {"pk": apple.pk}, ) def test_fk_related_model_not_in_admin(self): @@ -760,12 +766,12 @@ class ForeignKeyRawIdWidgetTest(TestCase): ) self.assertHTMLEqual( w.render("test", child_of_hidden.parent_id, attrs={}), - '<input type="text" name="test" value="93" ' + '<div><input type="text" name="test" value="93" ' ' class="vForeignKeyRawIdAdminField">' '<a href="/admin_widgets/inventory/?_to_field=barcode" ' 'class="related-lookup" id="lookup_id_test" title="Lookup"></a>' ' <strong><a href="/admin_widgets/inventory/%(pk)s/change/">' - "Hidden</a></strong>" % {"pk": hidden.pk}, + "Hidden</a></strong></div>" % {"pk": hidden.pk}, ) def test_render_unsafe_limit_choices_to(self): @@ -773,10 +779,10 @@ class ForeignKeyRawIdWidgetTest(TestCase): w = widgets.ForeignKeyRawIdWidget(rel, widget_admin_site) self.assertHTMLEqual( w.render("test", None), - '<input type="text" name="test" class="vForeignKeyRawIdAdminField">\n' + '<div><input type="text" name="test" class="vForeignKeyRawIdAdminField">' '<a href="/admin_widgets/band/?name=%22%26%3E%3Cescapeme&' '_to_field=artist_ptr" class="related-lookup" id="lookup_id_test" ' - 'title="Lookup"></a>', + 'title="Lookup"></a></div>', ) def test_render_fk_as_pk_model(self): @@ -784,9 +790,9 @@ class ForeignKeyRawIdWidgetTest(TestCase): w = widgets.ForeignKeyRawIdWidget(rel, widget_admin_site) self.assertHTMLEqual( w.render("test", None), - '<input type="text" name="test" class="vForeignKeyRawIdAdminField">\n' + '<div><input type="text" name="test" class="vForeignKeyRawIdAdminField">' '<a href="/admin_widgets/releaseevent/?_to_field=album" ' - 'class="related-lookup" id="lookup_id_test" title="Lookup"></a>', + 'class="related-lookup" id="lookup_id_test" title="Lookup"></a></div>', ) @@ -804,10 +810,10 @@ class ManyToManyRawIdWidgetTest(TestCase): self.assertHTMLEqual( w.render("test", [m1.pk, m2.pk], attrs={}), ( - '<input type="text" name="test" value="%(m1pk)s,%(m2pk)s" ' + '<div><input type="text" name="test" value="%(m1pk)s,%(m2pk)s" ' ' class="vManyToManyRawIdAdminField">' '<a href="/admin_widgets/member/" class="related-lookup" ' - ' id="lookup_id_test" title="Lookup"></a>' + ' id="lookup_id_test" title="Lookup"></a></div>' ) % {"m1pk": m1.pk, "m2pk": m2.pk}, ) @@ -815,10 +821,10 @@ class ManyToManyRawIdWidgetTest(TestCase): self.assertHTMLEqual( w.render("test", [m1.pk]), ( - '<input type="text" name="test" value="%(m1pk)s" ' + '<div><input type="text" name="test" value="%(m1pk)s" ' ' class="vManyToManyRawIdAdminField">' '<a href="/admin_widgets/member/" class="related-lookup" ' - ' id="lookup_id_test" title="Lookup"></a>' + ' id="lookup_id_test" title="Lookup"></a></div>' ) % {"m1pk": m1.pk}, ) @@ -1680,6 +1686,7 @@ class AdminRawIdWidgetSeleniumTests(AdminWidgetSeleniumTestCase): Band.objects.create(id=42, name="Bogey Blues") Band.objects.create(id=98, name="Green Potatoes") + @screenshot_cases(["desktop_size", "mobile_size", "rtl", "dark", "high_contrast"]) def test_ForeignKey(self): from selenium.webdriver.common.by import By @@ -1688,6 +1695,7 @@ class AdminRawIdWidgetSeleniumTests(AdminWidgetSeleniumTestCase): self.live_server_url + reverse("admin:admin_widgets_event_add") ) main_window = self.selenium.current_window_handle + self.take_screenshot("raw_id_widget") # No value has been selected yet self.assertEqual( diff --git a/tests/aggregation/tests.py b/tests/aggregation/tests.py index 075e707102..b6ba728e77 100644 --- a/tests/aggregation/tests.py +++ b/tests/aggregation/tests.py @@ -1750,6 +1750,26 @@ class AggregateTestCase(TestCase): ], ) + def test_order_by_aggregate_default_alias(self): + publisher_books = ( + Publisher.objects.values("book") + .annotate(Count("book")) + .order_by("book__count", "book__id") + .values_list("book", flat=True) + ) + self.assertQuerySetEqual( + publisher_books, + [ + None, + self.b1.id, + self.b2.id, + self.b3.id, + self.b4.id, + self.b5.id, + self.b6.id, + ], + ) + def test_empty_result_optimization(self): with self.assertNumQueries(0): self.assertEqual( @@ -2345,6 +2365,7 @@ class AggregateAnnotationPruningTests(TestCase): ).aggregate(count=Count("id", filter=Q(id__in=[F("max_book_author"), 0]))) self.assertEqual(aggregates, {"count": 1}) + @skipUnlessDBFeature("supports_select_union") def test_aggregate_combined_queries(self): # Combined queries could have members in their values select mask while # others have them in their annotation mask which makes annotation diff --git a/tests/annotations/models.py b/tests/annotations/models.py index fbb9ca6988..914770d2fe 100644 --- a/tests/annotations/models.py +++ b/tests/annotations/models.py @@ -58,3 +58,11 @@ class Company(models.Model): class Ticket(models.Model): active_at = models.DateTimeField() duration = models.DurationField() + + +class JsonModel(models.Model): + data = models.JSONField(default=dict, blank=True) + id = models.IntegerField(primary_key=True) + + class Meta: + required_db_features = {"supports_json_field"} diff --git a/tests/annotations/tests.py b/tests/annotations/tests.py index 703847e1dd..29660a827e 100644 --- a/tests/annotations/tests.py +++ b/tests/annotations/tests.py @@ -1,7 +1,9 @@ import datetime from decimal import Decimal +from unittest import skipUnless from django.core.exceptions import FieldDoesNotExist, FieldError +from django.db import connection from django.db.models import ( BooleanField, Case, @@ -15,6 +17,7 @@ from django.db.models import ( FloatField, Func, IntegerField, + JSONField, Max, OuterRef, Q, @@ -43,6 +46,7 @@ from .models import ( Company, DepartmentStore, Employee, + JsonModel, Publisher, Store, Ticket, @@ -1167,6 +1171,23 @@ class NonAggregateAnnotationTestCase(TestCase): with self.assertRaisesMessage(ValueError, msg): Book.objects.annotate(**{crafted_alias: Value(1)}) + @skipUnless(connection.vendor == "postgresql", "PostgreSQL tests") + @skipUnlessDBFeature("supports_json_field") + def test_set_returning_functions(self): + class JSONBPathQuery(Func): + function = "jsonb_path_query" + output_field = JSONField() + set_returning = True + + test_model = JsonModel.objects.create( + data={"key": [{"id": 1, "name": "test1"}, {"id": 2, "name": "test2"}]}, id=1 + ) + qs = JsonModel.objects.annotate( + table_element=JSONBPathQuery("data", Value("$.key[*]")) + ).filter(pk=test_model.pk) + + self.assertEqual(qs.count(), len(qs)) + class AliasTests(TestCase): @classmethod 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_forms.py b/tests/auth_tests/test_forms.py index 3dd9324304..5d81d8f7fd 100644 --- a/tests/auth_tests/test_forms.py +++ b/tests/auth_tests/test_forms.py @@ -5,6 +5,7 @@ from unittest import mock from django.contrib.auth.forms import ( AdminPasswordChangeForm, + AdminUserCreationForm, AuthenticationForm, BaseUserCreationForm, PasswordChangeForm, @@ -76,13 +77,22 @@ class ExtraValidationFormMixin: class BaseUserCreationFormTest(TestDataMixin, TestCase): + + form_class = BaseUserCreationForm + + def test_form_fields(self): + form = self.form_class() + self.assertEqual( + list(form.fields.keys()), ["username", "password1", "password2"] + ) + def test_user_already_exists(self): data = { "username": "testclient", "password1": "test123", "password2": "test123", } - form = BaseUserCreationForm(data) + form = self.form_class(data) self.assertFalse(form.is_valid()) self.assertEqual( form["username"].errors, @@ -95,7 +105,7 @@ class BaseUserCreationFormTest(TestDataMixin, TestCase): "password1": "test123", "password2": "test123", } - form = BaseUserCreationForm(data) + form = self.form_class(data) self.assertFalse(form.is_valid()) validator = next( v @@ -111,7 +121,7 @@ class BaseUserCreationFormTest(TestDataMixin, TestCase): "password1": "test123", "password2": "test", } - form = BaseUserCreationForm(data) + form = self.form_class(data) self.assertFalse(form.is_valid()) self.assertEqual( form["password2"].errors, [str(form.error_messages["password_mismatch"])] @@ -120,14 +130,14 @@ class BaseUserCreationFormTest(TestDataMixin, TestCase): def test_both_passwords(self): # One (or both) passwords weren't given data = {"username": "jsmith"} - form = BaseUserCreationForm(data) + form = self.form_class(data) required_error = [str(Field.default_error_messages["required"])] self.assertFalse(form.is_valid()) self.assertEqual(form["password1"].errors, required_error) self.assertEqual(form["password2"].errors, required_error) data["password2"] = "test123" - form = UserCreationForm(data) + form = self.form_class(data) self.assertFalse(form.is_valid()) self.assertEqual(form["password1"].errors, required_error) self.assertEqual(form["password2"].errors, []) @@ -140,7 +150,7 @@ class BaseUserCreationFormTest(TestDataMixin, TestCase): "password1": "test123", "password2": "test123", } - form = BaseUserCreationForm(data) + form = self.form_class(data) self.assertTrue(form.is_valid()) form.save(commit=False) self.assertEqual(password_changed.call_count, 0) @@ -154,7 +164,7 @@ class BaseUserCreationFormTest(TestDataMixin, TestCase): "password1": "test123", "password2": "test123", } - form = BaseUserCreationForm(data) + form = self.form_class(data) self.assertTrue(form.is_valid()) u = form.save() self.assertEqual(u.username, "宝") @@ -168,7 +178,7 @@ class BaseUserCreationFormTest(TestDataMixin, TestCase): "password1": "pwd2", "password2": "pwd2", } - form = BaseUserCreationForm(data) + form = self.form_class(data) self.assertTrue(form.is_valid()) user = form.save() self.assertNotEqual(user.username, ohm_username) @@ -195,7 +205,7 @@ class BaseUserCreationFormTest(TestDataMixin, TestCase): "password1": "pwd2", "password2": "pwd2", } - form = BaseUserCreationForm(data) + form = self.form_class(data) self.assertFalse(form.is_valid()) self.assertEqual( form.errors["username"], ["A user with that username already exists."] @@ -221,11 +231,11 @@ class BaseUserCreationFormTest(TestDataMixin, TestCase): ) def test_validates_password(self): data = { - "username": "testclient", - "password1": "testclient", - "password2": "testclient", + "username": "otherclient", + "password1": "otherclient", + "password2": "otherclient", } - form = BaseUserCreationForm(data) + form = self.form_class(data) self.assertFalse(form.is_valid()) self.assertEqual(len(form["password2"].errors), 2) self.assertIn( @@ -236,15 +246,108 @@ class BaseUserCreationFormTest(TestDataMixin, TestCase): form["password2"].errors, ) - # passwords are not validated if `usable_password` is unset + def test_password_whitespace_not_stripped(self): data = { - "username": "othertestclient", - "password1": "othertestclient", - "password2": "othertestclient", - "usable_password": "false", + "username": "testuser", + "password1": " testpassword ", + "password2": " testpassword ", } - form = BaseUserCreationForm(data) - self.assertIs(form.is_valid(), True, form.errors) + form = self.form_class(data) + self.assertTrue(form.is_valid()) + self.assertEqual(form.cleaned_data["password1"], data["password1"]) + self.assertEqual(form.cleaned_data["password2"], data["password2"]) + + @override_settings( + AUTH_PASSWORD_VALIDATORS=[ + { + "NAME": ( + "django.contrib.auth.password_validation." + "UserAttributeSimilarityValidator" + ) + }, + ] + ) + def test_password_help_text(self): + form = self.form_class() + self.assertEqual( + form.fields["password1"].help_text, + "<ul><li>" + "Your password can’t be too similar to your other personal information." + "</li></ul>", + ) + + def test_password_extra_validations(self): + class ExtraValidationForm(ExtraValidationFormMixin, self.form_class): + def clean_password1(self): + return self.failing_helper("password1") + + def clean_password2(self): + return self.failing_helper("password2") + + data = {"username": "extra", "password1": "abc", "password2": "abc"} + for fields in (["password1"], ["password2"], ["password1", "password2"]): + with self.subTest(fields=fields): + errors = {field: [f"Extra validation for {field}."] for field in fields} + form = ExtraValidationForm(data, failing_fields=errors) + self.assertIs(form.is_valid(), False) + self.assertDictEqual(form.errors, errors) + + @override_settings( + AUTH_PASSWORD_VALIDATORS=[ + { + "NAME": ( + "django.contrib.auth.password_validation." + "UserAttributeSimilarityValidator" + ) + }, + ] + ) + def test_user_create_form_validates_password_with_all_data(self): + """ + BaseUserCreationForm password validation uses all of the form's data. + """ + + class CustomUserCreationForm(self.form_class): + class Meta(self.form_class.Meta): + model = User + fields = ("username", "email", "first_name", "last_name") + + form = CustomUserCreationForm( + { + "username": "testuser", + "password1": "testpassword", + "password2": "testpassword", + "first_name": "testpassword", + "last_name": "lastname", + } + ) + self.assertFalse(form.is_valid()) + self.assertEqual( + form.errors["password2"], + ["The password is too similar to the first name."], + ) + + def test_username_field_autocapitalize_none(self): + form = self.form_class() + self.assertEqual( + form.fields["username"].widget.attrs.get("autocapitalize"), "none" + ) + + def test_html_autocomplete_attributes(self): + form = self.form_class() + tests = ( + ("username", "username"), + ("password1", "new-password"), + ("password2", "new-password"), + ) + for field_name, autocomplete in tests: + with self.subTest(field_name=field_name, autocomplete=autocomplete): + self.assertEqual( + form.fields[field_name].widget.attrs["autocomplete"], autocomplete + ) + + +class CustomUserCreationFormTest(TestDataMixin, TestCase): def test_custom_form(self): class CustomUserCreationForm(BaseUserCreationForm): @@ -309,132 +412,11 @@ class BaseUserCreationFormTest(TestDataMixin, TestCase): user = form.save(commit=True) self.assertSequenceEqual(user.orgs.all(), [organization]) - def test_password_whitespace_not_stripped(self): - data = { - "username": "testuser", - "password1": " testpassword ", - "password2": " testpassword ", - } - form = BaseUserCreationForm(data) - self.assertTrue(form.is_valid()) - self.assertEqual(form.cleaned_data["password1"], data["password1"]) - self.assertEqual(form.cleaned_data["password2"], data["password2"]) - @override_settings( - AUTH_PASSWORD_VALIDATORS=[ - { - "NAME": ( - "django.contrib.auth.password_validation." - "UserAttributeSimilarityValidator" - ) - }, - ] - ) - def test_password_help_text(self): - form = BaseUserCreationForm() - self.assertEqual( - form.fields["password1"].help_text, - "<ul><li>" - "Your password can’t be too similar to your other personal information." - "</li></ul>", - ) +class UserCreationFormTest(BaseUserCreationFormTest): - def test_password_extra_validations(self): - class ExtraValidationForm(ExtraValidationFormMixin, BaseUserCreationForm): - def clean_password1(self): - return self.failing_helper("password1") + form_class = UserCreationForm - def clean_password2(self): - return self.failing_helper("password2") - - data = {"username": "extra", "password1": "abc", "password2": "abc"} - for fields in (["password1"], ["password2"], ["password1", "password2"]): - with self.subTest(fields=fields): - errors = {field: [f"Extra validation for {field}."] for field in fields} - form = ExtraValidationForm(data, failing_fields=errors) - self.assertIs(form.is_valid(), False) - self.assertDictEqual(form.errors, errors) - - @override_settings( - AUTH_PASSWORD_VALIDATORS=[ - { - "NAME": ( - "django.contrib.auth.password_validation." - "UserAttributeSimilarityValidator" - ) - }, - ] - ) - def test_user_create_form_validates_password_with_all_data(self): - """ - BaseUserCreationForm password validation uses all of the form's data. - """ - - class CustomUserCreationForm(BaseUserCreationForm): - class Meta(BaseUserCreationForm.Meta): - model = User - fields = ("username", "email", "first_name", "last_name") - - form = CustomUserCreationForm( - { - "username": "testuser", - "password1": "testpassword", - "password2": "testpassword", - "first_name": "testpassword", - "last_name": "lastname", - } - ) - self.assertFalse(form.is_valid()) - self.assertEqual( - form.errors["password2"], - ["The password is too similar to the first name."], - ) - - # passwords are not validated if `usable_password` is unset - form = CustomUserCreationForm( - { - "username": "testuser", - "password1": "testpassword", - "password2": "testpassword", - "first_name": "testpassword", - "last_name": "lastname", - "usable_password": "false", - } - ) - self.assertIs(form.is_valid(), True, form.errors) - - def test_username_field_autocapitalize_none(self): - form = BaseUserCreationForm() - self.assertEqual( - form.fields["username"].widget.attrs.get("autocapitalize"), "none" - ) - - def test_html_autocomplete_attributes(self): - form = BaseUserCreationForm() - tests = ( - ("username", "username"), - ("password1", "new-password"), - ("password2", "new-password"), - ) - for field_name, autocomplete in tests: - with self.subTest(field_name=field_name, autocomplete=autocomplete): - self.assertEqual( - form.fields[field_name].widget.attrs["autocomplete"], autocomplete - ) - - def test_unusable_password(self): - data = { - "username": "new-user-which-does-not-exist", - "usable_password": "false", - } - form = BaseUserCreationForm(data) - self.assertIs(form.is_valid(), True, form.errors) - u = form.save() - self.assertEqual(u.username, data["username"]) - self.assertFalse(u.has_usable_password()) - - -class UserCreationFormTest(TestDataMixin, TestCase): def test_case_insensitive_username(self): data = { "username": "TeStClIeNt", @@ -1161,6 +1143,14 @@ class PasswordResetFormTest(TestDataMixin, TestCase): # makes tests interfere with each other, see #11505 Site.objects.clear_cache() + def assertEmailMessageSent(self, **kwargs): + self.assertEqual(len(mail.outbox), 1) + msg = mail.outbox[0] + for attr, expected in kwargs.items(): + with self.subTest(attr=attr): + self.assertEqual(getattr(msg, attr), expected) + return msg + def create_dummy_user(self): """ Create a user and return a tuple (user_object, username, email). @@ -1183,8 +1173,7 @@ class PasswordResetFormTest(TestDataMixin, TestCase): form = PasswordResetForm(data) self.assertTrue(form.is_valid()) form.save() - self.assertEqual(len(mail.outbox), 1) - self.assertEqual(mail.outbox[0].to, ["mıke@example.org"]) + self.assertEmailMessageSent(to=["mıke@example.org"]) def test_user_email_domain_unicode_collision(self): User.objects.create_user("mike123", "mike@ixample.org", "test123") @@ -1193,8 +1182,7 @@ class PasswordResetFormTest(TestDataMixin, TestCase): form = PasswordResetForm(data) self.assertTrue(form.is_valid()) form.save() - self.assertEqual(len(mail.outbox), 1) - self.assertEqual(mail.outbox[0].to, ["mike@ıxample.org"]) + self.assertEmailMessageSent(to=["mike@ıxample.org"]) def test_user_email_unicode_collision_nonexistent(self): User.objects.create_user("mike123", "mike@example.org", "test123") @@ -1229,7 +1217,7 @@ class PasswordResetFormTest(TestDataMixin, TestCase): self.assertTrue(form.is_valid()) form.save(domain_override="example.com") self.assertEqual(form.cleaned_data["email"], email) - self.assertEqual(len(mail.outbox), 1) + self.assertEmailMessageSent() def test_custom_email_subject(self): data = {"email": "testclient@example.com"} @@ -1239,8 +1227,7 @@ class PasswordResetFormTest(TestDataMixin, TestCase): # domain_override to prevent the save operation from failing in the # potential case where contrib.sites is not installed. Refs #16412. form.save(domain_override="example.com") - self.assertEqual(len(mail.outbox), 1) - self.assertEqual(mail.outbox[0].subject, "Custom password reset on example.com") + self.assertEmailMessageSent(subject="Custom password reset on example.com") def test_custom_email_constructor(self): data = {"email": "testclient@example.com"} @@ -1273,10 +1260,11 @@ class PasswordResetFormTest(TestDataMixin, TestCase): # domain_override to prevent the save operation from failing in the # potential case where contrib.sites is not installed. Refs #16412. form.save(domain_override="example.com") - self.assertEqual(len(mail.outbox), 1) - self.assertEqual(mail.outbox[0].subject, "Forgot your password?") - self.assertEqual(mail.outbox[0].bcc, ["site_monitor@example.com"]) - self.assertEqual(mail.outbox[0].content_subtype, "plain") + self.assertEmailMessageSent( + subject="Forgot your password?", + bcc=["site_monitor@example.com"], + content_subtype="plain", + ) def test_preserve_username_case(self): """ @@ -1323,12 +1311,12 @@ class PasswordResetFormTest(TestDataMixin, TestCase): form = PasswordResetForm({"email": email}) self.assertTrue(form.is_valid()) form.save() - self.assertEqual(len(mail.outbox), 1) - message = mail.outbox[0].message() + msg = self.assertEmailMessageSent() + self.assertEqual(len(msg.alternatives), 0) + message = msg.message() self.assertFalse(message.is_multipart()) self.assertEqual(message.get_content_type(), "text/plain") self.assertEqual(message.get("subject"), "Custom password reset on example.com") - self.assertEqual(len(mail.outbox[0].alternatives), 0) self.assertEqual(message.get_all("to"), [email]) self.assertTrue( re.match(r"^http://example.com/reset/[\w+/-]", message.get_payload()) @@ -1347,9 +1335,9 @@ class PasswordResetFormTest(TestDataMixin, TestCase): form.save( html_email_template_name="registration/html_password_reset_email.html" ) - self.assertEqual(len(mail.outbox), 1) - self.assertEqual(len(mail.outbox[0].alternatives), 1) - message = mail.outbox[0].message() + msg = self.assertEmailMessageSent() + self.assertEqual(len(msg.alternatives), 1) + message = msg.message() self.assertEqual(message.get("subject"), "Custom password reset on example.com") self.assertEqual(len(message.get_payload()), 2) self.assertTrue(message.is_multipart()) @@ -1369,6 +1357,27 @@ class PasswordResetFormTest(TestDataMixin, TestCase): ) ) + @override_settings(EMAIL_BACKEND="mail.custombackend.FailingEmailBackend") + def test_save_send_email_exceptions_are_catched_and_logged(self): + (user, username, email) = self.create_dummy_user() + form = PasswordResetForm({"email": email}) + self.assertTrue(form.is_valid()) + + with self.assertLogs("django.contrib.auth", level=0) as cm: + form.save() + + self.assertEqual(len(mail.outbox), 0) + self.assertEqual(len(cm.output), 1) + errors = cm.output[0].split("\n") + pk = user.pk + self.assertEqual( + errors[0], + f"ERROR:django.contrib.auth:Failed to send password reset email to {pk}", + ) + self.assertEqual( + errors[-1], "ValueError: FailingEmailBackend is doomed to fail." + ) + @override_settings(AUTH_USER_MODEL="auth_tests.CustomEmailField") def test_custom_email_field(self): email = "test@mail.com" @@ -1593,3 +1602,72 @@ class AdminPasswordChangeFormTest(TestDataMixin, TestCase): self.assertIs(form.is_valid(), True) # Valid despite password empty/mismatch. user = form.save(commit=True) self.assertIs(user.has_usable_password(), False) + + +class AdminUserCreationFormTest(BaseUserCreationFormTest): + + form_class = AdminUserCreationForm + + def test_form_fields(self): + form = self.form_class() + self.assertEqual( + list(form.fields.keys()), + ["username", "password1", "password2", "usable_password"], + ) + + @override_settings( + AUTH_PASSWORD_VALIDATORS=[ + { + "NAME": ( + "django.contrib.auth.password_validation." + "UserAttributeSimilarityValidator" + ) + }, + { + "NAME": ( + "django.contrib.auth.password_validation.MinimumLengthValidator" + ), + "OPTIONS": { + "min_length": 12, + }, + }, + ] + ) + def test_no_password_validation_if_unusable_password_set(self): + data = { + "username": "otherclient", + "password1": "otherclient", + "password2": "otherclient", + "usable_password": "false", + } + form = self.form_class(data) + # Passwords are not validated if `usable_password` is unset. + self.assertIs(form.is_valid(), True, form.errors) + + class CustomUserCreationForm(self.form_class): + class Meta(self.form_class.Meta): + model = User + fields = ("username", "email", "first_name", "last_name") + + form = CustomUserCreationForm( + { + "username": "testuser", + "password1": "testpassword", + "password2": "testpassword", + "first_name": "testpassword", + "last_name": "lastname", + "usable_password": "false", + } + ) + self.assertIs(form.is_valid(), True, form.errors) + + def test_unusable_password(self): + data = { + "username": "new-user-which-does-not-exist", + "usable_password": "false", + } + form = self.form_class(data) + self.assertIs(form.is_valid(), True, form.errors) + u = form.save() + self.assertEqual(u.username, data["username"]) + self.assertFalse(u.has_usable_password()) diff --git a/tests/auth_tests/test_hashers.py b/tests/auth_tests/test_hashers.py index 1b41c75e69..77242bca4f 100644 --- a/tests/auth_tests/test_hashers.py +++ b/tests/auth_tests/test_hashers.py @@ -1,3 +1,4 @@ +from contextlib import contextmanager from unittest import mock, skipUnless from django.conf.global_settings import PASSWORD_HASHERS @@ -452,8 +453,33 @@ class TestUtilsHashPass(SimpleTestCase): check_password("wrong_password", encoded) self.assertEqual(hasher.harden_runtime.call_count, 1) - def test_check_password_calls_make_password_to_fake_runtime(self): + @contextmanager + def assertMakePasswordCalled(self, password, encoded, hasher_side_effect): hasher = get_hasher("default") + with ( + mock.patch( + "django.contrib.auth.hashers.identify_hasher", + side_effect=hasher_side_effect, + ) as mock_identify_hasher, + mock.patch( + "django.contrib.auth.hashers.make_password" + ) as mock_make_password, + mock.patch( + "django.contrib.auth.hashers.get_random_string", + side_effect=lambda size: "x" * size, + ), + mock.patch.object(hasher, "verify"), + ): + # Ensure make_password is called to standardize timing. + yield + self.assertEqual(hasher.verify.call_count, 0) + self.assertEqual(mock_identify_hasher.mock_calls, [mock.call(encoded)]) + self.assertEqual( + mock_make_password.mock_calls, + [mock.call("x" * UNUSABLE_PASSWORD_SUFFIX_LENGTH)], + ) + + def test_check_password_calls_make_password_to_fake_runtime(self): cases = [ (None, None, None), # no plain text password provided ("foo", make_password(password=None), None), # unusable encoded @@ -462,27 +488,22 @@ class TestUtilsHashPass(SimpleTestCase): for password, encoded, hasher_side_effect in cases: with ( self.subTest(encoded=encoded), - mock.patch( - "django.contrib.auth.hashers.identify_hasher", - side_effect=hasher_side_effect, - ) as mock_identify_hasher, - mock.patch( - "django.contrib.auth.hashers.make_password" - ) as mock_make_password, - mock.patch( - "django.contrib.auth.hashers.get_random_string", - side_effect=lambda size: "x" * size, - ), - mock.patch.object(hasher, "verify"), + self.assertMakePasswordCalled(password, encoded, hasher_side_effect), ): - # Ensure make_password is called to standardize timing. check_password(password, encoded) - self.assertEqual(hasher.verify.call_count, 0) - self.assertEqual(mock_identify_hasher.mock_calls, [mock.call(encoded)]) - self.assertEqual( - mock_make_password.mock_calls, - [mock.call("x" * UNUSABLE_PASSWORD_SUFFIX_LENGTH)], - ) + + async def test_acheck_password_calls_make_password_to_fake_runtime(self): + cases = [ + (None, None, None), # no plain text password provided + ("foo", make_password(password=None), None), # unusable encoded + ("letmein", make_password(password="letmein"), ValueError), # valid encoded + ] + for password, encoded, hasher_side_effect in cases: + with ( + self.subTest(encoded=encoded), + self.assertMakePasswordCalled(password, encoded, hasher_side_effect), + ): + await acheck_password(password, encoded) def test_encode_invalid_salt(self): hasher_classes = [ diff --git a/tests/auth_tests/test_models.py b/tests/auth_tests/test_models.py index 34f411f2f9..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") @@ -433,6 +458,13 @@ class UserWithPermTestCase(TestCase): backend="invalid.backend.CustomModelBackend", ) + def test_invalid_backend_submodule(self): + with self.assertRaises(ImportError): + User.objects.with_perm( + "auth.test", + backend="json.tool", + ) + @override_settings( AUTHENTICATION_BACKENDS=["auth_tests.test_models.CustomModelBackend"] ) @@ -550,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_templates.py b/tests/auth_tests/test_templates.py index ceecfa3325..edde6ca6b4 100644 --- a/tests/auth_tests/test_templates.py +++ b/tests/auth_tests/test_templates.py @@ -37,6 +37,12 @@ class AuthTemplateTests(TestCase): ) self.assertContains(response, "<h1>Password reset</h1>") + def test_password_reset_view_error_title(self): + response = self.client.post(reverse("password_reset"), {}) + self.assertContains( + response, "<title>Error: Password reset | Django site admin" + ) + def test_password_reset_done_view(self): response = PasswordResetDoneView.as_view()(self.request) self.assertContains( @@ -77,6 +83,19 @@ class AuthTemplateTests(TestCase): '', ) + def test_password_reset_confirm_view_error_title(self): + client = PasswordResetConfirmClient() + default_token_generator = PasswordResetTokenGenerator() + token = default_token_generator.make_token(self.user) + uidb64 = urlsafe_base64_encode(str(self.user.pk).encode()) + url = reverse( + "password_reset_confirm", kwargs={"uidb64": uidb64, "token": token} + ) + response = client.post(url, {}) + self.assertContains( + response, "Error: Enter new password | Django site admin" + ) + @override_settings(AUTH_USER_MODEL="auth_tests.CustomUser") def test_password_reset_confirm_view_custom_username_hint(self): custom_user = CustomUser.custom_objects.create_user( diff --git a/tests/auth_tests/test_validators.py b/tests/auth_tests/test_validators.py index 4da031a793..506c85c0ae 100644 --- a/tests/auth_tests/test_validators.py +++ b/tests/auth_tests/test_validators.py @@ -14,7 +14,7 @@ from django.contrib.auth.password_validation import ( password_validators_help_texts, validate_password, ) -from django.core.exceptions import ValidationError +from django.core.exceptions import ImproperlyConfigured, ValidationError from django.db import models from django.test import SimpleTestCase, TestCase, override_settings from django.test.utils import isolate_apps @@ -50,6 +50,15 @@ class PasswordValidationTest(SimpleTestCase): self.assertEqual(get_password_validators([]), []) + def test_get_password_validators_custom_invalid(self): + validator_config = [{"NAME": "json.tool"}] + msg = ( + "The module in NAME could not be imported: json.tool. " + "Check your AUTH_PASSWORD_VALIDATORS setting." + ) + with self.assertRaisesMessage(ImproperlyConfigured, msg): + get_password_validators(validator_config) + def test_validate_password(self): self.assertIsNone(validate_password("sufficiently-long")) msg_too_short = ( diff --git a/tests/backends/base/test_operations.py b/tests/backends/base/test_operations.py index 8df02ee76b..18433352ad 100644 --- a/tests/backends/base/test_operations.py +++ b/tests/backends/base/test_operations.py @@ -239,8 +239,9 @@ class DeprecationTests(TestCase): "DatabaseOperations.field_cast_sql() is deprecated use " "DatabaseOperations.lookup_cast() instead." ) - with self.assertRaisesMessage(RemovedInDjango60Warning, msg): + with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx: base_ops.field_cast_sql("integer", "IntegerField") + self.assertEqual(ctx.filename, __file__) def test_field_cast_sql_usage_warning(self): compiler = Author.objects.all().query.get_compiler(connection.alias) diff --git a/tests/backends/postgresql/tests.py b/tests/backends/postgresql/tests.py index 0b4f580612..37c5ee562b 100644 --- a/tests/backends/postgresql/tests.py +++ b/tests/backends/postgresql/tests.py @@ -567,3 +567,49 @@ class Tests(TestCase): ) finally: new_connection.close() + + def test_bypass_timezone_configuration(self): + from django.db.backends.postgresql.base import DatabaseWrapper + + class CustomDatabaseWrapper(DatabaseWrapper): + def _configure_timezone(self, connection): + return False + + for Wrapper, commit in [ + (DatabaseWrapper, True), + (CustomDatabaseWrapper, False), + ]: + with self.subTest(wrapper=Wrapper, commit=commit): + new_connection = no_pool_connection() + self.addCleanup(new_connection.close) + + # Set the database default time zone to be different from + # the time zone in new_connection.settings_dict. + with new_connection.cursor() as cursor: + cursor.execute("RESET TIMEZONE") + cursor.execute("SHOW TIMEZONE") + db_default_tz = cursor.fetchone()[0] + new_tz = "Europe/Paris" if db_default_tz == "UTC" else "UTC" + new_connection.timezone_name = new_tz + + settings = new_connection.settings_dict.copy() + conn = new_connection.connection + self.assertIs(Wrapper(settings)._configure_connection(conn), commit) + + def test_bypass_role_configuration(self): + from django.db.backends.postgresql.base import DatabaseWrapper + + class CustomDatabaseWrapper(DatabaseWrapper): + def _configure_role(self, connection): + return False + + new_connection = no_pool_connection() + self.addCleanup(new_connection.close) + new_connection.connect() + + settings = new_connection.settings_dict.copy() + settings["OPTIONS"]["assume_role"] = "django_nonexistent_role" + conn = new_connection.connection + self.assertIs( + CustomDatabaseWrapper(settings)._configure_connection(conn), False + ) diff --git a/tests/backends/tests.py b/tests/backends/tests.py index 08a21d8ded..2adfa51360 100644 --- a/tests/backends/tests.py +++ b/tests/backends/tests.py @@ -225,6 +225,7 @@ class LongNameTest(TransactionTestCase): connection.ops.execute_sql_flush(sql_list) +@skipUnlessDBFeature("supports_sequence_reset") class SequenceResetTest(TestCase): def test_generic_relation(self): "Sequence names are correct when resetting generic relations (Ref #13941)" @@ -557,8 +558,9 @@ class BackendTestCase(TransactionTestCase): "Limit for query logging exceeded, only the last 3 queries will be " "returned." ) - with self.assertWarnsMessage(UserWarning, msg): + with self.assertWarnsMessage(UserWarning, msg) as ctx: self.assertEqual(3, len(new_connection.queries)) + self.assertEqual(ctx.filename, __file__) finally: BaseDatabaseWrapper.queries_limit = old_queries_limit diff --git a/tests/basic/tests.py b/tests/basic/tests.py index 6fb67f7e6e..cb267be0b1 100644 --- a/tests/basic/tests.py +++ b/tests/basic/tests.py @@ -206,9 +206,10 @@ class ModelInstanceCreationTests(TestCase): def test_save_deprecation(self): a = Article(headline="original", pub_date=datetime(2014, 5, 16)) msg = "Passing positional arguments to save() is deprecated" - with self.assertWarnsMessage(RemovedInDjango60Warning, msg): + with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx: a.save(False, False, None, None) self.assertEqual(Article.objects.count(), 1) + self.assertEqual(ctx.filename, __file__) def test_save_deprecation_positional_arguments_used(self): a = Article() @@ -259,9 +260,10 @@ class ModelInstanceCreationTests(TestCase): async def test_asave_deprecation(self): a = Article(headline="original", pub_date=datetime(2014, 5, 16)) msg = "Passing positional arguments to asave() is deprecated" - with self.assertWarnsMessage(RemovedInDjango60Warning, msg): + with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx: await a.asave(False, False, None, None) self.assertEqual(await Article.objects.acount(), 1) + self.assertEqual(ctx.filename, __file__) async def test_asave_deprecation_positional_arguments_used(self): a = Article() @@ -659,6 +661,31 @@ class ModelTest(TestCase): headline__startswith="Area", ) + def test_is_pk_unset(self): + cases = [ + Article(), + Article(id=None), + ] + for case in cases: + with self.subTest(case=case): + self.assertIs(case._is_pk_set(), False) + + def test_is_pk_set(self): + def new_instance(): + a = Article(pub_date=datetime.today()) + a.save() + return a + + cases = [ + Article(id=1), + Article(id=0), + Article.objects.create(pub_date=datetime.today()), + new_instance(), + ] + for case in cases: + with self.subTest(case=case): + self.assertIs(case._is_pk_set(), True) + class ModelLookupTest(TestCase): @classmethod diff --git a/tests/cache/tests.py b/tests/cache/tests.py index 978efdd9d3..2636a7d6ce 100644 --- a/tests/cache/tests.py +++ b/tests/cache/tests.py @@ -11,6 +11,7 @@ import tempfile import threading import time import unittest +from functools import wraps from pathlib import Path from unittest import mock, skipIf @@ -89,6 +90,25 @@ KEY_ERRORS_WITH_MEMCACHED_MSG = ( ) +def retry(retries=3, delay=1): + def decorator(func): + @wraps(func) + def wrapper(*args, **kwargs): + attempts = 0 + while attempts < retries: + try: + return func(*args, **kwargs) + except AssertionError: + attempts += 1 + if attempts >= retries: + raise + time.sleep(delay) + + return wrapper + + return decorator + + @override_settings( CACHES={ "default": { @@ -489,6 +509,7 @@ class BaseCacheTests: self.assertEqual(cache.get("expire2"), "newvalue") self.assertIs(cache.has_key("expire3"), False) + @retry() def test_touch(self): # cache.touch() updates the timeout. cache.set("expire1", "very quickly", timeout=1) @@ -616,6 +637,7 @@ class BaseCacheTests: self.assertEqual(cache.get("key3"), "sausage") self.assertEqual(cache.get("key4"), "lobster bisque") + @retry() def test_forever_timeout(self): """ Passing in None into timeout results in a value that is cached forever @@ -1397,6 +1419,7 @@ class LocMemCacheTests(BaseCacheTests, TestCase): self.assertEqual(cache.decr(key), 1) self.assertEqual(expire, cache._expire_info[_key]) + @retry() @limit_locmem_entries def test_lru_get(self): """get() moves cache keys.""" @@ -1424,6 +1447,7 @@ class LocMemCacheTests(BaseCacheTests, TestCase): for key in range(3): self.assertIsNone(cache.get(key)) + @retry() @limit_locmem_entries def test_lru_incr(self): """incr() moves cache keys.""" @@ -2674,6 +2698,7 @@ class CacheMiddlewareTest(SimpleTestCase): response = other_with_prefix_view(request, "16") self.assertEqual(response.content, b"Hello World 16") + @retry() def test_cache_page_timeout(self): # Page timeout takes precedence over the "max-age" section of the # "Cache-Control". diff --git a/tests/constraints/models.py b/tests/constraints/models.py index 87b97b2a85..829f671cdd 100644 --- a/tests/constraints/models.py +++ b/tests/constraints/models.py @@ -1,4 +1,5 @@ from django.db import models +from django.db.models.functions import Coalesce, Lower class Product(models.Model): @@ -28,6 +29,46 @@ class Product(models.Model): ] +class GeneratedFieldStoredProduct(models.Model): + name = models.CharField(max_length=255, null=True) + price = models.IntegerField(null=True) + discounted_price = models.IntegerField(null=True) + rebate = models.GeneratedField( + expression=Coalesce("price", 0) + - Coalesce("discounted_price", Coalesce("price", 0)), + output_field=models.IntegerField(), + db_persist=True, + ) + lower_name = models.GeneratedField( + expression=Lower(models.F("name")), + output_field=models.CharField(max_length=255, null=True), + db_persist=True, + ) + + class Meta: + required_db_features = {"supports_stored_generated_columns"} + + +class GeneratedFieldVirtualProduct(models.Model): + name = models.CharField(max_length=255, null=True) + price = models.IntegerField(null=True) + discounted_price = models.IntegerField(null=True) + rebate = models.GeneratedField( + expression=Coalesce("price", 0) + - Coalesce("discounted_price", Coalesce("price", 0)), + output_field=models.IntegerField(), + db_persist=False, + ) + lower_name = models.GeneratedField( + expression=Lower(models.F("name")), + output_field=models.CharField(max_length=255, null=True), + db_persist=False, + ) + + class Meta: + required_db_features = {"supports_virtual_generated_columns"} + + class UniqueConstraintProduct(models.Model): name = models.CharField(max_length=255) color = models.CharField(max_length=32, null=True) @@ -128,3 +169,10 @@ class JSONFieldModel(models.Model): class Meta: required_db_features = {"supports_json_field"} + + +class ModelWithDatabaseDefault(models.Model): + field = models.CharField(max_length=255) + field_with_db_default = models.CharField( + max_length=255, db_default=models.Value("field_with_db_default") + ) diff --git a/tests/constraints/tests.py b/tests/constraints/tests.py index 31c5d64652..e1c431956f 100644 --- a/tests/constraints/tests.py +++ b/tests/constraints/tests.py @@ -4,7 +4,7 @@ from django.core.exceptions import ValidationError from django.db import IntegrityError, connection, models from django.db.models import F from django.db.models.constraints import BaseConstraint, UniqueConstraint -from django.db.models.functions import Abs, Lower +from django.db.models.functions import Abs, Lower, Sqrt, Upper from django.db.transaction import atomic from django.test import SimpleTestCase, TestCase, skipIfDBFeature, skipUnlessDBFeature from django.test.utils import ignore_warnings @@ -13,7 +13,10 @@ from django.utils.deprecation import RemovedInDjango60Warning from .models import ( ChildModel, ChildUniqueConstraintProduct, + GeneratedFieldStoredProduct, + GeneratedFieldVirtualProduct, JSONFieldModel, + ModelWithDatabaseDefault, Product, UniqueConstraintConditionProduct, UniqueConstraintDeferrable, @@ -383,18 +386,72 @@ class CheckConstraintTests(TestCase): with self.assertRaisesMessage(ValidationError, msg): json_exact_constraint.validate(JSONFieldModel, JSONFieldModel(data=data)) + @skipUnlessDBFeature("supports_stored_generated_columns") + def test_validate_generated_field_stored(self): + self.assertGeneratedFieldIsValidated(model=GeneratedFieldStoredProduct) + + @skipUnlessDBFeature("supports_virtual_generated_columns") + def test_validate_generated_field_virtual(self): + self.assertGeneratedFieldIsValidated(model=GeneratedFieldVirtualProduct) + + def assertGeneratedFieldIsValidated(self, model): + constraint = models.CheckConstraint( + condition=models.Q(rebate__range=(0, 100)), name="bounded_rebate" + ) + constraint.validate(model, model(price=50, discounted_price=20)) + + invalid_product = model(price=1200, discounted_price=500) + msg = f"Constraint “{constraint.name}” is violated." + with self.assertRaisesMessage(ValidationError, msg): + constraint.validate(model, invalid_product) + + # Excluding referenced or generated fields should skip validation. + constraint.validate(model, invalid_product, exclude={"price"}) + constraint.validate(model, invalid_product, exclude={"rebate"}) + def test_check_deprecation(self): msg = "CheckConstraint.check is deprecated in favor of `.condition`." condition = models.Q(foo="bar") - with self.assertWarnsRegex(RemovedInDjango60Warning, msg): + with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx: constraint = models.CheckConstraint(name="constraint", check=condition) - with self.assertWarnsRegex(RemovedInDjango60Warning, msg): + self.assertEqual(ctx.filename, __file__) + with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx: self.assertIs(constraint.check, condition) + self.assertEqual(ctx.filename, __file__) other_condition = models.Q(something="else") - with self.assertWarnsRegex(RemovedInDjango60Warning, msg): + with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx: constraint.check = other_condition - with self.assertWarnsRegex(RemovedInDjango60Warning, msg): + self.assertEqual(ctx.filename, __file__) + with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx: self.assertIs(constraint.check, other_condition) + self.assertEqual(ctx.filename, __file__) + + def test_database_default(self): + models.CheckConstraint( + condition=models.Q(field_with_db_default="field_with_db_default"), + name="check_field_with_db_default", + ).validate(ModelWithDatabaseDefault, ModelWithDatabaseDefault()) + + # Ensure that a check also does not silently pass with either + # FieldError or DatabaseError when checking with a db_default. + with self.assertRaises(ValidationError): + models.CheckConstraint( + condition=models.Q( + field_with_db_default="field_with_db_default", field="field" + ), + name="check_field_with_db_default_2", + ).validate( + ModelWithDatabaseDefault, ModelWithDatabaseDefault(field="not-field") + ) + + with self.assertRaises(ValidationError): + models.CheckConstraint( + condition=models.Q(field_with_db_default="field_with_db_default"), + name="check_field_with_db_default", + ).validate( + ModelWithDatabaseDefault, + ModelWithDatabaseDefault(field_with_db_default="other value"), + ) class UniqueConstraintTests(TestCase): @@ -896,6 +953,7 @@ class UniqueConstraintTests(TestCase): ChildUniqueConstraintProduct(name=self.p1.name, color=self.p1.color), ) + @skipUnlessDBFeature("supports_table_check_constraints") def test_validate_fields_unattached(self): Product.objects.create(price=42) constraint = models.UniqueConstraint(fields=["price"], name="uniq_prices") @@ -1033,6 +1091,90 @@ class UniqueConstraintTests(TestCase): exclude={"name"}, ) + @skipUnlessDBFeature("supports_stored_generated_columns") + def test_validate_expression_generated_field_stored(self): + self.assertGeneratedFieldWithExpressionIsValidated( + model=GeneratedFieldStoredProduct + ) + + @skipUnlessDBFeature("supports_virtual_generated_columns") + def test_validate_expression_generated_field_virtual(self): + self.assertGeneratedFieldWithExpressionIsValidated( + model=GeneratedFieldVirtualProduct + ) + + def assertGeneratedFieldWithExpressionIsValidated(self, model): + constraint = UniqueConstraint(Sqrt("rebate"), name="unique_rebate_sqrt") + model.objects.create(price=100, discounted_price=84) + + valid_product = model(price=100, discounted_price=75) + constraint.validate(model, valid_product) + + invalid_product = model(price=20, discounted_price=4) + with self.assertRaisesMessage( + ValidationError, f"Constraint “{constraint.name}” is violated." + ): + constraint.validate(model, invalid_product) + + # Excluding referenced or generated fields should skip validation. + constraint.validate(model, invalid_product, exclude={"rebate"}) + constraint.validate(model, invalid_product, exclude={"price"}) + + @skipUnlessDBFeature("supports_stored_generated_columns") + def test_validate_fields_generated_field_stored(self): + self.assertGeneratedFieldWithFieldsIsValidated( + model=GeneratedFieldStoredProduct + ) + + @skipUnlessDBFeature("supports_virtual_generated_columns") + def test_validate_fields_generated_field_virtual(self): + self.assertGeneratedFieldWithFieldsIsValidated( + model=GeneratedFieldVirtualProduct + ) + + def assertGeneratedFieldWithFieldsIsValidated(self, model): + constraint = models.UniqueConstraint( + fields=["lower_name"], name="lower_name_unique" + ) + model.objects.create(name="Box") + constraint.validate(model, model(name="Case")) + + invalid_product = model(name="BOX") + msg = str(invalid_product.unique_error_message(model, ["lower_name"])) + with self.assertRaisesMessage(ValidationError, msg): + constraint.validate(model, invalid_product) + + # Excluding referenced or generated fields should skip validation. + constraint.validate(model, invalid_product, exclude={"lower_name"}) + constraint.validate(model, invalid_product, exclude={"name"}) + + @skipUnlessDBFeature("supports_stored_generated_columns") + def test_validate_fields_generated_field_stored_nulls_distinct(self): + self.assertGeneratedFieldNullsDistinctIsValidated( + model=GeneratedFieldStoredProduct + ) + + @skipUnlessDBFeature("supports_virtual_generated_columns") + def test_validate_fields_generated_field_virtual_nulls_distinct(self): + self.assertGeneratedFieldNullsDistinctIsValidated( + model=GeneratedFieldVirtualProduct + ) + + def assertGeneratedFieldNullsDistinctIsValidated(self, model): + constraint = models.UniqueConstraint( + fields=["lower_name"], + name="lower_name_unique_nulls_distinct", + nulls_distinct=False, + ) + model.objects.create(name=None) + valid_product = model(name="Box") + constraint.validate(model, valid_product) + + invalid_product = model(name=None) + msg = str(invalid_product.unique_error_message(model, ["lower_name"])) + with self.assertRaisesMessage(ValidationError, msg): + constraint.validate(model, invalid_product) + @skipUnlessDBFeature("supports_table_check_constraints") def test_validate_nullable_textfield_with_isnull_true(self): is_null_constraint = models.UniqueConstraint( @@ -1070,6 +1212,7 @@ class UniqueConstraintTests(TestCase): is_not_null_constraint.validate(Product, Product(price=4, discounted_price=3)) is_not_null_constraint.validate(Product, Product(price=2, discounted_price=1)) + @skipUnlessDBFeature("supports_table_check_constraints") def test_validate_nulls_distinct_fields(self): Product.objects.create(price=42) constraint = models.UniqueConstraint( @@ -1083,6 +1226,7 @@ class UniqueConstraintTests(TestCase): with self.assertRaisesMessage(ValidationError, msg): constraint.validate(Product, Product(price=None)) + @skipUnlessDBFeature("supports_table_check_constraints") def test_validate_nulls_distinct_expressions(self): Product.objects.create(price=42) constraint = models.UniqueConstraint( @@ -1262,3 +1406,30 @@ class UniqueConstraintTests(TestCase): msg = "A unique constraint must be named." with self.assertRaisesMessage(ValueError, msg): models.UniqueConstraint(fields=["field"]) + + def test_database_default(self): + models.UniqueConstraint( + fields=["field_with_db_default"], name="unique_field_with_db_default" + ).validate(ModelWithDatabaseDefault, ModelWithDatabaseDefault()) + models.UniqueConstraint( + Upper("field_with_db_default"), + name="unique_field_with_db_default_expression", + ).validate(ModelWithDatabaseDefault, ModelWithDatabaseDefault()) + + ModelWithDatabaseDefault.objects.create() + + msg = ( + "Model with database default with this Field with db default already " + "exists." + ) + with self.assertRaisesMessage(ValidationError, msg): + models.UniqueConstraint( + fields=["field_with_db_default"], name="unique_field_with_db_default" + ).validate(ModelWithDatabaseDefault, ModelWithDatabaseDefault()) + + msg = "Constraint “unique_field_with_db_default_expression” is violated." + with self.assertRaisesMessage(ValidationError, msg): + models.UniqueConstraint( + Upper("field_with_db_default"), + name="unique_field_with_db_default_expression", + ).validate(ModelWithDatabaseDefault, ModelWithDatabaseDefault()) diff --git a/tests/contenttypes_tests/test_fields.py b/tests/contenttypes_tests/test_fields.py index 15f1dafd63..ab16324fb6 100644 --- a/tests/contenttypes_tests/test_fields.py +++ b/tests/contenttypes_tests/test_fields.py @@ -98,8 +98,9 @@ class GetPrefetchQuerySetDeprecation(TestCase): "get_prefetch_queryset() is deprecated. Use get_prefetch_querysets() " "instead." ) - with self.assertWarnsMessage(RemovedInDjango60Warning, msg): + with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx: questions[0].answer_set.get_prefetch_queryset(questions) + self.assertEqual(ctx.filename, __file__) def test_generic_foreign_key_warning(self): answers = Answer.objects.all() @@ -107,8 +108,9 @@ class GetPrefetchQuerySetDeprecation(TestCase): "get_prefetch_queryset() is deprecated. Use get_prefetch_querysets() " "instead." ) - with self.assertWarnsMessage(RemovedInDjango60Warning, msg): + with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx: Answer.question.get_prefetch_queryset(answers) + self.assertEqual(ctx.filename, __file__) class GetPrefetchQuerySetsTests(TestCase): diff --git a/tests/decorators/tests.py b/tests/decorators/tests.py index 58f822f2a5..1f8d623e02 100644 --- a/tests/decorators/tests.py +++ b/tests/decorators/tests.py @@ -1,6 +1,9 @@ +import asyncio from functools import update_wrapper, wraps from unittest import TestCase +from asgiref.sync import iscoroutinefunction + from django.contrib.admin.views.decorators import staff_member_required from django.contrib.auth.decorators import ( login_required, @@ -434,3 +437,262 @@ class MethodDecoratorTests(SimpleTestCase): Test().method() self.assertEqual(func_name, "method") self.assertIsNotNone(func_module) + + +def async_simple_dec(func): + @wraps(func) + async def wrapper(*args, **kwargs): + result = await func(*args, **kwargs) + return f"returned: {result}" + + return wrapper + + +async_simple_dec_m = method_decorator(async_simple_dec) + + +class AsyncMethodDecoratorTests(SimpleTestCase): + """ + Tests for async method_decorator + """ + + async def test_preserve_signature(self): + class Test: + @async_simple_dec_m + async def say(self, msg): + return f"Saying {msg}" + + self.assertEqual(await Test().say("hello"), "returned: Saying hello") + + def test_preserve_attributes(self): + async def func(*args, **kwargs): + await asyncio.sleep(0.01) + return args, kwargs + + def myattr_dec(func): + async def wrapper(*args, **kwargs): + return await func(*args, **kwargs) + + wrapper.myattr = True + return wrapper + + def myattr2_dec(func): + async def wrapper(*args, **kwargs): + return await func(*args, **kwargs) + + wrapper.myattr2 = True + return wrapper + + # Sanity check myattr_dec and myattr2_dec + func = myattr_dec(func) + + self.assertIs(getattr(func, "myattr", False), True) + + func = myattr2_dec(func) + self.assertIs(getattr(func, "myattr2", False), True) + + func = myattr_dec(myattr2_dec(func)) + self.assertIs(getattr(func, "myattr", False), True) + self.assertIs(getattr(func, "myattr2", False), False) + + myattr_dec_m = method_decorator(myattr_dec) + myattr2_dec_m = method_decorator(myattr2_dec) + + # Decorate using method_decorator() on the async method. + class TestPlain: + @myattr_dec_m + @myattr2_dec_m + async def method(self): + "A method" + + # Decorate using method_decorator() on both the class and the method. + # The decorators applied to the methods are applied before the ones + # applied to the class. + @method_decorator(myattr_dec_m, "method") + class TestMethodAndClass: + @method_decorator(myattr2_dec_m) + async def method(self): + "A method" + + # Decorate using an iterable of function decorators. + @method_decorator((myattr_dec, myattr2_dec), "method") + class TestFunctionIterable: + async def method(self): + "A method" + + # Decorate using an iterable of method decorators. + @method_decorator((myattr_dec_m, myattr2_dec_m), "method") + class TestMethodIterable: + async def method(self): + "A method" + + tests = ( + TestPlain, + TestMethodAndClass, + TestFunctionIterable, + TestMethodIterable, + ) + for Test in tests: + with self.subTest(Test=Test): + self.assertIs(getattr(Test().method, "myattr", False), True) + self.assertIs(getattr(Test().method, "myattr2", False), True) + self.assertIs(getattr(Test.method, "myattr", False), True) + self.assertIs(getattr(Test.method, "myattr2", False), True) + self.assertEqual(Test.method.__doc__, "A method") + self.assertEqual(Test.method.__name__, "method") + + async def test_new_attribute(self): + """A decorator that sets a new attribute on the method.""" + + def decorate(func): + func.x = 1 + return func + + class MyClass: + @method_decorator(decorate) + async def method(self): + return True + + obj = MyClass() + self.assertEqual(obj.method.x, 1) + self.assertIs(await obj.method(), True) + + def test_bad_iterable(self): + decorators = {async_simple_dec} + msg = "'set' object is not subscriptable" + with self.assertRaisesMessage(TypeError, msg): + + @method_decorator(decorators, "method") + class TestIterable: + async def method(self): + await asyncio.sleep(0.01) + + async def test_argumented(self): + + class ClsDecAsync: + def __init__(self, myattr): + self.myattr = myattr + + def __call__(self, f): + async def wrapper(): + result = await f() + return f"{result} appending {self.myattr}" + + return update_wrapper(wrapper, f) + + class Test: + @method_decorator(ClsDecAsync(False)) + async def method(self): + return True + + self.assertEqual(await Test().method(), "True appending False") + + async def test_descriptors(self): + class bound_wrapper: + def __init__(self, wrapped): + self.wrapped = wrapped + self.__name__ = wrapped.__name__ + + async def __call__(self, *args, **kwargs): + return await self.wrapped(*args, **kwargs) + + def __get__(self, instance, cls=None): + return self + + class descriptor_wrapper: + def __init__(self, wrapped): + self.wrapped = wrapped + self.__name__ = wrapped.__name__ + + def __get__(self, instance, cls=None): + return bound_wrapper(self.wrapped.__get__(instance, cls)) + + class Test: + @async_simple_dec_m + @descriptor_wrapper + async def method(self, arg): + return arg + + self.assertEqual(await Test().method(1), "returned: 1") + + async def test_class_decoration(self): + """ + @method_decorator can be used to decorate a class and its methods. + """ + + @method_decorator(async_simple_dec, name="method") + class Test: + async def method(self): + return False + + async def not_method(self): + return "a string" + + self.assertEqual(await Test().method(), "returned: False") + self.assertEqual(await Test().not_method(), "a string") + + async def test_tuple_of_decorators(self): + """ + @method_decorator can accept a tuple of decorators. + """ + + def add_question_mark(func): + async def _wrapper(*args, **kwargs): + await asyncio.sleep(0.01) + return await func(*args, **kwargs) + "?" + + return _wrapper + + def add_exclamation_mark(func): + async def _wrapper(*args, **kwargs): + await asyncio.sleep(0.01) + return await func(*args, **kwargs) + "!" + + return _wrapper + + decorators = (add_exclamation_mark, add_question_mark) + + @method_decorator(decorators, name="method") + class TestFirst: + async def method(self): + return "hello world" + + class TestSecond: + @method_decorator(decorators) + async def method(self): + return "world hello" + + self.assertEqual(await TestFirst().method(), "hello world?!") + self.assertEqual(await TestSecond().method(), "world hello?!") + + async def test_wrapper_assignments(self): + """@method_decorator preserves wrapper assignments.""" + func_data = {} + + def decorator(func): + @wraps(func) + async def inner(*args, **kwargs): + func_data["func_name"] = getattr(func, "__name__", None) + func_data["func_module"] = getattr(func, "__module__", None) + return await func(*args, **kwargs) + + return inner + + class Test: + @method_decorator(decorator) + async def method(self): + return "tests" + + await Test().method() + expected = {"func_name": "method", "func_module": "decorators.tests"} + self.assertEqual(func_data, expected) + + async def test_markcoroutinefunction_applied(self): + class Test: + @async_simple_dec_m + async def method(self): + return "tests" + + method = Test().method + self.assertIs(iscoroutinefunction(method), True) + self.assertEqual(await method(), "returned: tests") 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/deprecation/tests.py b/tests/deprecation/tests.py index 5548e90285..66f6a4d922 100644 --- a/tests/deprecation/tests.py +++ b/tests/deprecation/tests.py @@ -20,12 +20,14 @@ class RenameMethodsTests(SimpleTestCase): the faulty method. """ msg = "`Manager.old` method should be renamed `new`." - with self.assertWarnsMessage(DeprecationWarning, msg): + with self.assertWarnsMessage(DeprecationWarning, msg) as ctx: class Manager(metaclass=RenameManagerMethods): def old(self): pass + self.assertEqual(ctx.filename, __file__) + def test_get_new_defined(self): """ Ensure `old` complains and not `new` when only `new` is defined. @@ -43,20 +45,23 @@ class RenameMethodsTests(SimpleTestCase): self.assertEqual(len(recorded), 0) msg = "`Manager.old` is deprecated, use `new` instead." - with self.assertWarnsMessage(DeprecationWarning, msg): + with self.assertWarnsMessage(DeprecationWarning, msg) as ctx: manager.old() + self.assertEqual(ctx.filename, __file__) def test_get_old_defined(self): """ Ensure `old` complains when only `old` is defined. """ msg = "`Manager.old` method should be renamed `new`." - with self.assertWarnsMessage(DeprecationWarning, msg): + with self.assertWarnsMessage(DeprecationWarning, msg) as ctx: class Manager(metaclass=RenameManagerMethods): def old(self): pass + self.assertEqual(ctx.filename, __file__) + manager = Manager() with warnings.catch_warnings(record=True) as recorded: @@ -65,8 +70,9 @@ class RenameMethodsTests(SimpleTestCase): self.assertEqual(len(recorded), 0) msg = "`Manager.old` is deprecated, use `new` instead." - with self.assertWarnsMessage(DeprecationWarning, msg): + with self.assertWarnsMessage(DeprecationWarning, msg) as ctx: manager.old() + self.assertEqual(ctx.filename, __file__) def test_deprecated_subclass_renamed(self): """ @@ -79,21 +85,25 @@ class RenameMethodsTests(SimpleTestCase): pass msg = "`Deprecated.old` method should be renamed `new`." - with self.assertWarnsMessage(DeprecationWarning, msg): + with self.assertWarnsMessage(DeprecationWarning, msg) as ctx: class Deprecated(Renamed): def old(self): super().old() + self.assertEqual(ctx.filename, __file__) + deprecated = Deprecated() msg = "`Renamed.old` is deprecated, use `new` instead." - with self.assertWarnsMessage(DeprecationWarning, msg): + with self.assertWarnsMessage(DeprecationWarning, msg) as ctx: deprecated.new() + self.assertEqual(ctx.filename, __file__) msg = "`Deprecated.old` is deprecated, use `new` instead." - with self.assertWarnsMessage(DeprecationWarning, msg): + with self.assertWarnsMessage(DeprecationWarning, msg) as ctx: deprecated.old() + self.assertEqual(ctx.filename, __file__) def test_renamed_subclass_deprecated(self): """ @@ -101,12 +111,14 @@ class RenameMethodsTests(SimpleTestCase): `old` subclass one that didn't. """ msg = "`Deprecated.old` method should be renamed `new`." - with self.assertWarnsMessage(DeprecationWarning, msg): + with self.assertWarnsMessage(DeprecationWarning, msg) as ctx: class Deprecated(metaclass=RenameManagerMethods): def old(self): pass + self.assertEqual(ctx.filename, __file__) + class Renamed(Deprecated): def new(self): super().new() @@ -119,8 +131,9 @@ class RenameMethodsTests(SimpleTestCase): self.assertEqual(len(recorded), 0) msg = "`Renamed.old` is deprecated, use `new` instead." - with self.assertWarnsMessage(DeprecationWarning, msg): + with self.assertWarnsMessage(DeprecationWarning, msg) as ctx: renamed.old() + self.assertEqual(ctx.filename, __file__) def test_deprecated_subclass_renamed_and_mixins(self): """ @@ -142,20 +155,24 @@ class RenameMethodsTests(SimpleTestCase): super().old() msg = "`DeprecatedMixin.old` method should be renamed `new`." - with self.assertWarnsMessage(DeprecationWarning, msg): + with self.assertWarnsMessage(DeprecationWarning, msg) as ctx: class Deprecated(DeprecatedMixin, RenamedMixin, Renamed): pass + self.assertEqual(ctx.filename, __file__) + deprecated = Deprecated() msg = "`RenamedMixin.old` is deprecated, use `new` instead." - with self.assertWarnsMessage(DeprecationWarning, msg): + with self.assertWarnsMessage(DeprecationWarning, msg) as ctx: deprecated.new() + self.assertEqual(ctx.filename, __file__) msg = "`DeprecatedMixin.old` is deprecated, use `new` instead." - with self.assertWarnsMessage(DeprecationWarning, msg): + with self.assertWarnsMessage(DeprecationWarning, msg) as ctx: deprecated.old() + self.assertEqual(ctx.filename, __file__) def test_removedafternextversionwarning_pending(self): self.assertTrue( diff --git a/tests/expressions/models.py b/tests/expressions/models.py index 31891a13d7..3909bdf0cd 100644 --- a/tests/expressions/models.py +++ b/tests/expressions/models.py @@ -115,3 +115,10 @@ class UUID(models.Model): class Text(models.Model): name = models.TextField() + + +class JSONFieldModel(models.Model): + data = models.JSONField(null=True) + + class Meta: + required_db_features = {"supports_json_field"} diff --git a/tests/expressions/test_queryset_values.py b/tests/expressions/test_queryset_values.py index 80addef37b..47bd1358de 100644 --- a/tests/expressions/test_queryset_values.py +++ b/tests/expressions/test_queryset_values.py @@ -1,7 +1,7 @@ from django.db.models import F, Sum -from django.test import TestCase +from django.test import TestCase, skipUnlessDBFeature -from .models import Company, Employee +from .models import Company, Employee, JSONFieldModel class ValuesExpressionsTests(TestCase): @@ -43,6 +43,19 @@ class ValuesExpressionsTests(TestCase): with self.assertRaisesMessage(ValueError, msg): Company.objects.values(**{crafted_alias: F("ceo__salary")}) + @skipUnlessDBFeature("supports_json_field") + def test_values_expression_alias_sql_injection_json_field(self): + crafted_alias = """injected_name" from "expressions_company"; --""" + msg = ( + "Column aliases cannot contain whitespace characters, quotation marks, " + "semicolons, or SQL comments." + ) + with self.assertRaisesMessage(ValueError, msg): + JSONFieldModel.objects.values(f"data__{crafted_alias}") + + with self.assertRaisesMessage(ValueError, msg): + JSONFieldModel.objects.values_list(f"data__{crafted_alias}") + def test_values_expression_group_by(self): # values() applies annotate() first, so values selected are grouped by # id, not firstname. diff --git a/tests/expressions/tests.py b/tests/expressions/tests.py index 64103f14db..af4cf01fca 100644 --- a/tests/expressions/tests.py +++ b/tests/expressions/tests.py @@ -47,6 +47,7 @@ from django.db.models import ( ) from django.db.models.expressions import ( Col, + ColPairs, Combinable, CombinedExpression, NegatedExpression, @@ -208,24 +209,28 @@ class BasicExpressionsTests(TestCase): def _test_slicing_of_f_expressions(self, model): tests = [ - (F("name")[:], "Example Inc.", "Example Inc."), - (F("name")[:7], "Example Inc.", "Example"), - (F("name")[:6][:5], "Example", "Examp"), # Nested slicing. - (F("name")[0], "Examp", "E"), - (F("name")[5], "E", ""), - (F("name")[7:], "Foobar Ltd.", "Ltd."), - (F("name")[0:10], "Ltd.", "Ltd."), - (F("name")[2:7], "Test GmbH", "st Gm"), - (F("name")[1:][:3], "st Gm", "t G"), - (F("name")[2:2], "t G", ""), + (F("name")[:], "Example Inc."), + (F("name")[:7], "Example"), + (F("name")[:6][:5], "Examp"), # Nested slicing. + (F("name")[0], "E"), + (F("name")[13], ""), + (F("name")[8:], "Inc."), + (F("name")[0:15], "Example Inc."), + (F("name")[2:7], "ample"), + (F("name")[1:][:3], "xam"), + (F("name")[2:2], ""), ] - for expression, name, expected in tests: - with self.subTest(expression=expression, name=name, expected=expected): - obj = model.objects.get(name=name) - obj.name = expression - obj.save() - obj.refresh_from_db() - self.assertEqual(obj.name, expected) + for expression, expected in tests: + with self.subTest(expression=expression, expected=expected): + obj = model.objects.get(name="Example Inc.") + try: + obj.name = expression + obj.save(update_fields=["name"]) + obj.refresh_from_db() + self.assertEqual(obj.name, expected) + finally: + obj.name = "Example Inc." + obj.save(update_fields=["name"]) def test_slicing_of_f_expressions_charfield(self): self._test_slicing_of_f_expressions(Company) @@ -2311,11 +2316,6 @@ class ValueTests(TestCase): self.assertNotEqual(value, other_value) self.assertNotEqual(value, no_output_field) - def test_raise_empty_expressionlist(self): - msg = "ExpressionList requires at least one expression" - with self.assertRaisesMessage(ValueError, msg): - ExpressionList() - def test_compile_unresolved(self): # This test might need to be revisited later on if #25425 is enforced. compiler = Time.objects.all().query.get_compiler(connection=connection) @@ -2467,6 +2467,10 @@ class ReprTests(SimpleTestCase): " THEN Value('legal')>", ) self.assertEqual(repr(Col("alias", "field")), "Col(alias, field)") + self.assertEqual( + repr(ColPairs("alias", ["t1", "t2"], ["s1", "s2"], "f")), + "ColPairs('alias', ['t1', 't2'], ['s1', 's2'], 'f')", + ) self.assertEqual(repr(F("published")), "F(published)") self.assertEqual( repr(F("cost") + F("tax")), "" diff --git a/tests/expressions_window/tests.py b/tests/expressions_window/tests.py index fd674e319b..fd9858ccf9 100644 --- a/tests/expressions_window/tests.py +++ b/tests/expressions_window/tests.py @@ -928,6 +928,20 @@ class WindowFunctionTests(TestCase): ), ) + def test_empty_ordering(self): + """ + Explicit empty ordering makes little sense but it is something that + was historically allowed. + """ + qs = Employee.objects.annotate( + sum=Window( + expression=Sum("salary"), + partition_by="department", + order_by=[], + ) + ).order_by("department", "sum") + self.assertEqual(len(qs), 12) + def test_related_ordering_with_count(self): qs = Employee.objects.annotate( department_sum=Window( diff --git a/tests/file_storage/models.py b/tests/file_storage/models.py index 873c3e176a..cb0207cae9 100644 --- a/tests/file_storage/models.py +++ b/tests/file_storage/models.py @@ -72,6 +72,9 @@ class Storage(models.Model): default = models.FileField( storage=temp_storage, upload_to="tests", default="tests/default.txt" ) + db_default = models.FileField( + storage=temp_storage, upload_to="tests", db_default="tests/db_default.txt" + ) empty = models.FileField(storage=temp_storage) limited_length = models.FileField( storage=temp_storage, upload_to="tests", max_length=20 diff --git a/tests/file_storage/test_generate_filename.py b/tests/file_storage/test_generate_filename.py index 9631705fc8..483115e09c 100644 --- a/tests/file_storage/test_generate_filename.py +++ b/tests/file_storage/test_generate_filename.py @@ -80,11 +80,14 @@ class GenerateFilenameStorageTests(SimpleTestCase): ("", ""), ] s = FileSystemStorage() + s_overwrite = FileSystemStorage(allow_overwrite=True) msg = "Could not derive file name from '%s'" for file_name, base_name in candidates: with self.subTest(file_name=file_name): with self.assertRaisesMessage(SuspiciousFileOperation, msg % base_name): s.get_available_name(file_name) + with self.assertRaisesMessage(SuspiciousFileOperation, msg % base_name): + s_overwrite.get_available_name(file_name) with self.assertRaisesMessage(SuspiciousFileOperation, msg % base_name): s.generate_filename(file_name) @@ -98,11 +101,14 @@ class GenerateFilenameStorageTests(SimpleTestCase): ("\\tmp\\..\\path", "/tmp/.."), ] s = FileSystemStorage() + s_overwrite = FileSystemStorage(allow_overwrite=True) for file_name, path in candidates: msg = "Detected path traversal attempt in '%s'" % path with self.subTest(file_name=file_name): with self.assertRaisesMessage(SuspiciousFileOperation, msg): s.get_available_name(file_name) + with self.assertRaisesMessage(SuspiciousFileOperation, msg): + s_overwrite.get_available_name(file_name) with self.assertRaisesMessage(SuspiciousFileOperation, msg): s.generate_filename(file_name) diff --git a/tests/file_storage/tests.py b/tests/file_storage/tests.py index 38d87dc7f2..5f0024b81a 100644 --- a/tests/file_storage/tests.py +++ b/tests/file_storage/tests.py @@ -95,18 +95,18 @@ class FileStorageTests(SimpleTestCase): """ Standard file access options are available, and work as expected. """ - self.assertFalse(os.path.exists(os.path.join(self.temp_dir, "storage_test"))) + self.assertFalse(self.storage.exists("storage_test")) f = self.storage.open("storage_test", "w") f.write("storage contents") f.close() - self.assertTrue(os.path.exists(os.path.join(self.temp_dir, "storage_test"))) + self.assertTrue(self.storage.exists("storage_test")) f = self.storage.open("storage_test", "r") self.assertEqual(f.read(), "storage contents") f.close() self.storage.delete("storage_test") - self.assertFalse(os.path.exists(os.path.join(self.temp_dir, "storage_test"))) + self.assertFalse(self.storage.exists("storage_test")) def _test_file_time_getter(self, getter): # Check for correct behavior under both USE_TZ=True and USE_TZ=False. @@ -275,10 +275,10 @@ class FileStorageTests(SimpleTestCase): """ Saving a pathname should create intermediate directories as necessary. """ - self.assertFalse(os.path.exists(os.path.join(self.temp_dir, "path/to"))) + self.assertFalse(self.storage.exists("path/to")) self.storage.save("path/to/test.file", ContentFile("file saved with path")) - self.assertTrue(os.path.exists(os.path.join(self.temp_dir, "path/to"))) + self.assertTrue(self.storage.exists("path/to")) with self.storage.open("path/to/test.file") as f: self.assertEqual(f.read(), b"file saved with path") @@ -635,10 +635,11 @@ class OverwritingStorageOSOpenFlagsWarningTests(SimpleTestCase): def test_os_open_flags_deprecation_warning(self): msg = "Overriding OS_OPEN_FLAGS is deprecated. Use the allow_overwrite " msg += "parameter instead." - with self.assertWarnsMessage(RemovedInDjango60Warning, msg): + with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx: self.storage = self.storage_class( location=self.temp_dir, base_url="/test_media_url/" ) + self.assertEqual(ctx.filename, __file__) # RemovedInDjango60Warning: Remove this test class. @@ -692,12 +693,12 @@ class OverwritingStorageTests(FileStorageTests): stored_name_1 = self.storage.save(name, f_1) try: self.assertEqual(stored_name_1, name) - self.assertTrue(os.path.exists(os.path.join(self.temp_dir, name))) + self.assertTrue(self.storage.exists(name)) with self.storage.open(name) as fp: self.assertEqual(fp.read(), content_1) stored_name_2 = self.storage.save(name, f_2) self.assertEqual(stored_name_2, name) - self.assertTrue(os.path.exists(os.path.join(self.temp_dir, name))) + self.assertTrue(self.storage.exists(name)) with self.storage.open(name) as fp: self.assertEqual(fp.read(), content_2) finally: @@ -729,6 +730,22 @@ class OverwritingStorageTests(FileStorageTests): finally: self.storage.delete(name) + def test_file_name_truncation(self): + name = "test_long_file_name.txt" + file = ContentFile(b"content") + stored_name = self.storage.save(name, file, max_length=10) + self.addCleanup(self.storage.delete, stored_name) + self.assertEqual(stored_name, "test_l.txt") + self.assertEqual(len(stored_name), 10) + + def test_file_name_truncation_extension_too_long(self): + name = "file_name.longext" + file = ContentFile(b"content") + with self.assertRaisesMessage( + SuspiciousFileOperation, "Storage can not find an available filename" + ): + self.storage.save(name, file, max_length=5) + class DiscardingFalseContentStorage(FileSystemStorage): def _save(self, name, content): @@ -755,7 +772,8 @@ class DiscardingFalseContentStorageTests(FileStorageTests): class FileFieldStorageTests(TestCase): def tearDown(self): - shutil.rmtree(temp_storage_location) + if os.path.exists(temp_storage_location): + shutil.rmtree(temp_storage_location) def _storage_max_filename_length(self, storage): """ @@ -928,6 +946,20 @@ class FileFieldStorageTests(TestCase): self.assertEqual(obj.default.read(), b"default content") obj.default.close() + def test_filefield_db_default(self): + temp_storage.save("tests/db_default.txt", ContentFile("default content")) + obj = Storage.objects.create() + self.assertEqual(obj.db_default.name, "tests/db_default.txt") + self.assertEqual(obj.db_default.read(), b"default content") + obj.db_default.close() + + # File is not deleted, even if there are no more objects using it. + obj.delete() + s = Storage() + self.assertEqual(s.db_default.name, "tests/db_default.txt") + self.assertEqual(s.db_default.read(), b"default content") + s.db_default.close() + def test_empty_upload_to(self): # upload_to can be empty, meaning it does not use subdirectory. obj = Storage() @@ -994,6 +1026,23 @@ class FileFieldStorageTests(TestCase): with temp_storage.open("tests/stringio") as f: self.assertEqual(f.read(), b"content") + @override_settings( + STORAGES={ + DEFAULT_STORAGE_ALIAS: { + "BACKEND": "django.core.files.storage.InMemoryStorage" + } + } + ) + def test_create_file_field_from_another_file_field_in_memory_storage(self): + f = ContentFile("content", "file.txt") + obj = Storage.objects.create(storage_callable_default=f) + new_obj = Storage.objects.create( + storage_callable_default=obj.storage_callable_default.file + ) + storage = callable_default_storage() + with storage.open(new_obj.storage_callable_default.name) as f: + self.assertEqual(f.read(), b"content") + class FieldCallableFileStorageTests(SimpleTestCase): def setUp(self): diff --git a/tests/fixtures_regress/fixtures/sequence_empty_lines_jsonl.jsonl b/tests/fixtures_regress/fixtures/sequence_empty_lines_jsonl.jsonl new file mode 100644 index 0000000000..c8ac372cab --- /dev/null +++ b/tests/fixtures_regress/fixtures/sequence_empty_lines_jsonl.jsonl @@ -0,0 +1,3 @@ + + +{"pk": "1", "model": "fixtures_regress.animal", "fields": {"name": "Eagle", "latin_name": "Aquila", "count": 3, "weight": 1.2}} diff --git a/tests/fixtures_regress/fixtures/sequence_extra_jsonl.jsonl b/tests/fixtures_regress/fixtures/sequence_extra_jsonl.jsonl new file mode 100644 index 0000000000..6644eaf95d --- /dev/null +++ b/tests/fixtures_regress/fixtures/sequence_extra_jsonl.jsonl @@ -0,0 +1,2 @@ +{"pk": "1", "model": "fixtures_regress.animal", "fields": {"name": "Eagle", "extra_name": "Super Eagle", "latin_name": "Aquila", "count": 3, "weight": 1.2}} +{"pk": "1", "model": "fixtures_regress.animal_extra", "fields": {"name": "Nonexistent model", "extra_name": "test for ticket #29522", "latin_name": "Aquila", "count": 3, "weight": 1.2}} diff --git a/tests/fixtures_regress/fixtures/sequence_extra_yaml.yaml b/tests/fixtures_regress/fixtures/sequence_extra_yaml.yaml new file mode 100644 index 0000000000..760b2d4275 --- /dev/null +++ b/tests/fixtures_regress/fixtures/sequence_extra_yaml.yaml @@ -0,0 +1,17 @@ +- pk: "1" + model: fixtures_regress.animal + fields: + name: Cat + extra_name: Super Cat + latin_name: Felis catus + count: 3 + weight: 1.2 + +- pk: "1" + model: fixtures_regress.animal_extra + fields: + name: Nonexistent model + extra_name: test for ticket \#29522 + latin_name: Felis catus + count: 3 + weight: 1.2 diff --git a/tests/fixtures_regress/tests.py b/tests/fixtures_regress/tests.py index 54d7cac50a..4a982c7262 100644 --- a/tests/fixtures_regress/tests.py +++ b/tests/fixtures_regress/tests.py @@ -2,6 +2,7 @@ import json import os import re +import unittest from io import StringIO from pathlib import Path @@ -55,6 +56,13 @@ from .models import ( Widget, ) +try: + import yaml # NOQA + + HAS_YAML = True +except ImportError: + HAS_YAML = False + _cur_dir = os.path.dirname(os.path.abspath(__file__)) @@ -96,12 +104,22 @@ class TestFixtures(TestCase): the serialized data for fields that have been removed from the database when not ignored. """ - with self.assertRaises(DeserializationError): - management.call_command( - "loaddata", - "sequence_extra", - verbosity=0, - ) + test_fixtures = [ + "sequence_extra", + "sequence_extra_jsonl", + ] + if HAS_YAML: + test_fixtures.append("sequence_extra_yaml") + for fixture_file in test_fixtures: + with ( + self.subTest(fixture_file=fixture_file), + self.assertRaises(DeserializationError), + ): + management.call_command( + "loaddata", + fixture_file, + verbosity=0, + ) def test_loaddata_not_found_fields_ignore(self): """ @@ -130,6 +148,33 @@ class TestFixtures(TestCase): ) self.assertEqual(Animal.specimens.all()[0].name, "Wolf") + def test_loaddata_not_found_fields_ignore_jsonl(self): + management.call_command( + "loaddata", + "sequence_extra_jsonl", + ignore=True, + verbosity=0, + ) + self.assertEqual(Animal.specimens.all()[0].name, "Eagle") + + @unittest.skipUnless(HAS_YAML, "No yaml library detected") + def test_loaddata_not_found_fields_ignore_yaml(self): + management.call_command( + "loaddata", + "sequence_extra_yaml", + ignore=True, + verbosity=0, + ) + self.assertEqual(Animal.specimens.all()[0].name, "Cat") + + def test_loaddata_empty_lines_jsonl(self): + management.call_command( + "loaddata", + "sequence_empty_lines_jsonl.jsonl", + verbosity=0, + ) + self.assertEqual(Animal.specimens.all()[0].name, "Eagle") + @skipIfDBFeature("interprets_empty_strings_as_nulls") def test_pretty_print_xml(self): """ diff --git a/tests/foreign_object/models/person.py b/tests/foreign_object/models/person.py index 33063e728a..f0848e6c3e 100644 --- a/tests/foreign_object/models/person.py +++ b/tests/foreign_object/models/person.py @@ -49,7 +49,7 @@ class Group(models.Model): class Membership(models.Model): # Table Column Fields - membership_country = models.ForeignKey(Country, models.CASCADE) + membership_country = models.ForeignKey(Country, models.CASCADE, null=True) date_joined = models.DateTimeField(default=datetime.datetime.now) invite_reason = models.CharField(max_length=64, null=True) person_id = models.IntegerField() diff --git a/tests/foreign_object/test_tuple_lookups.py b/tests/foreign_object/test_tuple_lookups.py new file mode 100644 index 0000000000..e2561676f3 --- /dev/null +++ b/tests/foreign_object/test_tuple_lookups.py @@ -0,0 +1,383 @@ +import unittest + +from django.db import NotSupportedError, connection +from django.db.models import F +from django.db.models.fields.tuple_lookups import ( + TupleExact, + TupleGreaterThan, + TupleGreaterThanOrEqual, + TupleIn, + TupleIsNull, + TupleLessThan, + TupleLessThanOrEqual, +) +from django.test import TestCase + +from .models import Contact, Customer + + +class TupleLookupsTests(TestCase): + @classmethod + def setUpTestData(cls): + super().setUpTestData() + cls.customer_1 = Customer.objects.create(customer_id=1, company="a") + cls.customer_2 = Customer.objects.create(customer_id=1, company="b") + cls.customer_3 = Customer.objects.create(customer_id=2, company="c") + cls.customer_4 = Customer.objects.create(customer_id=3, company="d") + cls.customer_5 = Customer.objects.create(customer_id=1, company="e") + cls.contact_1 = Contact.objects.create(customer=cls.customer_1) + cls.contact_2 = Contact.objects.create(customer=cls.customer_1) + cls.contact_3 = Contact.objects.create(customer=cls.customer_2) + cls.contact_4 = Contact.objects.create(customer=cls.customer_3) + cls.contact_5 = Contact.objects.create(customer=cls.customer_1) + cls.contact_6 = Contact.objects.create(customer=cls.customer_5) + + def test_exact(self): + test_cases = ( + (self.customer_1, (self.contact_1, self.contact_2, self.contact_5)), + (self.customer_2, (self.contact_3,)), + (self.customer_3, (self.contact_4,)), + (self.customer_4, ()), + (self.customer_5, (self.contact_6,)), + ) + + for customer, contacts in test_cases: + with self.subTest( + "filter(customer=customer)", + customer=customer, + contacts=contacts, + ): + self.assertSequenceEqual( + Contact.objects.filter(customer=customer).order_by("id"), contacts + ) + with self.subTest( + "filter(TupleExact)", + customer=customer, + contacts=contacts, + ): + lhs = (F("customer_code"), F("company_code")) + rhs = (customer.customer_id, customer.company) + lookup = TupleExact(lhs, rhs) + self.assertSequenceEqual( + Contact.objects.filter(lookup).order_by("id"), contacts + ) + + def test_exact_subquery(self): + with self.assertRaisesMessage( + NotSupportedError, "'exact' doesn't support multi-column subqueries." + ): + subquery = Customer.objects.filter(id=self.customer_1.id)[:1] + self.assertSequenceEqual( + Contact.objects.filter(customer=subquery).order_by("id"), () + ) + + def test_in(self): + cust_1, cust_2, cust_3, cust_4, cust_5 = ( + self.customer_1, + self.customer_2, + self.customer_3, + self.customer_4, + self.customer_5, + ) + c1, c2, c3, c4, c5, c6 = ( + self.contact_1, + self.contact_2, + self.contact_3, + self.contact_4, + self.contact_5, + self.contact_6, + ) + test_cases = ( + ((), ()), + ((cust_1,), (c1, c2, c5)), + ((cust_1, cust_2), (c1, c2, c3, c5)), + ((cust_1, cust_2, cust_3), (c1, c2, c3, c4, c5)), + ((cust_1, cust_2, cust_3, cust_4), (c1, c2, c3, c4, c5)), + ((cust_1, cust_2, cust_3, cust_4, cust_5), (c1, c2, c3, c4, c5, c6)), + ) + + for customers, contacts in test_cases: + with self.subTest( + "filter(customer__in=customers)", + customers=customers, + contacts=contacts, + ): + self.assertSequenceEqual( + Contact.objects.filter(customer__in=customers).order_by("id"), + contacts, + ) + with self.subTest( + "filter(TupleIn)", + customers=customers, + contacts=contacts, + ): + lhs = (F("customer_code"), F("company_code")) + rhs = [(c.customer_id, c.company) for c in customers] + lookup = TupleIn(lhs, rhs) + self.assertSequenceEqual( + Contact.objects.filter(lookup).order_by("id"), contacts + ) + + @unittest.skipIf( + connection.vendor == "mysql", + "MySQL doesn't support LIMIT & IN/ALL/ANY/SOME subquery", + ) + def test_in_subquery(self): + subquery = Customer.objects.filter(id=self.customer_1.id)[:1] + self.assertSequenceEqual( + Contact.objects.filter(customer__in=subquery).order_by("id"), + (self.contact_1, self.contact_2, self.contact_5), + ) + + def test_lt(self): + c1, c2, c3, c4, c5, c6 = ( + self.contact_1, + self.contact_2, + self.contact_3, + self.contact_4, + self.contact_5, + self.contact_6, + ) + test_cases = ( + (self.customer_1, ()), + (self.customer_2, (c1, c2, c5)), + (self.customer_5, (c1, c2, c3, c5)), + (self.customer_3, (c1, c2, c3, c5, c6)), + (self.customer_4, (c1, c2, c3, c4, c5, c6)), + ) + + for customer, contacts in test_cases: + with self.subTest( + "filter(customer__lt=customer)", + customer=customer, + contacts=contacts, + ): + self.assertSequenceEqual( + Contact.objects.filter(customer__lt=customer).order_by("id"), + contacts, + ) + with self.subTest( + "filter(TupleLessThan)", + customer=customer, + contacts=contacts, + ): + lhs = (F("customer_code"), F("company_code")) + rhs = (customer.customer_id, customer.company) + lookup = TupleLessThan(lhs, rhs) + self.assertSequenceEqual( + Contact.objects.filter(lookup).order_by("id"), contacts + ) + + def test_lt_subquery(self): + with self.assertRaisesMessage( + NotSupportedError, "'lt' doesn't support multi-column subqueries." + ): + subquery = Customer.objects.filter(id=self.customer_1.id)[:1] + self.assertSequenceEqual( + Contact.objects.filter(customer__lt=subquery).order_by("id"), () + ) + + def test_lte(self): + c1, c2, c3, c4, c5, c6 = ( + self.contact_1, + self.contact_2, + self.contact_3, + self.contact_4, + self.contact_5, + self.contact_6, + ) + test_cases = ( + (self.customer_1, (c1, c2, c5)), + (self.customer_2, (c1, c2, c3, c5)), + (self.customer_5, (c1, c2, c3, c5, c6)), + (self.customer_3, (c1, c2, c3, c4, c5, c6)), + (self.customer_4, (c1, c2, c3, c4, c5, c6)), + ) + + for customer, contacts in test_cases: + with self.subTest( + "filter(customer__lte=customer)", + customer=customer, + contacts=contacts, + ): + self.assertSequenceEqual( + Contact.objects.filter(customer__lte=customer).order_by("id"), + contacts, + ) + with self.subTest( + "filter(TupleLessThanOrEqual)", + customer=customer, + contacts=contacts, + ): + lhs = (F("customer_code"), F("company_code")) + rhs = (customer.customer_id, customer.company) + lookup = TupleLessThanOrEqual(lhs, rhs) + self.assertSequenceEqual( + Contact.objects.filter(lookup).order_by("id"), contacts + ) + + def test_lte_subquery(self): + with self.assertRaisesMessage( + NotSupportedError, "'lte' doesn't support multi-column subqueries." + ): + subquery = Customer.objects.filter(id=self.customer_1.id)[:1] + self.assertSequenceEqual( + Contact.objects.filter(customer__lte=subquery).order_by("id"), () + ) + + def test_gt(self): + test_cases = ( + (self.customer_1, (self.contact_3, self.contact_4, self.contact_6)), + (self.customer_2, (self.contact_4, self.contact_6)), + (self.customer_5, (self.contact_4,)), + (self.customer_3, ()), + (self.customer_4, ()), + ) + + for customer, contacts in test_cases: + with self.subTest( + "filter(customer__gt=customer)", + customer=customer, + contacts=contacts, + ): + self.assertSequenceEqual( + Contact.objects.filter(customer__gt=customer).order_by("id"), + contacts, + ) + with self.subTest( + "filter(TupleGreaterThan)", + customer=customer, + contacts=contacts, + ): + lhs = (F("customer_code"), F("company_code")) + rhs = (customer.customer_id, customer.company) + lookup = TupleGreaterThan(lhs, rhs) + self.assertSequenceEqual( + Contact.objects.filter(lookup).order_by("id"), contacts + ) + + def test_gt_subquery(self): + with self.assertRaisesMessage( + NotSupportedError, "'gt' doesn't support multi-column subqueries." + ): + subquery = Customer.objects.filter(id=self.customer_1.id)[:1] + self.assertSequenceEqual( + Contact.objects.filter(customer__gt=subquery).order_by("id"), () + ) + + def test_gte(self): + c1, c2, c3, c4, c5, c6 = ( + self.contact_1, + self.contact_2, + self.contact_3, + self.contact_4, + self.contact_5, + self.contact_6, + ) + test_cases = ( + (self.customer_1, (c1, c2, c3, c4, c5, c6)), + (self.customer_2, (c3, c4, c6)), + (self.customer_5, (c4, c6)), + (self.customer_3, (c4,)), + (self.customer_4, ()), + ) + + for customer, contacts in test_cases: + with self.subTest( + "filter(customer__gte=customer)", + customer=customer, + contacts=contacts, + ): + self.assertSequenceEqual( + Contact.objects.filter(customer__gte=customer).order_by("pk"), + contacts, + ) + with self.subTest( + "filter(TupleGreaterThanOrEqual)", + customer=customer, + contacts=contacts, + ): + lhs = (F("customer_code"), F("company_code")) + rhs = (customer.customer_id, customer.company) + lookup = TupleGreaterThanOrEqual(lhs, rhs) + self.assertSequenceEqual( + Contact.objects.filter(lookup).order_by("id"), contacts + ) + + def test_gte_subquery(self): + with self.assertRaisesMessage( + NotSupportedError, "'gte' doesn't support multi-column subqueries." + ): + subquery = Customer.objects.filter(id=self.customer_1.id)[:1] + self.assertSequenceEqual( + Contact.objects.filter(customer__gte=subquery).order_by("id"), () + ) + + def test_isnull(self): + contacts = ( + self.contact_1, + self.contact_2, + self.contact_3, + self.contact_4, + self.contact_5, + self.contact_6, + ) + + with self.subTest("filter(customer__isnull=True)"): + self.assertSequenceEqual( + Contact.objects.filter(customer__isnull=True).order_by("id"), + (), + ) + with self.subTest("filter(TupleIsNull(True))"): + lhs = (F("customer_code"), F("company_code")) + lookup = TupleIsNull(lhs, True) + self.assertSequenceEqual( + Contact.objects.filter(lookup).order_by("id"), + (), + ) + with self.subTest("filter(customer__isnull=False)"): + self.assertSequenceEqual( + Contact.objects.filter(customer__isnull=False).order_by("id"), + contacts, + ) + with self.subTest("filter(TupleIsNull(False))"): + lhs = (F("customer_code"), F("company_code")) + lookup = TupleIsNull(lhs, False) + self.assertSequenceEqual( + Contact.objects.filter(lookup).order_by("id"), + contacts, + ) + + def test_isnull_subquery(self): + with self.assertRaisesMessage( + NotSupportedError, "'isnull' doesn't support multi-column subqueries." + ): + subquery = Customer.objects.filter(id=0)[:1] + self.assertSequenceEqual( + Contact.objects.filter(customer__isnull=subquery).order_by("id"), () + ) + + 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" + test_cases = ( + ({"customer": 1}, m_2_elements % "exact"), + ({"customer": (1, 2, 3)}, m_2_elements % "exact"), + ({"customer__in": (1, 2, 3)}, m_2_elements_each), + ({"customer__in": ("foo", "bar")}, m_2_elements_each), + ({"customer__gt": 1}, m_2_elements % "gt"), + ({"customer__gt": (1, 2, 3)}, m_2_elements % "gt"), + ({"customer__gte": 1}, m_2_elements % "gte"), + ({"customer__gte": (1, 2, 3)}, m_2_elements % "gte"), + ({"customer__lt": 1}, m_2_elements % "lt"), + ({"customer__lt": (1, 2, 3)}, m_2_elements % "lt"), + ({"customer__lte": 1}, m_2_elements % "lte"), + ({"customer__lte": (1, 2, 3)}, m_2_elements % "lte"), + ) + + for kwargs, message in test_cases: + with ( + self.subTest(kwargs=kwargs), + self.assertRaisesMessage(ValueError, message), + ): + Contact.objects.get(**kwargs) diff --git a/tests/foreign_object/tests.py b/tests/foreign_object/tests.py index 2d3aa800f7..e288ecd7d4 100644 --- a/tests/foreign_object/tests.py +++ b/tests/foreign_object/tests.py @@ -516,18 +516,35 @@ class MultiColumnFKTests(TestCase): def test_isnull_lookup(self): m1 = Membership.objects.create( - membership_country=self.usa, person=self.bob, group_id=None + person_id=self.bob.id, + membership_country_id=self.usa.id, + group_id=None, ) m2 = Membership.objects.create( - membership_country=self.usa, person=self.bob, group=self.cia + person_id=self.jim.id, + membership_country_id=None, + group_id=self.cia.id, ) + m3 = Membership.objects.create( + person_id=self.jane.id, + membership_country_id=None, + group_id=None, + ) + m4 = Membership.objects.create( + person_id=self.george.id, + membership_country_id=self.soviet_union.id, + group_id=self.kgb.id, + ) + for member in [m1, m2, m3]: + with self.assertRaises(Membership.group.RelatedObjectDoesNotExist): + getattr(member, "group") self.assertSequenceEqual( Membership.objects.filter(group__isnull=True), - [m1], + [m1, m2, m3], ) self.assertSequenceEqual( Membership.objects.filter(group__isnull=False), - [m2], + [m4], ) @@ -703,24 +720,27 @@ class GetJoiningDeprecationTests(TestCase): "ForeignObject.get_joining_columns() is deprecated. Use " "get_joining_fields() instead." ) - with self.assertWarnsMessage(RemovedInDjango60Warning, msg): + with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx: Membership.person.field.get_joining_columns() + self.assertEqual(ctx.filename, __file__) def test_foreign_object_get_reverse_joining_columns_warning(self): msg = ( "ForeignObject.get_reverse_joining_columns() is deprecated. Use " "get_reverse_joining_fields() instead." ) - with self.assertWarnsMessage(RemovedInDjango60Warning, msg): + with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx: Membership.person.field.get_reverse_joining_columns() + self.assertEqual(ctx.filename, __file__) def test_foreign_object_rel_get_joining_columns_warning(self): msg = ( "ForeignObjectRel.get_joining_columns() is deprecated. Use " "get_joining_fields() instead." ) - with self.assertWarnsMessage(RemovedInDjango60Warning, msg): + with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx: Membership.person.field.remote_field.get_joining_columns() + self.assertEqual(ctx.filename, __file__) def test_join_get_joining_columns_warning(self): class CustomForeignKey(models.ForeignKey): diff --git a/tests/forms_tests/field_tests/test_urlfield.py b/tests/forms_tests/field_tests/test_urlfield.py index 8ba7842064..54bc7c5e46 100644 --- a/tests/forms_tests/field_tests/test_urlfield.py +++ b/tests/forms_tests/field_tests/test_urlfield.py @@ -163,9 +163,10 @@ class URLFieldAssumeSchemeDeprecationTest(FormFieldAssertionsMixin, SimpleTestCa "or set the FORMS_URLFIELD_ASSUME_HTTPS transitional setting to True to " "opt into using 'https' as the new default scheme." ) - with self.assertWarnsMessage(RemovedInDjango60Warning, msg): + with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx: f = URLField() self.assertEqual(f.clean("example.com"), "http://example.com") + self.assertEqual(ctx.filename, __file__) @ignore_warnings(category=RemovedInDjango60Warning) def test_urlfield_forms_urlfield_assume_https(self): diff --git a/tests/forms_tests/tests/test_renderers.py b/tests/forms_tests/tests/test_renderers.py index dbde6df49d..3c1d8bb8ea 100644 --- a/tests/forms_tests/tests/test_renderers.py +++ b/tests/forms_tests/tests/test_renderers.py @@ -64,16 +64,18 @@ class DeprecationTests(SimpleTestCase): "The DjangoDivFormRenderer transitional form renderer is deprecated. Use " "DjangoTemplates instead." ) - with self.assertRaisesMessage(RemovedInDjango60Warning, msg): + with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx: DjangoDivFormRenderer() + self.assertEqual(ctx.filename, __file__) def test_jinja2_div_renderer_warning(self): msg = ( "The Jinja2DivFormRenderer transitional form renderer is deprecated. Use " "Jinja2 instead." ) - with self.assertRaisesMessage(RemovedInDjango60Warning, msg): + with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx: Jinja2DivFormRenderer() + self.assertEqual(ctx.filename, __file__) @ignore_warnings(category=RemovedInDjango60Warning) def test_deprecation_renderers_can_be_instantiated(self): diff --git a/tests/forms_tests/widget_tests/test_colorinput.py b/tests/forms_tests/widget_tests/test_colorinput.py new file mode 100644 index 0000000000..f316534bfa --- /dev/null +++ b/tests/forms_tests/widget_tests/test_colorinput.py @@ -0,0 +1,15 @@ +from django.forms import ColorInput + +from .base import WidgetTest + + +class ColorInputTest(WidgetTest): + widget = ColorInput() + + def test_render(self): + self.check_html( + self.widget, + "color", + "", + html="", + ) diff --git a/tests/forms_tests/widget_tests/test_searchinput.py b/tests/forms_tests/widget_tests/test_searchinput.py new file mode 100644 index 0000000000..b11ffaaa82 --- /dev/null +++ b/tests/forms_tests/widget_tests/test_searchinput.py @@ -0,0 +1,12 @@ +from django.forms import SearchInput + +from .base import WidgetTest + + +class SearchInputTest(WidgetTest): + widget = SearchInput() + + def test_render(self): + self.check_html( + self.widget, "search", "", html='' + ) diff --git a/tests/forms_tests/widget_tests/test_telinput.py b/tests/forms_tests/widget_tests/test_telinput.py new file mode 100644 index 0000000000..1477f153d1 --- /dev/null +++ b/tests/forms_tests/widget_tests/test_telinput.py @@ -0,0 +1,12 @@ +from django.forms import TelInput + +from .base import WidgetTest + + +class TelInputTest(WidgetTest): + widget = TelInput() + + def test_render(self): + self.check_html( + self.widget, "telephone", "", html='' + ) diff --git a/tests/gis_tests/gdal_tests/test_geom.py b/tests/gis_tests/gdal_tests/test_geom.py index 3967b945a4..5c23a6f2cf 100644 --- a/tests/gis_tests/gdal_tests/test_geom.py +++ b/tests/gis_tests/gdal_tests/test_geom.py @@ -972,6 +972,7 @@ class DeprecationTests(SimpleTestCase): def test_coord_setter_deprecation(self): geom = OGRGeometry("POINT (1 2)") msg = "coord_dim setter is deprecated. Use set_3d() instead." - with self.assertWarnsMessage(RemovedInDjango60Warning, msg): + with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx: geom.coord_dim = 3 self.assertEqual(geom.coord_dim, 3) + self.assertEqual(ctx.filename, __file__) diff --git a/tests/gis_tests/gis_migrations/test_operations.py b/tests/gis_tests/gis_migrations/test_operations.py index 3ecde2025e..98201ed3f7 100644 --- a/tests/gis_tests/gis_migrations/test_operations.py +++ b/tests/gis_tests/gis_migrations/test_operations.py @@ -92,6 +92,20 @@ class OperationTestCase(TransactionTestCase): else: self.assertIn([column], [c["columns"] for c in constraints.values()]) + def assertSpatialIndexNotExists(self, table, column, raster=False): + with connection.cursor() as cursor: + constraints = connection.introspection.get_constraints(cursor, table) + if raster: + self.assertFalse( + any( + "st_convexhull(%s)" % column in c["definition"] + for c in constraints.values() + if c["definition"] is not None + ) + ) + else: + self.assertNotIn([column], [c["columns"] for c in constraints.values()]) + def alter_gis_model( self, migration_class, @@ -239,6 +253,102 @@ class OperationTests(OperationTestCase): if connection.features.supports_raster: self.assertSpatialIndexExists("gis_neighborhood", "rast", raster=True) + @skipUnlessDBFeature("can_alter_geometry_field") + def test_alter_field_add_spatial_index(self): + if not self.has_spatial_indexes: + self.skipTest("No support for Spatial indexes") + + self.alter_gis_model( + migrations.AddField, + "Neighborhood", + "point", + fields.PointField, + field_class_kwargs={"spatial_index": False}, + ) + self.assertSpatialIndexNotExists("gis_neighborhood", "point") + + self.alter_gis_model( + migrations.AlterField, + "Neighborhood", + "point", + fields.PointField, + field_class_kwargs={"spatial_index": True}, + ) + self.assertSpatialIndexExists("gis_neighborhood", "point") + + @skipUnlessDBFeature("can_alter_geometry_field") + def test_alter_field_remove_spatial_index(self): + if not self.has_spatial_indexes: + self.skipTest("No support for Spatial indexes") + + self.assertSpatialIndexExists("gis_neighborhood", "geom") + + self.alter_gis_model( + migrations.AlterField, + "Neighborhood", + "geom", + fields.MultiPolygonField, + field_class_kwargs={"spatial_index": False}, + ) + self.assertSpatialIndexNotExists("gis_neighborhood", "geom") + + @skipUnlessDBFeature("can_alter_geometry_field") + @skipUnless(connection.vendor == "mysql", "MySQL specific test") + def test_alter_field_nullable_with_spatial_index(self): + if not self.has_spatial_indexes: + self.skipTest("No support for Spatial indexes") + + self.alter_gis_model( + migrations.AddField, + "Neighborhood", + "point", + fields.PointField, + field_class_kwargs={"spatial_index": False, "null": True}, + ) + # MySQL doesn't support spatial indexes on NULL columns. + self.assertSpatialIndexNotExists("gis_neighborhood", "point") + + self.alter_gis_model( + migrations.AlterField, + "Neighborhood", + "point", + fields.PointField, + field_class_kwargs={"spatial_index": True, "null": True}, + ) + self.assertSpatialIndexNotExists("gis_neighborhood", "point") + + self.alter_gis_model( + migrations.AlterField, + "Neighborhood", + "point", + fields.PointField, + field_class_kwargs={"spatial_index": False, "null": True}, + ) + self.assertSpatialIndexNotExists("gis_neighborhood", "point") + + @skipUnlessDBFeature("can_alter_geometry_field") + def test_alter_field_with_spatial_index(self): + if not self.has_spatial_indexes: + self.skipTest("No support for Spatial indexes") + + self.alter_gis_model( + migrations.AddField, + "Neighborhood", + "point", + fields.PointField, + field_class_kwargs={"spatial_index": True}, + ) + self.assertSpatialIndexExists("gis_neighborhood", "point") + + self.alter_gis_model( + migrations.AlterField, + "Neighborhood", + "point", + fields.PointField, + field_class_kwargs={"spatial_index": True, "srid": 3086}, + ) + self.assertSpatialIndexExists("gis_neighborhood", "point") + @skipUnlessDBFeature("supports_3d_storage") def test_add_3d_field_opclass(self): if not connection.ops.postgis: diff --git a/tests/gis_tests/test_geoip2.py b/tests/gis_tests/test_geoip2.py index 12837725c9..11c73bec0c 100644 --- a/tests/gis_tests/test_geoip2.py +++ b/tests/gis_tests/test_geoip2.py @@ -207,16 +207,18 @@ class GeoLite2Test(SimpleTestCase): def test_coords_deprecation_warning(self): g = GeoIP2() msg = "GeoIP2.coords() is deprecated. Use GeoIP2.lon_lat() instead." - with self.assertWarnsMessage(RemovedInDjango60Warning, msg): + with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx: e1, e2 = g.coords(self.ipv4_str) self.assertIsInstance(e1, float) self.assertIsInstance(e2, float) + self.assertEqual(ctx.filename, __file__) def test_open_deprecation_warning(self): msg = "GeoIP2.open() is deprecated. Use GeoIP2() instead." - with self.assertWarnsMessage(RemovedInDjango60Warning, msg): + with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx: g = GeoIP2.open(settings.GEOIP_PATH, GeoIP2.MODE_AUTO) self.assertTrue(g._reader) + self.assertEqual(ctx.filename, __file__) @skipUnless(HAS_GEOIP2, "GeoIP2 is required.") diff --git a/tests/handlers/tests.py b/tests/handlers/tests.py index ffa362abdd..e73fc15195 100644 --- a/tests/handlers/tests.py +++ b/tests/handlers/tests.py @@ -258,8 +258,9 @@ class HandlerRequestTests(SimpleTestCase): "StreamingHttpResponse must consume asynchronous iterators in order to " "serve them synchronously. Use a synchronous iterator instead." ) - with self.assertWarnsMessage(Warning, msg): + with self.assertWarnsMessage(Warning, msg) as ctx: self.assertEqual(b"".join(list(response)), b"streaming content") + self.assertEqual(ctx.filename, __file__) class ScriptNameTests(SimpleTestCase): @@ -350,10 +351,11 @@ class AsyncHandlerRequestTests(SimpleTestCase): "StreamingHttpResponse must consume synchronous iterators in order to " "serve them asynchronously. Use an asynchronous iterator instead." ) - with self.assertWarnsMessage(Warning, msg): + with self.assertWarnsMessage(Warning, msg) as ctx: self.assertEqual( b"".join([chunk async for chunk in response]), b"streaming content" ) + self.assertEqual(ctx.filename, __file__) async def test_async_streaming(self): response = await self.async_client.get("/async_streaming/") diff --git a/tests/i18n/test_compilation.py b/tests/i18n/test_compilation.py index 7da95ba9e9..7b02776dbe 100644 --- a/tests/i18n/test_compilation.py +++ b/tests/i18n/test_compilation.py @@ -8,7 +8,6 @@ from subprocess import run from unittest import mock from django.core.management import CommandError, call_command, execute_from_command_line -from django.core.management.commands.makemessages import Command as MakeMessagesCommand from django.core.management.utils import find_command from django.test import SimpleTestCase, override_settings from django.test.utils import captured_stderr, captured_stdout @@ -269,9 +268,6 @@ class CompilationErrorHandling(MessageCompilationTests): "django.core.management.utils.run", lambda *args, **kwargs: run(*args, env=env, **kwargs), ): - cmd = MakeMessagesCommand() - if cmd.gettext_version < (0, 18, 3): - self.skipTest("python-brace-format is a recent gettext addition.") stderr = StringIO() with self.assertRaisesMessage( CommandError, "compilemessages generated one or more errors" diff --git a/tests/i18n/test_extraction.py b/tests/i18n/test_extraction.py index 226d51ce11..7aa600c4c1 100644 --- a/tests/i18n/test_extraction.py +++ b/tests/i18n/test_extraction.py @@ -6,7 +6,7 @@ import time import warnings from io import StringIO from pathlib import Path -from unittest import mock, skipIf, skipUnless +from unittest import mock, skipUnless from admin_scripts.tests import AdminScriptTestCase @@ -25,10 +25,6 @@ from .utils import POFileAssertionMixin, RunInTmpDirMixin, copytree LOCALE = "de" has_xgettext = find_command("xgettext") -gettext_version = MakeMessagesCommand().gettext_version if has_xgettext else None -requires_gettext_019 = skipIf( - has_xgettext and gettext_version < (0, 19), "gettext 0.19 required" -) @skipUnless(has_xgettext, "xgettext is mandatory for extraction tests") @@ -836,7 +832,6 @@ class LocationCommentsTests(ExtractorTests): self.assertLocationCommentNotPresent(self.PO_FILE, None, ".html.py") self.assertLocationCommentPresent(self.PO_FILE, 5, "templates", "test.html") - @requires_gettext_019 def test_add_location_full(self): """makemessages --add-location=full""" management.call_command( @@ -848,7 +843,6 @@ class LocationCommentsTests(ExtractorTests): self.PO_FILE, "Translatable literal #6b", "templates", "test.html" ) - @requires_gettext_019 def test_add_location_file(self): """makemessages --add-location=file""" management.call_command( @@ -862,7 +856,6 @@ class LocationCommentsTests(ExtractorTests): self.PO_FILE, "Translatable literal #6b", "templates", "test.html" ) - @requires_gettext_019 def test_add_location_never(self): """makemessages --add-location=never""" management.call_command( @@ -871,24 +864,6 @@ class LocationCommentsTests(ExtractorTests): self.assertTrue(os.path.exists(self.PO_FILE)) self.assertLocationCommentNotPresent(self.PO_FILE, None, "test.html") - @mock.patch( - "django.core.management.commands.makemessages.Command.gettext_version", - new=(0, 18, 99), - ) - def test_add_location_gettext_version_check(self): - """ - CommandError is raised when using makemessages --add-location with - gettext < 0.19. - """ - msg = ( - "The --add-location option requires gettext 0.19 or later. You have " - "0.18.99." - ) - with self.assertRaisesMessage(CommandError, msg): - management.call_command( - "makemessages", locale=[LOCALE], verbosity=0, add_location="full" - ) - class NoObsoleteExtractorTests(ExtractorTests): work_subdir = "obsolete_translations" diff --git a/tests/i18n/tests.py b/tests/i18n/tests.py index 1bd1dadf93..1f50ba1112 100644 --- a/tests/i18n/tests.py +++ b/tests/i18n/tests.py @@ -8,7 +8,7 @@ import tempfile from contextlib import contextmanager from importlib import import_module from pathlib import Path -from unittest import mock +from unittest import mock, skipUnless from asgiref.local import Local @@ -17,6 +17,7 @@ from django.apps import AppConfig from django.conf import settings from django.conf.locale import LANG_INFO from django.conf.urls.i18n import i18n_patterns +from django.core.management.utils import find_command, popen_wrapper from django.template import Context, Template from django.test import RequestFactory, SimpleTestCase, TestCase, override_settings from django.utils import translation @@ -99,7 +100,7 @@ class TranslationTests(SimpleTestCase): ) self.assertEqual( ngettext("%(num)d year", "%(num)d years", 2) % {"num": 2}, - "2 années", + "2 ans", ) self.assertEqual( ngettext("%(size)d byte", "%(size)d bytes", 0) % {"size": 0}, "0 octet" @@ -130,6 +131,49 @@ class TranslationTests(SimpleTestCase): self.assertEqual(french._catalog[("%d singular", 0)], "%d singulier") self.assertEqual(french._catalog[("%(num)d hour", 0)], "%(num)d heure") + @translation.override("fr") + @skipUnless(find_command("msgfmt"), "msgfmt is mandatory for this test") + def test_multiple_plurals_merge(self): + def _create_translation_from_string(content): + with tempfile.TemporaryDirectory() as dirname: + po_path = Path(dirname).joinpath("fr", "LC_MESSAGES", "django.po") + po_path.parent.mkdir(parents=True) + po_path.write_text(content) + errors = popen_wrapper( + ["msgfmt", "-o", po_path.with_suffix(".mo"), po_path] + )[1] + if errors: + self.fail(f"msgfmt compilation error: {errors}") + return gettext_module.translation( + domain="django", + localedir=dirname, + languages=["fr"], + ) + + french = trans_real.catalog() + # Merge a new translation file with different plural forms. + catalog1 = _create_translation_from_string( + 'msgid ""\n' + 'msgstr ""\n' + '"Content-Type: text/plain; charset=UTF-8\\n"\n' + '"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n==0 ? 1 : 2);\\n"\n' + 'msgid "I win"\n' + 'msgstr "Je perds"\n' + ) + french.merge(catalog1) + # Merge a second translation file with plural forms from django.conf. + catalog2 = _create_translation_from_string( + 'msgid ""\n' + 'msgstr ""\n' + '"Content-Type: text/plain; charset=UTF-8\\n"\n' + '"Plural-Forms: Plural-Forms: nplurals=2; plural=(n > 1);\\n"\n' + 'msgid "I win"\n' + 'msgstr "Je gagne"\n' + ) + french.merge(catalog2) + # Translations from this last one are supposed to win. + self.assertEqual(french.gettext("I win"), "Je gagne") + def test_override(self): activate("de") try: @@ -1673,14 +1717,13 @@ class MiscTests(SimpleTestCase): g("xyz") with self.assertRaises(LookupError): g("xy-zz") - msg = "'lang_code' exceeds the maximum accepted length" with self.assertRaises(LookupError): g("x" * LANGUAGE_CODE_MAX_LENGTH) - with self.assertRaisesMessage(ValueError, msg): + with self.assertRaises(LookupError): g("x" * (LANGUAGE_CODE_MAX_LENGTH + 1)) # 167 * 3 = 501 which is LANGUAGE_CODE_MAX_LENGTH + 1. self.assertEqual(g("en-" * 167), "en") - with self.assertRaisesMessage(ValueError, msg): + with self.assertRaises(LookupError): g("en-" * 167, strict=True) self.assertEqual(g("en-" * 30000), "en") # catastrophic test @@ -1734,6 +1777,7 @@ class MiscTests(SimpleTestCase): ("/i-mingo/", "i-mingo"), ("/kl-tunumiit/", "kl-tunumiit"), ("/nan-hani-tw/", "nan-hani-tw"), + (f"/{'a' * 501}/", None), ] for path, language in tests: with self.subTest(path=path): @@ -2009,6 +2053,11 @@ class CountrySpecificLanguageTests(SimpleTestCase): lang = get_language_from_request(request) self.assertEqual("bg", lang) + def test_get_language_from_request_code_too_long(self): + request = self.rf.get("/", headers={"accept-language": "a" * 501}) + lang = get_language_from_request(request) + self.assertEqual("en-us", lang) + def test_get_language_from_request_null(self): lang = trans_null.get_language_from_request(None) self.assertEqual(lang, "en") diff --git a/tests/lookup/tests.py b/tests/lookup/tests.py index 28acd72874..df96546d04 100644 --- a/tests/lookup/tests.py +++ b/tests/lookup/tests.py @@ -24,6 +24,7 @@ from django.db.models.lookups import ( Exact, GreaterThan, GreaterThanOrEqual, + In, IsNull, LessThan, LessThanOrEqual, @@ -327,6 +328,13 @@ class LookupTests(TestCase): with self.assertRaisesMessage(TypeError, msg): Article.objects.all()[0:5].in_bulk([self.a1.id, self.a2.id]) + def test_in_bulk_not_model_iterable(self): + msg = "in_bulk() cannot be used with values() or values_list()." + with self.assertRaisesMessage(TypeError, msg): + Author.objects.values().in_bulk() + with self.assertRaisesMessage(TypeError, msg): + Author.objects.values_list().in_bulk() + def test_values(self): # values() returns a list of dictionaries instead of object instances -- # and you can specify which fields you want to retrieve. @@ -1504,6 +1512,25 @@ class LookupQueryingTests(TestCase): [self.s1, self.s3], ) + def test_in_lookup_in_filter(self): + test_cases = [ + ((), ()), + ((1942,), (self.s1,)), + ((1842,), (self.s2,)), + ((2042,), (self.s3,)), + ((1942, 1842), (self.s1, self.s2)), + ((1942, 2042), (self.s1, self.s3)), + ((1842, 2042), (self.s2, self.s3)), + ((1942, 1942, 1942), (self.s1,)), + ((1942, 2042, 1842), (self.s1, self.s2, self.s3)), + ] + + for years, seasons in test_cases: + with self.subTest(years=years, seasons=seasons): + self.assertSequenceEqual( + Season.objects.filter(In(F("year"), years)).order_by("pk"), seasons + ) + def test_filter_lookup_lhs(self): qs = Season.objects.annotate(before_20=LessThan(F("year"), 2000)).filter( before_20=LessThan(F("year"), 1900), diff --git a/tests/mail/custombackend.py b/tests/mail/custombackend.py index 14e7f077ba..c63f1c07b5 100644 --- a/tests/mail/custombackend.py +++ b/tests/mail/custombackend.py @@ -12,3 +12,9 @@ class EmailBackend(BaseEmailBackend): # Messages are stored in an instance variable for testing. self.test_outbox.extend(email_messages) return len(email_messages) + + +class FailingEmailBackend(BaseEmailBackend): + + def send_messages(self, email_messages): + raise ValueError("FailingEmailBackend is doomed to fail.") diff --git a/tests/mail/tests.py b/tests/mail/tests.py index a0d28eb0ce..6280bfa5c8 100644 --- a/tests/mail/tests.py +++ b/tests/mail/tests.py @@ -17,6 +17,8 @@ from unittest import mock, skipUnless from django.core import mail from django.core.mail import ( DNS_NAME, + EmailAlternative, + EmailAttachment, EmailMessage, EmailMultiAlternatives, mail_admins, @@ -557,12 +559,50 @@ class MailTests(HeadersCheckMixin, SimpleTestCase): mime_type = "text/html" msg.attach_alternative(html_content, mime_type) + self.assertIsInstance(msg.alternatives[0], EmailAlternative) + self.assertEqual(msg.alternatives[0][0], html_content) self.assertEqual(msg.alternatives[0].content, html_content) self.assertEqual(msg.alternatives[0][1], mime_type) self.assertEqual(msg.alternatives[0].mimetype, mime_type) + self.assertIn(html_content, msg.message().as_string()) + + def test_alternatives_constructor(self): + html_content = "

    This is html

    " + mime_type = "text/html" + + msg = EmailMultiAlternatives( + alternatives=[EmailAlternative(html_content, mime_type)] + ) + + self.assertIsInstance(msg.alternatives[0], EmailAlternative) + + self.assertEqual(msg.alternatives[0][0], html_content) + self.assertEqual(msg.alternatives[0].content, html_content) + + self.assertEqual(msg.alternatives[0][1], mime_type) + self.assertEqual(msg.alternatives[0].mimetype, mime_type) + + self.assertIn(html_content, msg.message().as_string()) + + def test_alternatives_constructor_from_tuple(self): + html_content = "

    This is html

    " + mime_type = "text/html" + + msg = EmailMultiAlternatives(alternatives=[(html_content, mime_type)]) + + self.assertIsInstance(msg.alternatives[0], EmailAlternative) + + self.assertEqual(msg.alternatives[0][0], html_content) + self.assertEqual(msg.alternatives[0].content, html_content) + + self.assertEqual(msg.alternatives[0][1], mime_type) + self.assertEqual(msg.alternatives[0].mimetype, mime_type) + + self.assertIn(html_content, msg.message().as_string()) + def test_none_body(self): msg = EmailMessage("subject", None, "from@example.com", ["to@example.com"]) self.assertEqual(msg.body, "") @@ -654,6 +694,51 @@ class MailTests(HeadersCheckMixin, SimpleTestCase): self.assertEqual(msg.attachments[0][2], mime_type) self.assertEqual(msg.attachments[0].mimetype, mime_type) + attachments = self.get_decoded_attachments(msg) + self.assertEqual(attachments[0], (file_name, file_content.encode(), mime_type)) + + def test_attachments_constructor(self): + file_name = "example.txt" + file_content = "Text file content" + mime_type = "text/plain" + msg = EmailMessage( + attachments=[EmailAttachment(file_name, file_content, mime_type)] + ) + + self.assertIsInstance(msg.attachments[0], EmailAttachment) + + self.assertEqual(msg.attachments[0][0], file_name) + self.assertEqual(msg.attachments[0].filename, file_name) + + self.assertEqual(msg.attachments[0][1], file_content) + self.assertEqual(msg.attachments[0].content, file_content) + + self.assertEqual(msg.attachments[0][2], mime_type) + self.assertEqual(msg.attachments[0].mimetype, mime_type) + + attachments = self.get_decoded_attachments(msg) + self.assertEqual(attachments[0], (file_name, file_content.encode(), mime_type)) + + def test_attachments_constructor_from_tuple(self): + file_name = "example.txt" + file_content = "Text file content" + mime_type = "text/plain" + msg = EmailMessage(attachments=[(file_name, file_content, mime_type)]) + + self.assertIsInstance(msg.attachments[0], EmailAttachment) + + self.assertEqual(msg.attachments[0][0], file_name) + self.assertEqual(msg.attachments[0].filename, file_name) + + self.assertEqual(msg.attachments[0][1], file_content) + self.assertEqual(msg.attachments[0].content, file_content) + + self.assertEqual(msg.attachments[0][2], mime_type) + self.assertEqual(msg.attachments[0].mimetype, mime_type) + + attachments = self.get_decoded_attachments(msg) + self.assertEqual(attachments[0], (file_name, file_content.encode(), mime_type)) + def test_decoded_attachments(self): """Regression test for #9367""" headers = {"Date": "Fri, 09 Nov 2001 01:08:47 -0000", "Message-ID": "foo"} diff --git a/tests/many_to_many/tests.py b/tests/many_to_many/tests.py index 351e4eb8cc..c17ed0258c 100644 --- a/tests/many_to_many/tests.py +++ b/tests/many_to_many/tests.py @@ -583,8 +583,9 @@ class ManyToManyTests(TestCase): "get_prefetch_queryset() is deprecated. Use get_prefetch_querysets() " "instead." ) - with self.assertWarnsMessage(RemovedInDjango60Warning, msg): + with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx: self.a1.publications.get_prefetch_queryset(articles) + self.assertEqual(ctx.filename, __file__) def test_get_prefetch_querysets_invalid_querysets_length(self): articles = Article.objects.all() diff --git a/tests/many_to_one/tests.py b/tests/many_to_one/tests.py index ac5b35e055..e7dd0f229f 100644 --- a/tests/many_to_one/tests.py +++ b/tests/many_to_one/tests.py @@ -894,8 +894,9 @@ class ManyToOneTests(TestCase): "get_prefetch_queryset() is deprecated. Use get_prefetch_querysets() " "instead." ) - with self.assertWarnsMessage(RemovedInDjango60Warning, msg): + with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx: City.country.get_prefetch_queryset(cities) + self.assertEqual(ctx.filename, __file__) def test_get_prefetch_queryset_reverse_warning(self): usa = Country.objects.create(name="United States") @@ -905,8 +906,9 @@ class ManyToOneTests(TestCase): "get_prefetch_queryset() is deprecated. Use get_prefetch_querysets() " "instead." ) - with self.assertWarnsMessage(RemovedInDjango60Warning, msg): + with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx: usa.cities.get_prefetch_queryset(countries) + self.assertEqual(ctx.filename, __file__) def test_get_prefetch_querysets_invalid_querysets_length(self): City.objects.create(name="Chicago") diff --git a/tests/messages_tests/tests.py b/tests/messages_tests/tests.py index 19aeee9a08..3f5cd56e85 100644 --- a/tests/messages_tests/tests.py +++ b/tests/messages_tests/tests.py @@ -1,5 +1,7 @@ import importlib import sys +import traceback +import unittest from unittest import mock from django.conf import settings @@ -185,3 +187,17 @@ class AssertMessagesTest(MessagesTestMixin, SimpleTestCase): ) with self.assertRaisesMessage(AssertionError, msg): self.assertMessages(response, []) + + def test_method_frames_ignored_by_unittest(self): + response = FakeResponse() + try: + self.assertMessages(response, [object()]) + except AssertionError: + exc_type, exc, tb = sys.exc_info() + + result = unittest.TestResult() + result.addFailure(self, (exc_type, exc, tb)) + stack = traceback.extract_tb(exc.__traceback__) + self.assertEqual(len(stack), 1) + # Top element in the stack is this method, not assertMessages. + self.assertEqual(stack[-1].name, "test_method_frames_ignored_by_unittest") diff --git a/tests/migration_test_data_persistence/tests.py b/tests/migration_test_data_persistence/tests.py index 862a06c4a5..a04259bba1 100644 --- a/tests/migration_test_data_persistence/tests.py +++ b/tests/migration_test_data_persistence/tests.py @@ -1,3 +1,4 @@ +from django.core.management import call_command from django.test import TestCase, TransactionTestCase from .models import Book @@ -19,6 +20,26 @@ class MigrationDataPersistenceTestCase(TransactionTestCase): ) +class MigrationDataPersistenceClassSetup(TransactionTestCase): + """ + Data loaded in migrations is available during class setup if + TransactionTestCase.serialized_rollback = True. + """ + + available_apps = ["migration_test_data_persistence"] + serialized_rollback = True + + @classmethod + def setUpClass(cls): + # Simulate another TransactionTestCase having just torn down. + call_command("flush", verbosity=0, interactive=False) + super().setUpClass() + cls.book = Book.objects.first() + + def test_data_available_in_class_setup(self): + self.assertIsInstance(self.book, Book) + + class MigrationDataNormalPersistenceTestCase(TestCase): """ Data loaded in migrations is available on TestCase diff --git a/tests/migrations/migrations_test_apps/without_init_file/migrations/.keep b/tests/migrations/migrations_test_apps/distributed_app_location_1/namespace_app/migrations/.gitkeep similarity index 100% rename from tests/migrations/migrations_test_apps/without_init_file/migrations/.keep rename to tests/migrations/migrations_test_apps/distributed_app_location_1/namespace_app/migrations/.gitkeep diff --git a/tests/migrations/migrations_test_apps/distributed_app_location_2/namespace_app/migrations/.gitkeep b/tests/migrations/migrations_test_apps/distributed_app_location_2/namespace_app/migrations/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/migrations/migrations_test_apps/without_init_file/migrations/.gitkeep b/tests/migrations/migrations_test_apps/without_init_file/migrations/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/migrations/test_base.py b/tests/migrations/test_base.py index 0ff1dda1d9..cde4063d04 100644 --- a/tests/migrations/test_base.py +++ b/tests/migrations/test_base.py @@ -7,9 +7,11 @@ from importlib import import_module from django.apps import apps from django.db import connection, connections, migrations, models from django.db.migrations.migration import Migration +from django.db.migrations.optimizer import MigrationOptimizer from django.db.migrations.recorder import MigrationRecorder +from django.db.migrations.serializer import serializer_factory from django.db.migrations.state import ProjectState -from django.test import TransactionTestCase +from django.test import SimpleTestCase, TransactionTestCase from django.test.utils import extend_sys_path from django.utils.module_loading import module_dir @@ -400,3 +402,38 @@ class OperationTestBase(MigrationTestBase): ) ) return self.apply_operations(app_label, ProjectState(), operations) + + +class OptimizerTestBase(SimpleTestCase): + """Common functions to help test the optimizer.""" + + def optimize(self, operations, app_label): + """ + Handy shortcut for getting results + number of loops + """ + optimizer = MigrationOptimizer() + return optimizer.optimize(operations, app_label), optimizer._iterations + + def serialize(self, value): + return serializer_factory(value).serialize()[0] + + def assertOptimizesTo( + self, operations, expected, exact=None, less_than=None, app_label=None + ): + result, iterations = self.optimize(operations, app_label or "migrations") + result = [self.serialize(f) for f in result] + expected = [self.serialize(f) for f in expected] + self.assertEqual(expected, result) + if exact is not None and iterations != exact: + raise self.failureException( + "Optimization did not take exactly %s iterations (it took %s)" + % (exact, iterations) + ) + if less_than is not None and iterations >= less_than: + raise self.failureException( + "Optimization did not take less than %s iterations (it took %s)" + % (less_than, iterations) + ) + + def assertDoesNotOptimize(self, operations, **kwargs): + self.assertOptimizesTo(operations, operations, **kwargs) diff --git a/tests/migrations/test_commands.py b/tests/migrations/test_commands.py index 6ef172ee6f..cab2906ed1 100644 --- a/tests/migrations/test_commands.py +++ b/tests/migrations/test_commands.py @@ -4,6 +4,7 @@ import io import os import shutil import sys +from pathlib import Path from unittest import mock from django.apps import apps @@ -21,7 +22,7 @@ from django.db.backends.utils import truncate_name 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 +from django.test.utils import captured_stdout, extend_sys_path from django.utils import timezone from django.utils.version import get_docs_version @@ -1729,6 +1730,25 @@ class MakeMigrationsTests(MigrationTestBase): call_command("makemigrations", stdout=out) self.assertIn("0001_initial.py", out.getvalue()) + def test_makemigrations_no_init_ambiguous(self): + """ + Migration directories without an __init__.py file are not allowed if + there are multiple namespace search paths that resolve to them. + """ + out = io.StringIO() + with self.temporary_migration_module( + module="migrations.test_migrations_no_init" + ) as migration_dir: + # Copy the project directory into another place under sys.path. + app_dir = Path(migration_dir).parent + os.remove(app_dir / "__init__.py") + project_dir = app_dir.parent + dest = project_dir.parent / "other_dir_in_path" + shutil.copytree(project_dir, dest) + with extend_sys_path(str(dest)): + call_command("makemigrations", stdout=out) + self.assertEqual("No changes detected\n", out.getvalue()) + def test_makemigrations_migrations_announce(self): """ makemigrations announces the migration at the default verbosity level. diff --git a/tests/migrations/test_operations.py b/tests/migrations/test_operations.py index f865500829..3ac813b899 100644 --- a/tests/migrations/test_operations.py +++ b/tests/migrations/test_operations.py @@ -4107,6 +4107,64 @@ class OperationTests(OperationTestBase): definition[2], {"model_name": "Pony", "constraint": gt_constraint} ) + @skipUnlessDBFeature("supports_table_check_constraints") + def test_create_model_constraint_percent_escaping(self): + app_label = "add_constraint_string_quoting" + from_state = ProjectState() + checks = [ + # "%" generated in startswith lookup should be escaped in a way + # that is considered a leading wildcard. + ( + models.Q(name__startswith="Albert"), + {"name": "Alberta"}, + {"name": "Artur"}, + ), + # Literal "%" should be escaped in a way that is not a considered a + # wildcard. + (models.Q(rebate__endswith="%"), {"rebate": "10%"}, {"rebate": "10%$"}), + # Right-hand-side baked "%" literals should not be used for + # parameters interpolation. + ( + ~models.Q(surname__startswith=models.F("name")), + {"name": "Albert"}, + {"name": "Albert", "surname": "Alberto"}, + ), + # Exact matches against "%" literals should also be supported. + ( + models.Q(name="%"), + {"name": "%"}, + {"name": "Albert"}, + ), + ] + for check, valid, invalid in checks: + with self.subTest(condition=check, valid=valid, invalid=invalid): + constraint = models.CheckConstraint(condition=check, name="constraint") + operation = migrations.CreateModel( + "Author", + fields=[ + ("id", models.AutoField(primary_key=True)), + ("name", models.CharField(max_length=100)), + ("surname", models.CharField(max_length=100, db_default="")), + ("rebate", models.CharField(max_length=100)), + ], + options={"constraints": [constraint]}, + ) + to_state = from_state.clone() + operation.state_forwards(app_label, to_state) + with connection.schema_editor() as editor: + operation.database_forwards(app_label, editor, from_state, to_state) + Author = to_state.apps.get_model(app_label, "Author") + try: + with transaction.atomic(): + Author.objects.create(**valid).delete() + with self.assertRaises(IntegrityError), transaction.atomic(): + Author.objects.create(**invalid) + finally: + with connection.schema_editor() as editor: + migrations.DeleteModel("Author").database_forwards( + app_label, editor, to_state, from_state + ) + @skipUnlessDBFeature("supports_table_check_constraints") def test_add_constraint_percent_escaping(self): app_label = "add_constraint_string_quoting" diff --git a/tests/migrations/test_optimizer.py b/tests/migrations/test_optimizer.py index 2acbc7f09f..0a40b50edc 100644 --- a/tests/migrations/test_optimizer.py +++ b/tests/migrations/test_optimizer.py @@ -1,49 +1,17 @@ from django.db import migrations, models from django.db.migrations import operations from django.db.migrations.optimizer import MigrationOptimizer -from django.db.migrations.serializer import serializer_factory from django.db.models.functions import Abs -from django.test import SimpleTestCase from .models import EmptyManager, UnicodeModel +from .test_base import OptimizerTestBase -class OptimizerTests(SimpleTestCase): +class OptimizerTests(OptimizerTestBase): """ - Tests the migration autodetector. + Tests the migration optimizer. """ - def optimize(self, operations, app_label): - """ - Handy shortcut for getting results + number of loops - """ - optimizer = MigrationOptimizer() - return optimizer.optimize(operations, app_label), optimizer._iterations - - def serialize(self, value): - return serializer_factory(value).serialize()[0] - - def assertOptimizesTo( - self, operations, expected, exact=None, less_than=None, app_label=None - ): - result, iterations = self.optimize(operations, app_label or "migrations") - result = [self.serialize(f) for f in result] - expected = [self.serialize(f) for f in expected] - self.assertEqual(expected, result) - if exact is not None and iterations != exact: - raise self.failureException( - "Optimization did not take exactly %s iterations (it took %s)" - % (exact, iterations) - ) - if less_than is not None and iterations >= less_than: - raise self.failureException( - "Optimization did not take less than %s iterations (it took %s)" - % (less_than, iterations) - ) - - def assertDoesNotOptimize(self, operations, **kwargs): - self.assertOptimizesTo(operations, operations, **kwargs) - def test_none_app_label(self): optimizer = MigrationOptimizer() with self.assertRaisesMessage(TypeError, "app_label must be a str"): @@ -154,6 +122,46 @@ class OptimizerTests(SimpleTestCase): ], ) + def test_create_alter_model_table(self): + self.assertOptimizesTo( + [ + migrations.CreateModel("Foo", fields=[]), + migrations.AlterModelTable( + name="foo", + table="foo", + ), + ], + [ + migrations.CreateModel( + "Foo", + fields=[], + options={ + "db_table": "foo", + }, + ), + ], + ) + + def test_create_alter_model_table_comment(self): + self.assertOptimizesTo( + [ + migrations.CreateModel("Foo", fields=[]), + migrations.AlterModelTableComment( + name="foo", + table_comment="A lovely table.", + ), + ], + [ + migrations.CreateModel( + "Foo", + fields=[], + options={ + "db_table_comment": "A lovely table.", + }, + ), + ], + ) + def test_create_model_and_remove_model_options(self): self.assertOptimizesTo( [ diff --git a/tests/migrations/test_writer.py b/tests/migrations/test_writer.py index 891efd8ac7..51783b7346 100644 --- a/tests/migrations/test_writer.py +++ b/tests/migrations/test_writer.py @@ -22,7 +22,8 @@ from django.core.validators import EmailValidator, RegexValidator from django.db import migrations, models from django.db.migrations.serializer import BaseSerializer from django.db.migrations.writer import MigrationWriter, OperationWriter -from django.test import SimpleTestCase +from django.test import SimpleTestCase, override_settings +from django.test.utils import extend_sys_path from django.utils.deconstruct import deconstructible from django.utils.functional import SimpleLazyObject from django.utils.timezone import get_default_timezone, get_fixed_timezone @@ -954,6 +955,29 @@ class WriterTests(SimpleTestCase): writer = MigrationWriter(migration) self.assertEqual(writer.path, expected_path) + @override_settings( + MIGRATION_MODULES={"namespace_app": "namespace_app.migrations"}, + INSTALLED_APPS=[ + "migrations.migrations_test_apps.distributed_app_location_2.namespace_app" + ], + ) + def test_migration_path_distributed_namespace(self): + base_dir = os.path.dirname(os.path.dirname(__file__)) + test_apps_dir = os.path.join(base_dir, "migrations", "migrations_test_apps") + expected_msg = ( + "Could not locate an appropriate location to create " + "migrations package namespace_app.migrations. Make sure the toplevel " + "package exists and can be imported." + ) + with extend_sys_path( + os.path.join(test_apps_dir, "distributed_app_location_1"), + os.path.join(test_apps_dir, "distributed_app_location_2"), + ): + migration = migrations.Migration("0001_initial", "namespace_app") + writer = MigrationWriter(migration) + with self.assertRaisesMessage(ValueError, expected_msg): + writer.path + def test_custom_operation(self): migration = type( "Migration", diff --git a/tests/model_enums/tests.py b/tests/model_enums/tests.py index 306bfc8d67..ee9dc369f6 100644 --- a/tests/model_enums/tests.py +++ b/tests/model_enums/tests.py @@ -328,5 +328,6 @@ class ChoicesMetaDeprecationTests(SimpleTestCase): from django.db.models import enums msg = "ChoicesMeta is deprecated in favor of ChoicesType." - with self.assertWarnsMessage(RemovedInDjango60Warning, msg): + with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx: enums.ChoicesMeta + self.assertEqual(ctx.filename, __file__) diff --git a/tests/model_fields/models.py b/tests/model_fields/models.py index 5dfed00329..599efafe7e 100644 --- a/tests/model_fields/models.py +++ b/tests/model_fields/models.py @@ -21,6 +21,10 @@ except ImportError: Image = None +# Set up a temp directory for file storage. +temp_storage_dir = tempfile.mkdtemp() +temp_storage = FileSystemStorage(temp_storage_dir) + test_collation = SimpleLazyObject( lambda: connection.features.test_collations["virtual"] ) @@ -206,7 +210,9 @@ class VerboseNameField(models.Model): field5 = models.DateTimeField("verbose field5") field6 = models.DecimalField("verbose field6", max_digits=6, decimal_places=1) field7 = models.EmailField("verbose field7") - field8 = models.FileField("verbose field8", upload_to="unused") + field8 = models.FileField( + "verbose field8", storage=temp_storage, upload_to="unused" + ) field9 = models.FilePathField("verbose field9") field10 = models.FloatField("verbose field10") # Don't want to depend on Pillow in this test @@ -256,7 +262,7 @@ class DataModel(models.Model): class Document(models.Model): - myfile = models.FileField(upload_to="unused", unique=True) + myfile = models.FileField(storage=temp_storage, upload_to="unused", unique=True) ############################################################################### @@ -282,10 +288,6 @@ if Image: class TestImageField(models.ImageField): attr_class = TestImageFieldFile - # Set up a temp directory for file storage. - temp_storage_dir = tempfile.mkdtemp() - temp_storage = FileSystemStorage(temp_storage_dir) - class Person(models.Model): """ Model that defines an ImageField with no dimension fields. diff --git a/tests/model_fields/test_mixins.py b/tests/model_fields/test_mixins.py index 5ccfac4d78..8847f25987 100644 --- a/tests/model_fields/test_mixins.py +++ b/tests/model_fields/test_mixins.py @@ -34,9 +34,10 @@ class FieldCacheMixinTests(SimpleTestCase): # RemovedInDjango60Warning. def test_get_cache_name_deprecated(self): msg = "Override ExampleOld.cache_name instead of get_cache_name()." - with self.assertWarnsMessage(RemovedInDjango60Warning, msg): + with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx: result = ExampleOld().cache_name self.assertEqual(result, "example") + self.assertEqual(ctx.filename, __file__) def test_cache_name(self): result = Example().cache_name diff --git a/tests/model_fields/tests.py b/tests/model_fields/tests.py index 36e54d4b8b..3d856d36c5 100644 --- a/tests/model_fields/tests.py +++ b/tests/model_fields/tests.py @@ -183,6 +183,33 @@ class ChoicesTests(SimpleTestCase): self.choices_from_callable.choices.func(), [(0, "0"), (1, "1"), (2, "2")] ) + def test_choices_slice(self): + for choices, expected_slice in [ + (self.empty_choices.choices, []), + (self.empty_choices_bool.choices, []), + (self.empty_choices_text.choices, []), + (self.with_choices.choices, [(1, "A")]), + (self.with_choices_dict.choices, [(1, "A")]), + (self.with_choices_nested_dict.choices, [("Thing", [(1, "A")])]), + (self.choices_from_iterator.choices, [(0, "0"), (1, "1")]), + (self.choices_from_callable.choices.func(), [(0, "0"), (1, "1")]), + (self.choices_from_callable.choices, [(0, "0"), (1, "1")]), + ]: + with self.subTest(choices=choices): + self.assertEqual(choices[:2], expected_slice) + + def test_choices_negative_index(self): + for choices, expected_choice in [ + (self.with_choices.choices, (1, "A")), + (self.with_choices_dict.choices, (1, "A")), + (self.with_choices_nested_dict.choices, ("Thing", [(1, "A")])), + (self.choices_from_iterator.choices, (2, "2")), + (self.choices_from_callable.choices.func(), (2, "2")), + (self.choices_from_callable.choices, (2, "2")), + ]: + with self.subTest(choices=choices): + self.assertEqual(choices[-1], expected_choice) + def test_flatchoices(self): self.assertEqual(self.no_choices.flatchoices, []) self.assertEqual(self.empty_choices.flatchoices, []) diff --git a/tests/modeladmin/test_checks.py b/tests/modeladmin/test_checks.py index f767a6c92b..94a80ca006 100644 --- a/tests/modeladmin/test_checks.py +++ b/tests/modeladmin/test_checks.py @@ -4,16 +4,17 @@ from django.contrib.admin import BooleanFieldListFilter, SimpleListFilter from django.contrib.admin.options import VERTICAL, ModelAdmin, TabularInline from django.contrib.admin.sites import AdminSite from django.core.checks import Error +from django.db import models from django.db.models import CASCADE, F, Field, ForeignKey, ManyToManyField, Model from django.db.models.functions import Upper from django.forms.models import BaseModelFormSet -from django.test import SimpleTestCase +from django.test import TestCase, skipUnlessDBFeature from django.test.utils import isolate_apps from .models import Band, Song, User, ValidationTestInlineModel, ValidationTestModel -class CheckTestCase(SimpleTestCase): +class CheckTestCase(TestCase): def assertIsInvalid( self, model_admin, @@ -97,6 +98,29 @@ class RawIdCheckTests(CheckTestCase): self.assertIsValid(TestModelAdmin, ValidationTestModel) + @isolate_apps("modeladmin") + def assertGeneratedDateTimeFieldIsValid(self, *, db_persist): + class TestModel(Model): + date = models.DateTimeField() + date_copy = models.GeneratedField( + expression=F("date"), + output_field=models.DateTimeField(), + db_persist=db_persist, + ) + + class TestModelAdmin(ModelAdmin): + date_hierarchy = "date_copy" + + self.assertIsValid(TestModelAdmin, TestModel) + + @skipUnlessDBFeature("supports_stored_generated_columns") + def test_valid_case_stored_generated_field(self): + self.assertGeneratedDateTimeFieldIsValid(db_persist=True) + + @skipUnlessDBFeature("supports_virtual_generated_columns") + def test_valid_case_virtual_generated_field(self): + self.assertGeneratedDateTimeFieldIsValid(db_persist=False) + def test_field_attname(self): class TestModelAdmin(ModelAdmin): raw_id_fields = ["band_id"] @@ -1029,6 +1053,33 @@ class DateHierarchyCheckTests(CheckTestCase): "admin.E128", ) + @isolate_apps("modeladmin") + def assertGeneratedIntegerFieldIsInvalid(self, *, db_persist): + class TestModel(Model): + generated = models.GeneratedField( + expression=models.Value(1), + output_field=models.IntegerField(), + db_persist=db_persist, + ) + + class TestModelAdmin(ModelAdmin): + date_hierarchy = "generated" + + self.assertIsInvalid( + TestModelAdmin, + TestModel, + "The value of 'date_hierarchy' must be a DateField or DateTimeField.", + "admin.E128", + ) + + @skipUnlessDBFeature("supports_stored_generated_columns") + def test_related_invalid_field_type_stored_generated_field(self): + self.assertGeneratedIntegerFieldIsInvalid(db_persist=True) + + @skipUnlessDBFeature("supports_virtual_generated_columns") + def test_related_invalid_field_type_virtual_generated_field(self): + self.assertGeneratedIntegerFieldIsInvalid(db_persist=False) + def test_valid_case(self): class TestModelAdmin(ModelAdmin): date_hierarchy = "pub_date" diff --git a/tests/modeladmin/tests.py b/tests/modeladmin/tests.py index e8b59ed0bf..062368d94e 100644 --- a/tests/modeladmin/tests.py +++ b/tests/modeladmin/tests.py @@ -928,8 +928,9 @@ class ModelAdminTests(TestCase): mock_request.user = User.objects.create(username="bill") content_type = get_content_type_for_model(self.band) msg = "ModelAdmin.log_deletion() is deprecated. Use log_deletions() instead." - with self.assertWarnsMessage(RemovedInDjango60Warning, msg): + with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx: created = ma.log_deletion(mock_request, self.band, str(self.band)) + self.assertEqual(ctx.filename, __file__) fetched = LogEntry.objects.filter(action_flag=DELETION).latest("id") self.assertEqual(created, fetched) self.assertEqual(fetched.action_flag, DELETION) @@ -966,8 +967,9 @@ class ModelAdminTests(TestCase): "instead." ) with self.assertNumQueries(3): - with self.assertWarnsMessage(RemovedInDjango60Warning, msg): + with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx: ima.log_deletions(mock_request, queryset) + self.assertEqual(ctx.filename, __file__) logs = ( LogEntry.objects.filter(action_flag=DELETION) .order_by("id") diff --git a/tests/one_to_one/tests.py b/tests/one_to_one/tests.py index 280a8273fb..0d30dd8f27 100644 --- a/tests/one_to_one/tests.py +++ b/tests/one_to_one/tests.py @@ -612,8 +612,9 @@ class OneToOneTests(TestCase): "get_prefetch_queryset() is deprecated. Use get_prefetch_querysets() " "instead." ) - with self.assertWarnsMessage(RemovedInDjango60Warning, msg): + with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx: Place.bar.get_prefetch_queryset(places) + self.assertEqual(ctx.filename, __file__) def test_get_prefetch_querysets_invalid_querysets_length(self): places = Place.objects.all() diff --git a/tests/postgres_tests/migrations/0002_create_test_models.py b/tests/postgres_tests/migrations/0002_create_test_models.py index 5538b436ad..188f79607d 100644 --- a/tests/postgres_tests/migrations/0002_create_test_models.py +++ b/tests/postgres_tests/migrations/0002_create_test_models.py @@ -434,7 +434,7 @@ class Migration(migrations.Migration): primary_key=True, ), ), - ("ints", IntegerRangeField(null=True, blank=True)), + ("ints", IntegerRangeField(null=True, blank=True, db_default=(5, 10))), ("bigints", BigIntegerRangeField(null=True, blank=True)), ("decimals", DecimalRangeField(null=True, blank=True)), ("timestamps", DateTimeRangeField(null=True, blank=True)), diff --git a/tests/postgres_tests/models.py b/tests/postgres_tests/models.py index a97894e327..e3118bc590 100644 --- a/tests/postgres_tests/models.py +++ b/tests/postgres_tests/models.py @@ -130,7 +130,7 @@ class LineSavedSearch(PostgreSQLModel): class RangesModel(PostgreSQLModel): - ints = IntegerRangeField(blank=True, null=True) + ints = IntegerRangeField(blank=True, null=True, db_default=(5, 10)) bigints = BigIntegerRangeField(blank=True, null=True) decimals = DecimalRangeField(blank=True, null=True) timestamps = DateTimeRangeField(blank=True, null=True) 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_constraints.py b/tests/postgres_tests/test_constraints.py index 770d4b1702..ab5bf2bab1 100644 --- a/tests/postgres_tests/test_constraints.py +++ b/tests/postgres_tests/test_constraints.py @@ -14,6 +14,7 @@ from django.db.models import ( F, ForeignKey, Func, + GeneratedField, IntegerField, Model, Q, @@ -32,6 +33,7 @@ try: from django.contrib.postgres.constraints import ExclusionConstraint from django.contrib.postgres.fields import ( DateTimeRangeField, + IntegerRangeField, RangeBoundary, RangeOperators, ) @@ -866,6 +868,38 @@ class ExclusionConstraintTests(PostgreSQLTestCase): constraint.validate(RangesModel, RangesModel(ints=(51, 60))) constraint.validate(RangesModel, RangesModel(ints=(10, 20)), exclude={"ints"}) + @skipUnlessDBFeature("supports_stored_generated_columns") + @isolate_apps("postgres_tests") + def test_validate_generated_field_range_adjacent(self): + class RangesModelGeneratedField(Model): + ints = IntegerRangeField(blank=True, null=True) + ints_generated = GeneratedField( + expression=F("ints"), + output_field=IntegerRangeField(null=True), + db_persist=True, + ) + + with connection.schema_editor() as editor: + editor.create_model(RangesModelGeneratedField) + + constraint = ExclusionConstraint( + name="ints_adjacent", + expressions=[("ints_generated", RangeOperators.ADJACENT_TO)], + violation_error_code="custom_code", + violation_error_message="Custom error message.", + ) + RangesModelGeneratedField.objects.create(ints=(20, 50)) + + range_obj = RangesModelGeneratedField(ints=(3, 20)) + with self.assertRaisesMessage(ValidationError, "Custom error message."): + constraint.validate(RangesModelGeneratedField, range_obj) + + # Excluding referenced or generated field should skip validation. + constraint.validate(RangesModelGeneratedField, range_obj, exclude={"ints"}) + constraint.validate( + RangesModelGeneratedField, range_obj, exclude={"ints_generated"} + ) + def test_validate_with_custom_code_and_condition(self): constraint = ExclusionConstraint( name="ints_adjacent", @@ -1213,3 +1247,12 @@ class ExclusionConstraintTests(PostgreSQLTestCase): constraint_name, self.get_constraints(ModelWithExclusionConstraint._meta.db_table), ) + + def test_database_default(self): + constraint = ExclusionConstraint( + name="ints_equal", expressions=[("ints", RangeOperators.EQUAL)] + ) + RangesModel.objects.create() + msg = "Constraint “ints_equal” is violated." + with self.assertRaisesMessage(ValidationError, msg): + constraint.validate(RangesModel, RangesModel()) diff --git a/tests/postgres_tests/test_operations.py b/tests/postgres_tests/test_operations.py index 5780348251..f344d4ae74 100644 --- a/tests/postgres_tests/test_operations.py +++ b/tests/postgres_tests/test_operations.py @@ -1,8 +1,9 @@ import unittest -from migrations.test_base import OperationTestBase +from migrations.test_base import OperationTestBase, OptimizerTestBase from django.db import IntegrityError, NotSupportedError, connection, transaction +from django.db.migrations.operations import RemoveIndex, RenameIndex from django.db.migrations.state import ProjectState from django.db.migrations.writer import OperationWriter from django.db.models import CheckConstraint, Index, Q, UniqueConstraint @@ -30,7 +31,7 @@ except ImportError: @unittest.skipUnless(connection.vendor == "postgresql", "PostgreSQL specific tests.") @modify_settings(INSTALLED_APPS={"append": "migrations"}) -class AddIndexConcurrentlyTests(OperationTestBase): +class AddIndexConcurrentlyTests(OptimizerTestBase, OperationTestBase): app_label = "test_add_concurrently" def test_requires_atomic_false(self): @@ -129,6 +130,51 @@ class AddIndexConcurrentlyTests(OperationTestBase): ) self.assertIndexNotExists(table_name, ["pink"]) + def test_reduce_add_remove_concurrently(self): + self.assertOptimizesTo( + [ + AddIndexConcurrently( + "Pony", + Index(fields=["pink"], name="pony_pink_idx"), + ), + RemoveIndex("Pony", "pony_pink_idx"), + ], + [], + ) + + def test_reduce_add_remove(self): + self.assertOptimizesTo( + [ + AddIndexConcurrently( + "Pony", + Index(fields=["pink"], name="pony_pink_idx"), + ), + RemoveIndexConcurrently("Pony", "pony_pink_idx"), + ], + [], + ) + + def test_reduce_add_rename(self): + self.assertOptimizesTo( + [ + AddIndexConcurrently( + "Pony", + Index(fields=["pink"], name="pony_pink_idx"), + ), + RenameIndex( + "Pony", + old_name="pony_pink_idx", + new_name="pony_pink_index", + ), + ], + [ + AddIndexConcurrently( + "Pony", + Index(fields=["pink"], name="pony_pink_index"), + ), + ], + ) + @unittest.skipUnless(connection.vendor == "postgresql", "PostgreSQL specific tests.") @modify_settings(INSTALLED_APPS={"append": "migrations"}) diff --git a/tests/postgres_tests/test_trigram.py b/tests/postgres_tests/test_trigram.py index 812403a324..b6c88c38a6 100644 --- a/tests/postgres_tests/test_trigram.py +++ b/tests/postgres_tests/test_trigram.py @@ -1,3 +1,6 @@ +from django.db.models import F, Value +from django.db.models.functions import Concat + from . import PostgreSQLTestCase from .models import CharFieldModel, TextFieldModel @@ -149,6 +152,21 @@ class TrigramTest(PostgreSQLTestCase): ], ) + def test_trigram_concat_precedence(self): + search_term = "im matthew" + self.assertSequenceEqual( + self.Model.objects.annotate( + concat_result=Concat( + Value("I'm "), + F("field"), + output_field=self.Model._meta.get_field("field"), + ), + ) + .filter(concat_result__trigram_similar=search_term) + .values("field"), + [{"field": "Matthew"}], + ) + class TrigramTextFieldTest(TrigramTest): """ diff --git a/tests/prefetch_related/tests.py b/tests/prefetch_related/tests.py index a418beb5a5..856f766d30 100644 --- a/tests/prefetch_related/tests.py +++ b/tests/prefetch_related/tests.py @@ -1999,6 +1999,21 @@ class PrefetchLimitTests(TestDataMixin, TestCase): with self.assertRaisesMessage(NotSupportedError, msg): list(Book.objects.prefetch_related(Prefetch("authors", authors[1:]))) + @skipUnlessDBFeature("supports_over_clause") + def test_empty_order(self): + authors = Author.objects.order_by() + with self.assertNumQueries(3): + books = list( + Book.objects.prefetch_related( + Prefetch("authors", authors), + Prefetch("authors", authors[:1], to_attr="authors_sliced"), + ) + ) + for book in books: + with self.subTest(book=book): + self.assertEqual(len(book.authors_sliced), 1) + self.assertIn(book.authors_sliced[0], list(book.authors.all())) + class DeprecationTests(TestCase): def test_get_current_queryset_warning(self): @@ -2007,13 +2022,15 @@ class DeprecationTests(TestCase): "get_current_querysets() instead." ) authors = Author.objects.all() - with self.assertWarnsMessage(RemovedInDjango60Warning, msg): + with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx: self.assertEqual( Prefetch("authors", authors).get_current_queryset(1), authors, ) - with self.assertWarnsMessage(RemovedInDjango60Warning, msg): + self.assertEqual(ctx.filename, __file__) + with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx: self.assertIsNone(Prefetch("authors").get_current_queryset(1)) + self.assertEqual(ctx.filename, __file__) @ignore_warnings(category=RemovedInDjango60Warning) def test_prefetch_one_level_fallback(self): diff --git a/tests/queries/test_explain.py b/tests/queries/test_explain.py index 44689aedf8..67440cb502 100644 --- a/tests/queries/test_explain.py +++ b/tests/queries/test_explain.py @@ -19,8 +19,11 @@ class ExplainTests(TestCase): Tag.objects.filter(name="test").prefetch_related("children"), Tag.objects.filter(name="test").annotate(Count("children")), Tag.objects.filter(name="test").values_list("name"), - Tag.objects.order_by().union(Tag.objects.order_by().filter(name="test")), ] + if connection.features.supports_select_union: + querysets.append( + Tag.objects.order_by().union(Tag.objects.order_by().filter(name="test")) + ) if connection.features.has_select_for_update: querysets.append(Tag.objects.select_for_update().filter(name="test")) supported_formats = connection.features.supported_explain_formats @@ -96,6 +99,7 @@ class ExplainTests(TestCase): option = "{} {}".format(name.upper(), "true" if value else "false") self.assertIn(option, captured_queries[0]["sql"]) + @skipUnlessDBFeature("supports_select_union") def test_multi_page_text_explain(self): if "TEXT" not in connection.features.supported_explain_formats: self.skipTest("This backend does not support TEXT format.") diff --git a/tests/queries/test_q.py b/tests/queries/test_q.py index f7192a430a..f37d7becac 100644 --- a/tests/queries/test_q.py +++ b/tests/queries/test_q.py @@ -1,4 +1,5 @@ from django.core.exceptions import FieldError +from django.db import connection from django.db.models import ( BooleanField, Exists, @@ -327,3 +328,6 @@ class QCheckTests(TestCase): f"Got a database error calling check() on {q!r}: ", cm.records[0].getMessage(), ) + + # We must leave the connection in a usable state (#35712). + self.assertTrue(connection.is_usable()) diff --git a/tests/queries/tests.py b/tests/queries/tests.py index ec88fa558d..45866fd50f 100644 --- a/tests/queries/tests.py +++ b/tests/queries/tests.py @@ -1375,6 +1375,7 @@ class Queries1Tests(TestCase): self.assertCountEqual(items_after, [self.i2, self.i3, self.i4]) self.assertCountEqual(items_before, items_after) + @skipUnlessDBFeature("supports_select_union") def test_union_values_subquery(self): items = Item.objects.filter(creator=OuterRef("pk")) item_authors = Author.objects.annotate(is_creator=Exists(items)).order_by() diff --git a/tests/requests_tests/test_accept_header.py b/tests/requests_tests/test_accept_header.py index 5afb9e9993..6585fec678 100644 --- a/tests/requests_tests/test_accept_header.py +++ b/tests/requests_tests/test_accept_header.py @@ -56,6 +56,35 @@ class MediaTypeTests(TestCase): with self.subTest(accepted_type, mime_type=mime_type): self.assertIs(MediaType(accepted_type).match(mime_type), False) + def test_quality(self): + tests = [ + ("*/*; q=0.8", 0.8), + ("*/*; q=0.0001", 0), + ("*/*; q=0.12345", 0.123), + ("*/*; q=0.1", 0.1), + ("*/*; q=-1", 1), + ("*/*; q=2", 1), + ("*/*; q=h", 1), + ("*/*", 1), + ] + for accepted_type, quality in tests: + with self.subTest(accepted_type, quality=quality): + self.assertEqual(MediaType(accepted_type).quality, quality) + + def test_specificity(self): + tests = [ + ("*/*", 0), + ("*/*;q=0.5", 0), + ("text/*", 1), + ("text/*;q=0.5", 1), + ("text/html", 2), + ("text/html;q=1", 2), + ("text/html;q=0.5", 3), + ] + for accepted_type, specificity in tests: + with self.subTest(accepted_type, specificity=specificity): + self.assertEqual(MediaType(accepted_type).specificity, specificity) + class AcceptHeaderTests(TestCase): def test_no_headers(self): @@ -69,13 +98,14 @@ class AcceptHeaderTests(TestCase): def test_accept_headers(self): request = HttpRequest() request.META["HTTP_ACCEPT"] = ( - "text/html, application/xhtml+xml,application/xml ;q=0.9,*/*;q=0.8" + "text/*,text/html, application/xhtml+xml,application/xml ;q=0.9,*/*;q=0.8," ) self.assertEqual( [str(accepted_type) for accepted_type in request.accepted_types], [ "text/html", "application/xhtml+xml", + "text/*", "application/xml; q=0.9", "*/*; q=0.8", ], @@ -85,12 +115,20 @@ class AcceptHeaderTests(TestCase): request = HttpRequest() request.META["HTTP_ACCEPT"] = "*/*" self.assertIs(request.accepts("application/json"), True) + self.assertIsNone(request.get_preferred_type([])) + self.assertEqual( + request.get_preferred_type(["application/json", "text/plain"]), + "application/json", + ) def test_request_accepts_none(self): request = HttpRequest() request.META["HTTP_ACCEPT"] = "" self.assertIs(request.accepts("application/json"), False) self.assertEqual(request.accepted_types, []) + self.assertIsNone( + request.get_preferred_type(["application/json", "text/plain"]) + ) def test_request_accepts_some(self): request = HttpRequest() @@ -101,3 +139,39 @@ class AcceptHeaderTests(TestCase): self.assertIs(request.accepts("application/xhtml+xml"), True) self.assertIs(request.accepts("application/xml"), True) self.assertIs(request.accepts("application/json"), False) + + def test_accept_header_priority(self): + request = HttpRequest() + request.META["HTTP_ACCEPT"] = ( + "text/html,application/xml;q=0.9,*/*;q=0.1,text/*;q=0.5" + ) + + tests = [ + (["text/html", "application/xml"], "text/html"), + (["application/xml", "application/json"], "application/xml"), + (["application/json"], "application/json"), + (["application/json", "text/plain"], "text/plain"), + ] + for types, preferred_type in tests: + with self.subTest(types, preferred_type=preferred_type): + self.assertEqual(str(request.get_preferred_type(types)), preferred_type) + + def test_accept_header_priority_overlapping_mime(self): + request = HttpRequest() + request.META["HTTP_ACCEPT"] = "text/*;q=0.8,text/html;q=0.8" + + self.assertEqual( + [str(accepted_type) for accepted_type in request.accepted_types], + [ + "text/html; q=0.8", + "text/*; q=0.8", + ], + ) + + def test_no_matching_accepted_type(self): + request = HttpRequest() + request.META["HTTP_ACCEPT"] = "text/html" + + self.assertIsNone( + request.get_preferred_type(["application/json", "text/plain"]) + ) diff --git a/tests/requirements/py3.txt b/tests/requirements/py3.txt index d1f3708720..a9679af97c 100644 --- a/tests/requirements/py3.txt +++ b/tests/requirements/py3.txt @@ -1,20 +1,20 @@ aiosmtpd -asgiref >= 3.7.0 -argon2-cffi >= 19.2.0; sys_platform != 'win32' or python_version < '3.13' +asgiref >= 3.8.1 +argon2-cffi >= 19.2.0 bcrypt black docutils >= 0.19 -geoip2; python_version < '3.13' +geoip2 jinja2 >= 2.11.0 -numpy; sys_platform != 'win32' or python_version < '3.13' -Pillow >= 6.2.1; sys_platform != 'win32' or python_version < '3.13' +numpy +Pillow >= 6.2.1 # pylibmc/libmemcached can't be built on Windows. pylibmc; sys_platform != 'win32' pymemcache >= 3.4.0 pywatchman; sys_platform != 'win32' PyYAML redis >= 3.4.0 -selenium >= 4.8.0; sys_platform != 'win32' or python_version < '3.13' +selenium >= 4.8.0 sqlparse >= 0.3.1 tblib >= 1.5.0 tzdata 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/schema/tests.py b/tests/schema/tests.py index 3a2947cf43..33a4bc527b 100644 --- a/tests/schema/tests.py +++ b/tests/schema/tests.py @@ -2160,6 +2160,19 @@ class SchemaTests(TransactionTestCase): with connection.schema_editor() as editor: editor.alter_field(SmallIntegerPK, old_field, new_field, strict=True) + # A model representing the updated model. + class IntegerPKToSmallAutoField(Model): + i = SmallAutoField(primary_key=True) + + class Meta: + app_label = "schema" + apps = new_apps + db_table = SmallIntegerPK._meta.db_table + + # An id (i) is generated by the database. + obj = IntegerPKToSmallAutoField.objects.create() + self.assertIsNotNone(obj.i) + @isolate_apps("schema") @unittest.skipUnless(connection.vendor == "postgresql", "PostgreSQL specific") def test_alter_serial_auto_field_to_bigautofield(self): diff --git a/tests/serializers/test_deserialization.py b/tests/serializers/test_deserialization.py new file mode 100644 index 0000000000..0bbb46b7ce --- /dev/null +++ b/tests/serializers/test_deserialization.py @@ -0,0 +1,135 @@ +import json +import unittest + +from django.core.serializers.base import DeserializationError, DeserializedObject +from django.core.serializers.json import Deserializer as JsonDeserializer +from django.core.serializers.jsonl import Deserializer as JsonlDeserializer +from django.core.serializers.python import Deserializer +from django.test import SimpleTestCase + +from .models import Author + +try: + import yaml # NOQA + + HAS_YAML = True +except ImportError: + HAS_YAML = False + + +class TestDeserializer(SimpleTestCase): + def setUp(self): + self.object_list = [ + {"pk": 1, "model": "serializers.author", "fields": {"name": "Jane"}}, + {"pk": 2, "model": "serializers.author", "fields": {"name": "Joe"}}, + ] + self.deserializer = Deserializer(self.object_list) + self.jane = Author(name="Jane", pk=1) + self.joe = Author(name="Joe", pk=2) + + def test_deserialized_object_repr(self): + deserial_obj = DeserializedObject(obj=self.jane) + self.assertEqual( + repr(deserial_obj), "" + ) + + def test_next_functionality(self): + first_item = next(self.deserializer) + + self.assertEqual(first_item.object, self.jane) + + second_item = next(self.deserializer) + self.assertEqual(second_item.object, self.joe) + + with self.assertRaises(StopIteration): + next(self.deserializer) + + def test_invalid_model_identifier(self): + invalid_object_list = [ + {"pk": 1, "model": "serializers.author2", "fields": {"name": "Jane"}} + ] + self.deserializer = Deserializer(invalid_object_list) + with self.assertRaises(DeserializationError): + next(self.deserializer) + + deserializer = Deserializer(object_list=[]) + with self.assertRaises(StopIteration): + next(deserializer) + + def test_custom_deserializer(self): + class CustomDeserializer(Deserializer): + @staticmethod + def _get_model_from_node(model_identifier): + return Author + + deserializer = CustomDeserializer(self.object_list) + result = next(iter(deserializer)) + deserialized_object = result.object + self.assertEqual( + self.jane, + deserialized_object, + ) + + def test_empty_object_list(self): + deserializer = Deserializer(object_list=[]) + with self.assertRaises(StopIteration): + next(deserializer) + + def test_json_bytes_input(self): + test_string = json.dumps(self.object_list) + stream = test_string.encode("utf-8") + deserializer = JsonDeserializer(stream_or_string=stream) + + first_item = next(deserializer) + second_item = next(deserializer) + + self.assertEqual(first_item.object, self.jane) + self.assertEqual(second_item.object, self.joe) + + def test_jsonl_bytes_input(self): + test_string = """ + {"pk": 1, "model": "serializers.author", "fields": {"name": "Jane"}} + {"pk": 2, "model": "serializers.author", "fields": {"name": "Joe"}} + {"pk": 3, "model": "serializers.author", "fields": {"name": "John"}} + {"pk": 4, "model": "serializers.author", "fields": {"name": "Smith"}}""" + stream = test_string.encode("utf-8") + deserializer = JsonlDeserializer(stream_or_string=stream) + + first_item = next(deserializer) + second_item = next(deserializer) + + self.assertEqual(first_item.object, self.jane) + self.assertEqual(second_item.object, self.joe) + + @unittest.skipUnless(HAS_YAML, "No yaml library detected") + def test_yaml_bytes_input(self): + from django.core.serializers.pyyaml import Deserializer as YamlDeserializer + + test_string = """- pk: 1 + model: serializers.author + fields: + name: Jane + +- pk: 2 + model: serializers.author + fields: + name: Joe + +- pk: 3 + model: serializers.author + fields: + name: John + +- pk: 4 + model: serializers.author + fields: + name: Smith +""" + stream = test_string.encode("utf-8") + deserializer = YamlDeserializer(stream_or_string=stream) + + first_item = next(deserializer) + second_item = next(deserializer) + + self.assertEqual(first_item.object, self.jane) + self.assertEqual(second_item.object, self.joe) diff --git a/tests/serializers/test_deserializedobject.py b/tests/serializers/test_deserializedobject.py deleted file mode 100644 index 1252052100..0000000000 --- a/tests/serializers/test_deserializedobject.py +++ /dev/null @@ -1,13 +0,0 @@ -from django.core.serializers.base import DeserializedObject -from django.test import SimpleTestCase - -from .models import Author - - -class TestDeserializedObjectTests(SimpleTestCase): - def test_repr(self): - author = Author(name="John", pk=1) - deserial_obj = DeserializedObject(obj=author) - self.assertEqual( - repr(deserial_obj), "" - ) diff --git a/tests/staticfiles_tests/project/documents/cached/module.js b/tests/staticfiles_tests/project/documents/cached/module.js index 7764e740d6..c56530aea6 100644 --- a/tests/staticfiles_tests/project/documents/cached/module.js +++ b/tests/staticfiles_tests/project/documents/cached/module.js @@ -2,6 +2,10 @@ import rootConst from "/static/absolute_root.js"; import testConst from "./module_test.js"; import * as NewModule from "./module_test.js"; +import*as m from "./module_test.js"; +import *as m from "./module_test.js"; +import* as m from "./module_test.js"; +import* as m from "./module_test.js"; import { testConst as alias } from "./module_test.js"; import { firstConst, secondConst } from "./module_test.js"; import { diff --git a/tests/staticfiles_tests/project/loop/baz.css b/tests/staticfiles_tests/project/loop/baz.css new file mode 100644 index 0000000000..4021a1b1e6 --- /dev/null +++ b/tests/staticfiles_tests/project/loop/baz.css @@ -0,0 +1,3 @@ +body { + background-color: #fafafa; +} diff --git a/tests/staticfiles_tests/test_finders.py b/tests/staticfiles_tests/test_finders.py index ddae508c5c..2f1863a1d4 100644 --- a/tests/staticfiles_tests/test_finders.py +++ b/tests/staticfiles_tests/test_finders.py @@ -37,11 +37,12 @@ class TestFinders: def test_find_all_deprecated_param(self): src, dst = self.find_all - with self.assertWarnsMessage(RemovedInDjango61Warning, DEPRECATION_MSG): + with self.assertWarnsMessage(RemovedInDjango61Warning, DEPRECATION_MSG) as ctx: found = self.finder.find(src, all=True) found = [os.path.normcase(f) for f in found] dst = [os.path.normcase(d) for d in dst] self.assertEqual(found, dst) + self.assertEqual(ctx.filename, __file__) def test_find_all_conflicting_params(self): src, dst = self.find_all @@ -50,10 +51,11 @@ class TestFinders: "argument 'find_all'" ) with ( - self.assertWarnsMessage(RemovedInDjango61Warning, DEPRECATION_MSG), + self.assertWarnsMessage(RemovedInDjango61Warning, DEPRECATION_MSG) as ctx, self.assertRaisesMessage(TypeError, msg), ): self.finder.find(src, find_all=True, all=True) + self.assertEqual(ctx.filename, __file__) def test_find_all_unexpected_params(self): src, dst = self.find_all @@ -62,10 +64,11 @@ class TestFinders: "argument 'wrong'" ) with ( - self.assertWarnsMessage(RemovedInDjango61Warning, DEPRECATION_MSG), + self.assertWarnsMessage(RemovedInDjango61Warning, DEPRECATION_MSG) as ctx, self.assertRaisesMessage(TypeError, msg), ): self.finder.find(src, all=True, wrong=1) + self.assertEqual(ctx.filename, __file__) with self.assertRaisesMessage(TypeError, msg): self.finder.find(src, find_all=True, wrong=1) @@ -165,28 +168,31 @@ class TestMiscFinder(SimpleTestCase): ) def test_searched_locations_deprecated_all(self): - with self.assertWarnsMessage(RemovedInDjango61Warning, DEPRECATION_MSG): + with self.assertWarnsMessage(RemovedInDjango61Warning, DEPRECATION_MSG) as ctx: finders.find("spam", all=True) self.assertEqual( finders.searched_locations, [os.path.join(TEST_ROOT, "project", "documents")], ) + self.assertEqual(ctx.filename, __file__) def test_searched_locations_conflicting_params(self): msg = "find() got multiple values for argument 'find_all'" with ( - self.assertWarnsMessage(RemovedInDjango61Warning, DEPRECATION_MSG), + self.assertWarnsMessage(RemovedInDjango61Warning, DEPRECATION_MSG) as ctx, self.assertRaisesMessage(TypeError, msg), ): finders.find("spam", find_all=True, all=True) + self.assertEqual(ctx.filename, __file__) def test_searched_locations_unexpected_params(self): msg = "find() got an unexpected keyword argument 'wrong'" with ( - self.assertWarnsMessage(RemovedInDjango61Warning, DEPRECATION_MSG), + self.assertWarnsMessage(RemovedInDjango61Warning, DEPRECATION_MSG) as ctx, self.assertRaisesMessage(TypeError, msg), ): finders.find("spam", all=True, wrong=1) + self.assertEqual(ctx.filename, __file__) with self.assertRaisesMessage(TypeError, msg): finders.find("spam", find_all=True, wrong=1) diff --git a/tests/staticfiles_tests/test_management.py b/tests/staticfiles_tests/test_management.py index c0d3817383..1b9179af49 100644 --- a/tests/staticfiles_tests/test_management.py +++ b/tests/staticfiles_tests/test_management.py @@ -124,6 +124,11 @@ class TestFindStatic(TestDefaults, CollectionTestCase): searched_locations, ) + def test_missing_args_message(self): + msg = "Enter at least one staticfile." + with self.assertRaisesMessage(CommandError, msg): + call_command("findstatic") + class TestConfiguration(StaticFilesTestCase): def test_location_empty(self): diff --git a/tests/staticfiles_tests/test_storage.py b/tests/staticfiles_tests/test_storage.py index 030b7dc6db..9ca4d62553 100644 --- a/tests/staticfiles_tests/test_storage.py +++ b/tests/staticfiles_tests/test_storage.py @@ -186,7 +186,9 @@ class TestHashedFiles: err = StringIO() with self.assertRaisesMessage(RuntimeError, "Max post-process passes exceeded"): call_command("collectstatic", interactive=False, verbosity=0, stderr=err) - self.assertEqual("Post-processing 'All' failed!\n\n", err.getvalue()) + self.assertEqual( + "Post-processing 'bar.css, foo.css' failed!\n\n", err.getvalue() + ) self.assertPostCondition() def test_post_processing(self): @@ -674,7 +676,7 @@ class TestCollectionJSModuleImportAggregationManifestStorage(CollectionTestCase) def test_module_import(self): relpath = self.hashed_file_path("cached/module.js") - self.assertEqual(relpath, "cached/module.55fd6938fbc5.js") + self.assertEqual(relpath, "cached/module.4326210cf0bd.js") tests = [ # Relative imports. b'import testConst from "./module_test.477bbebe77f0.js";', @@ -686,6 +688,11 @@ class TestCollectionJSModuleImportAggregationManifestStorage(CollectionTestCase) b'const dynamicModule = import("./module_test.477bbebe77f0.js");', # Creating a module object. b'import * as NewModule from "./module_test.477bbebe77f0.js";', + # Creating a minified module object. + b'import*as m from "./module_test.477bbebe77f0.js";', + b'import* as m from "./module_test.477bbebe77f0.js";', + b'import *as m from "./module_test.477bbebe77f0.js";', + b'import* as m from "./module_test.477bbebe77f0.js";', # Aliases. b'import { testConst as alias } from "./module_test.477bbebe77f0.js";', b"import {\n" @@ -701,7 +708,7 @@ class TestCollectionJSModuleImportAggregationManifestStorage(CollectionTestCase) def test_aggregating_modules(self): relpath = self.hashed_file_path("cached/module.js") - self.assertEqual(relpath, "cached/module.55fd6938fbc5.js") + self.assertEqual(relpath, "cached/module.4326210cf0bd.js") tests = [ b'export * from "./module_test.477bbebe77f0.js";', b'export { testConst } from "./module_test.477bbebe77f0.js";', diff --git a/tests/template_tests/filter_tests/test_floatformat.py b/tests/template_tests/filter_tests/test_floatformat.py index db17622309..6183f6a069 100644 --- a/tests/template_tests/filter_tests/test_floatformat.py +++ b/tests/template_tests/filter_tests/test_floatformat.py @@ -4,6 +4,7 @@ from django.template.defaultfilters import floatformat from django.test import SimpleTestCase from django.utils import translation from django.utils.safestring import mark_safe +from django.utils.version import PYPY from ..utils import setup @@ -60,12 +61,8 @@ class FunctionTests(SimpleTestCase): floatformat(Decimal("123456.123456789012345678901"), 21), "123456.123456789012345678901", ) - self.assertEqual(floatformat("foo"), "") self.assertEqual(floatformat(13.1031, "bar"), "13.1031") self.assertEqual(floatformat(18.125, 2), "18.13") - self.assertEqual(floatformat("foo", "bar"), "") - self.assertEqual(floatformat("¿Cómo esta usted?"), "") - self.assertEqual(floatformat(None), "") self.assertEqual( floatformat(-1.323297138040798e35, 2), "-132329713804079800000000000000000000.00", @@ -77,6 +74,46 @@ class FunctionTests(SimpleTestCase): self.assertEqual(floatformat(1.5e-15, 20), "0.00000000000000150000") self.assertEqual(floatformat(1.5e-15, -20), "0.00000000000000150000") self.assertEqual(floatformat(1.00000000000000015, 16), "1.0000000000000002") + self.assertEqual(floatformat("1e199"), "1" + "0" * 199) + + def test_invalid_inputs(self): + cases = [ + # Non-numeric strings. + None, + [], + {}, + object(), + "abc123", + "123abc", + "foo", + "error", + "¿Cómo esta usted?", + # Scientific notation - missing exponent value. + "1e", + "1e+", + "1e-", + # Scientific notation - missing base number. + "e400", + "e+400", + "e-400", + # Scientific notation - invalid exponent value. + "1e^2", + "1e2e3", + "1e2a", + "1e2.0", + "1e2,0", + # Scientific notation - misplaced decimal point. + "1e.2", + "1e2.", + # Scientific notation - misplaced '+' sign. + "1+e2", + "1e2+", + ] + for value in cases: + with self.subTest(value=value): + self.assertEqual(floatformat(value), "") + with self.subTest(value=value, arg="bar"): + self.assertEqual(floatformat(value, "bar"), "") def test_force_grouping(self): with translation.override("en"): @@ -134,6 +171,31 @@ class FunctionTests(SimpleTestCase): self.assertEqual(floatformat(pos_inf), "inf") self.assertEqual(floatformat(neg_inf), "-inf") self.assertEqual(floatformat(pos_inf / pos_inf), "nan") + self.assertEqual(floatformat("inf"), "inf") + self.assertEqual(floatformat("NaN"), "NaN") + + def test_too_many_digits_to_render(self): + cases = [ + "1e200", + "1E200", + "1E10000000000000000", + "-1E10000000000000000", + "1e10000000000000000", + "-1e10000000000000000", + ] + for value in cases: + with self.subTest(value=value): + self.assertEqual(floatformat(value), value) + + def test_too_many_digits_to_render_very_long(self): + value = "1" + "0" * 1_000_000 + if PYPY: + # PyPy casts decimal parts to int, which reaches the integer string + # conversion length limit (default 4300 digits, CVE-2020-10735). + with self.assertRaises(ValueError): + floatformat(value) + else: + self.assertEqual(floatformat(value), value) def test_float_dunder_method(self): class FloatWrapper: diff --git a/tests/template_tests/filter_tests/test_urlize.py b/tests/template_tests/filter_tests/test_urlize.py index c19103859e..546bd6c7d6 100644 --- a/tests/template_tests/filter_tests/test_urlize.py +++ b/tests/template_tests/filter_tests/test_urlize.py @@ -321,6 +321,11 @@ class FunctionTests(SimpleTestCase): '' "http://example.com?x=&;;", ) + self.assertEqual( + urlize("http://example.com?x=&.;...;", autoescape=False), + '' + "http://example.com?x=&.;...;", + ) def test_brackets(self): """ diff --git a/tests/template_tests/syntax_tests/test_basic.py b/tests/template_tests/syntax_tests/test_basic.py index 20bf30d55c..50e7a4c7b1 100644 --- a/tests/template_tests/syntax_tests/test_basic.py +++ b/tests/template_tests/syntax_tests/test_basic.py @@ -346,6 +346,52 @@ class BasicSyntaxTests(SimpleTestCase): output = self.engine.render_to_string("tpl-weird-percent") self.assertEqual(output, "% %s") + @setup( + {"template": "{{ class_var.class_property }} | {{ class_var.class_method }}"} + ) + def test_subscriptable_class(self): + class MyClass(list): + # As of Python 3.9 list defines __class_getitem__ which makes it + # subscriptable. + class_property = "Example property" + do_not_call_in_templates = True + + @classmethod + def class_method(cls): + return "Example method" + + for case in (MyClass, lambda: MyClass): + with self.subTest(case=case): + output = self.engine.render_to_string("template", {"class_var": case}) + self.assertEqual(output, "Example property | Example method") + + @setup({"template": "{{ meals.lunch }}"}) + def test_access_class_property_if_getitem_is_defined_in_metaclass(self): + """ + If the metaclass defines __getitem__, the template system should use + it to resolve the dot notation. + """ + + class MealMeta(type): + def __getitem__(cls, name): + return getattr(cls, name) + " is yummy." + + class Meals(metaclass=MealMeta): + lunch = "soup" + do_not_call_in_templates = True + + # Make class type subscriptable. + def __class_getitem__(cls, key): + from types import GenericAlias + + return GenericAlias(cls, key) + + self.assertEqual(Meals.lunch, "soup") + self.assertEqual(Meals["lunch"], "soup is yummy.") + + output = self.engine.render_to_string("template", {"meals": Meals}) + self.assertEqual(output, "soup is yummy.") + class BlockContextTests(SimpleTestCase): def test_repr(self): diff --git a/tests/template_tests/test_base.py b/tests/template_tests/test_base.py index 7b85d1de80..6457d4d4e6 100644 --- a/tests/template_tests/test_base.py +++ b/tests/template_tests/test_base.py @@ -8,7 +8,7 @@ class LexerTestMixin: template_string = ( "text\n" "{% if test %}{{ varvalue }}{% endif %}" - "{#comment {{not a var}} %{not a block}% #}" + "{#comment {{not a var}} {%not a block%} #}" "end text" ) expected_token_tuples = [ @@ -17,7 +17,7 @@ class LexerTestMixin: (TokenType.BLOCK, "if test", 2, (5, 18)), (TokenType.VAR, "varvalue", 2, (18, 32)), (TokenType.BLOCK, "endif", 2, (32, 43)), - (TokenType.COMMENT, "comment {{not a var}} %{not a block}%", 2, (43, 85)), + (TokenType.COMMENT, "comment {{not a var}} {%not a block%}", 2, (43, 85)), (TokenType.TEXT, "end text", 2, (85, 93)), ] diff --git a/tests/test_utils/fixtures/should_not_be_loaded.json b/tests/test_utils/fixtures/person.json similarity index 100% rename from tests/test_utils/fixtures/should_not_be_loaded.json rename to tests/test_utils/fixtures/person.json diff --git a/tests/test_utils/test_testcase.py b/tests/test_utils/test_testcase.py index efca01e29e..866e0dccc6 100644 --- a/tests/test_utils/test_testcase.py +++ b/tests/test_utils/test_testcase.py @@ -65,14 +65,12 @@ class TestTestCase(TestCase): @skipUnlessDBFeature("supports_transactions") def test_reset_sequences(self): - old_reset_sequences = self.reset_sequences - self.reset_sequences = True + old_reset_sequences = self.__class__.reset_sequences + self.__class__.reset_sequences = True + self.addCleanup(setattr, self.__class__, "reset_sequences", old_reset_sequences) msg = "reset_sequences cannot be used on TestCase instances" - try: - with self.assertRaisesMessage(TypeError, msg): - self._fixture_setup() - finally: - self.reset_sequences = old_reset_sequences + with self.assertRaisesMessage(TypeError, msg): + self._fixture_setup() def assert_no_queries(test): diff --git a/tests/test_utils/test_transactiontestcase.py b/tests/test_utils/test_transactiontestcase.py index 0032e2ee0c..12ef4c9a1c 100644 --- a/tests/test_utils/test_transactiontestcase.py +++ b/tests/test_utils/test_transactiontestcase.py @@ -4,7 +4,7 @@ from django.db import connections from django.test import TestCase, TransactionTestCase, override_settings from django.test.testcases import DatabaseOperationForbidden -from .models import Car +from .models import Car, Person class TestSerializedRollbackInhibitsPostMigrate(TransactionTestCase): @@ -68,3 +68,16 @@ class DisallowedDatabaseQueriesTests(TransactionTestCase): ) with self.assertRaisesMessage(DatabaseOperationForbidden, message): Car.objects.using("other").get() + + +class FixtureAvailableInSetUpClassTest(TransactionTestCase): + available_apps = ["test_utils"] + fixtures = ["person.json"] + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.elvis = Person.objects.get(name="Elvis Presley") + + def test_fixture_loaded_during_class_setup(self): + self.assertIsInstance(self.elvis, Person) diff --git a/tests/test_utils/tests.py b/tests/test_utils/tests.py index cd64c087c4..4fd9267429 100644 --- a/tests/test_utils/tests.py +++ b/tests/test_utils/tests.py @@ -1,6 +1,7 @@ import os import sys import threading +import traceback import unittest import warnings from io import StringIO @@ -1113,6 +1114,19 @@ class JSONEqualTests(SimpleTestCase): with self.assertRaises(AssertionError): self.assertJSONNotEqual(valid_json, invalid_json) + def test_method_frames_ignored_by_unittest(self): + try: + self.assertJSONEqual("1", "2") + except AssertionError: + exc_type, exc, tb = sys.exc_info() + + result = unittest.TestResult() + result.addFailure(self, (exc_type, exc, tb)) + stack = traceback.extract_tb(exc.__traceback__) + self.assertEqual(len(stack), 1) + # Top element in the stack is this method, not assertJSONEqual. + self.assertEqual(stack[-1].name, "test_method_frames_ignored_by_unittest") + class XMLEqualTests(SimpleTestCase): def test_simple_equal(self): @@ -1200,7 +1214,7 @@ class XMLEqualTests(SimpleTestCase): class SkippingExtraTests(TestCase): - fixtures = ["should_not_be_loaded.json"] + fixtures = ["person.json"] # HACK: This depends on internals of our TestCase subclasses def __call__(self, result=None): @@ -2172,6 +2186,8 @@ class AllowedDatabaseQueriesTests(SimpleTestCase): finally: new_connection.validate_thread_sharing() new_connection._close() + if hasattr(new_connection, "close_pool"): + new_connection.close_pool() class DatabaseAliasTests(SimpleTestCase): diff --git a/tests/update_only_fields/tests.py b/tests/update_only_fields/tests.py index 816112bc33..a6a5b7cb8e 100644 --- a/tests/update_only_fields/tests.py +++ b/tests/update_only_fields/tests.py @@ -262,10 +262,11 @@ class UpdateOnlyFieldsTests(TestCase): msg = "Passing positional arguments to save() is deprecated" with ( - self.assertWarnsMessage(RemovedInDjango60Warning, msg), + self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx, self.assertNumQueries(0), ): s.save(False, False, None, []) + self.assertEqual(ctx.filename, __file__) async def test_empty_update_fields_positional_asave(self): s = await Person.objects.acreate(name="Sara", gender="F") @@ -273,8 +274,9 @@ class UpdateOnlyFieldsTests(TestCase): s.name = "Other" msg = "Passing positional arguments to asave() is deprecated" - with self.assertWarnsMessage(RemovedInDjango60Warning, msg): + with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx: await s.asave(False, False, None, []) + self.assertEqual(ctx.filename, __file__) # No save occurred for an empty update_fields. await s.arefresh_from_db() diff --git a/tests/urlpatterns/tests.py b/tests/urlpatterns/tests.py index 78b71fe325..6c8d6470c0 100644 --- a/tests/urlpatterns/tests.py +++ b/tests/urlpatterns/tests.py @@ -212,10 +212,11 @@ class SimplifiedURLTests(SimpleTestCase): "converters is deprecated and will be removed in Django 6.0." ) try: - with self.assertWarnsMessage(RemovedInDjango60Warning, msg): + with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx: register_converter(IntConverter, "int") finally: REGISTERED_CONVERTERS.pop("int", None) + self.assertEqual(ctx.filename, __file__) def test_warning_override_converter(self): # RemovedInDjango60Warning: when the deprecation ends, replace with @@ -226,11 +227,12 @@ class SimplifiedURLTests(SimpleTestCase): "registered converters is deprecated and will be removed in Django 6.0." ) try: - with self.assertWarnsMessage(RemovedInDjango60Warning, msg): + with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx: register_converter(Base64Converter, "base64") register_converter(Base64Converter, "base64") finally: REGISTERED_CONVERTERS.pop("base64", None) + self.assertEqual(ctx.filename, __file__) def test_invalid_view(self): msg = "view must be a callable or a list/tuple in the case of include()." diff --git a/tests/utils_tests/test_html.py b/tests/utils_tests/test_html.py index 9fe782ed2f..b47919df99 100644 --- a/tests/utils_tests/test_html.py +++ b/tests/utils_tests/test_html.py @@ -10,6 +10,7 @@ from django.utils.html import ( escape, escapejs, format_html, + format_html_join, html_safe, json_script, linebreaks, @@ -70,10 +71,31 @@ class TestUtilsHtml(SimpleTestCase): msg = "Calling format_html() without passing args or kwargs is deprecated." # RemovedInDjango60Warning: when the deprecation ends, replace with: # msg = "args or kwargs must be provided." - # with self.assertRaisesMessage(ValueError, msg): - with self.assertWarnsMessage(RemovedInDjango60Warning, msg): + # with self.assertRaisesMessage(TypeError, msg): + with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx: name = "Adam" self.assertEqual(format_html(f"{name}"), "Adam") + self.assertEqual(ctx.filename, __file__) + + def test_format_html_join_with_positional_arguments(self): + self.assertEqual( + format_html_join( + "\n", + "
  • {}) {}
  • ", + [(1, "Emma"), (2, "Matilda")], + ), + "
  • 1) Emma
  • \n
  • 2) Matilda
  • ", + ) + + def test_format_html_join_with_keyword_arguments(self): + self.assertEqual( + format_html_join( + "\n", + "
  • {id}) {text}
  • ", + [{"id": 1, "text": "Emma"}, {"id": 2, "text": "Matilda"}], + ), + "
  • 1) Emma
  • \n
  • 2) Matilda
  • ", + ) def test_linebreaks(self): items = ( @@ -338,6 +360,15 @@ class TestUtilsHtml(SimpleTestCase): 'Search for google.com/?q=!', ), ("foo@example.com", 'foo@example.com'), + ( + "test@" + "한.글." * 15 + "aaa", + '' + + "test@" + + "한.글." * 15 + + "aaa", + ), ) for value, output in tests: with self.subTest(value=value): @@ -346,6 +377,10 @@ class TestUtilsHtml(SimpleTestCase): def test_urlize_unchanged_inputs(self): tests = ( ("a" + "@a" * 50000) + "a", # simple_email_re catastrophic test + # Unicode domain catastrophic tests. + "a@" + "한.글." * 1_000_000 + "a", + "http://" + "한.글." * 1_000_000 + "com", + "www." + "한.글." * 1_000_000 + "com", ("a" + "." * 1000000) + "a", # trailing_punctuation catastrophic test "foo@", "@foo.com", @@ -359,6 +394,9 @@ class TestUtilsHtml(SimpleTestCase): "[(" * 100_000 + ":" + ")]" * 100_000, "([[" * 100_000 + ":" + "]])" * 100_000, "&:" + ";" * 100_000, + "&.;" * 100_000, + ".;" * 100_000, + "&" + ";:" * 100_000, ) for value in tests: with self.subTest(value=value): diff --git a/tests/utils_tests/test_itercompat.py b/tests/utils_tests/test_itercompat.py index e6ea278ab4..a95867c621 100644 --- a/tests/utils_tests/test_itercompat.py +++ b/tests/utils_tests/test_itercompat.py @@ -11,5 +11,6 @@ class TestIterCompat(SimpleTestCase): "django.utils.itercompat.is_iterable() is deprecated. " "Use isinstance(..., collections.abc.Iterable) instead." ) - with self.assertWarnsMessage(RemovedInDjango60Warning, msg): + with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx: is_iterable([]) + self.assertEqual(ctx.filename, __file__) diff --git a/tests/utils_tests/test_jslex.py b/tests/utils_tests/test_jslex.py deleted file mode 100644 index 59551930c6..0000000000 --- a/tests/utils_tests/test_jslex.py +++ /dev/null @@ -1,401 +0,0 @@ -"""Tests for jslex.""" - -# originally from https://bitbucket.org/ned/jslex - -from django.test import SimpleTestCase -from django.utils.jslex import JsLexer, prepare_js_for_gettext - - -class JsTokensTest(SimpleTestCase): - LEX_CASES = [ - # ids - ("a ABC $ _ a123", ["id a", "id ABC", "id $", "id _", "id a123"]), - ( - "\\u1234 abc\\u0020 \\u0065_\\u0067", - ["id \\u1234", "id abc\\u0020", "id \\u0065_\\u0067"], - ), - # numbers - ( - "123 1.234 0.123e-3 0 1E+40 1e1 .123", - [ - "dnum 123", - "dnum 1.234", - "dnum 0.123e-3", - "dnum 0", - "dnum 1E+40", - "dnum 1e1", - "dnum .123", - ], - ), - ("0x1 0xabCD 0XABcd", ["hnum 0x1", "hnum 0xabCD", "hnum 0XABcd"]), - ("010 0377 090", ["onum 010", "onum 0377", "dnum 0", "dnum 90"]), - ("0xa123ghi", ["hnum 0xa123", "id ghi"]), - # keywords - ( - "function Function FUNCTION", - ["keyword function", "id Function", "id FUNCTION"], - ), - ( - "const constructor in inherits", - ["keyword const", "id constructor", "keyword in", "id inherits"], - ), - ("true true_enough", ["reserved true", "id true_enough"]), - # strings - (""" 'hello' "hello" """, ["string 'hello'", 'string "hello"']), - ( - r""" 'don\'t' "don\"t" '"' "'" '\'' "\"" """, - [ - r"""string 'don\'t'""", - r'''string "don\"t"''', - r"""string '"'""", - r'''string "'"''', - r"""string '\''""", - r'''string "\""''', - ], - ), - (r'"ƃuıxǝ⅂ ʇdıɹɔsɐʌɐſ\""', [r'string "ƃuıxǝ⅂ ʇdıɹɔsɐʌɐſ\""']), - # comments - ("a//b", ["id a", "linecomment //b"]), - ( - "/****/a/=2//hello", - ["comment /****/", "id a", "punct /=", "dnum 2", "linecomment //hello"], - ), - ( - "/*\n * Header\n */\na=1;", - ["comment /*\n * Header\n */", "id a", "punct =", "dnum 1", "punct ;"], - ), - # punctuation - ("a+++b", ["id a", "punct ++", "punct +", "id b"]), - # regex - (r"a=/a*/,1", ["id a", "punct =", "regex /a*/", "punct ,", "dnum 1"]), - (r"a=/a*[^/]+/,1", ["id a", "punct =", "regex /a*[^/]+/", "punct ,", "dnum 1"]), - (r"a=/a*\[^/,1", ["id a", "punct =", r"regex /a*\[^/", "punct ,", "dnum 1"]), - (r"a=/\//,1", ["id a", "punct =", r"regex /\//", "punct ,", "dnum 1"]), - # next two are from https://www-archive.mozilla.org/js/language/js20-2002-04/rationale/syntax.html#regular-expressions # NOQA - ( - 'for (var x = a in foo && "" || mot ? z:/x:3;x<5;y"', - "punct ||", - "id mot", - "punct ?", - "id z", - "punct :", - "regex /x:3;x<5;y" || mot ? z/x:3;x<5;y"', - "punct ||", - "id mot", - "punct ?", - "id z", - "punct /", - "id x", - "punct :", - "dnum 3", - "punct ;", - "id x", - "punct <", - "dnum 5", - "punct ;", - "id y", - "punct <", - "regex /g/i", - "punct )", - "punct {", - "id xyz", - "punct (", - "id x", - "punct ++", - "punct )", - "punct ;", - "punct }", - ], - ), - # Various "illegal" regexes that are valid according to the std. - ( - r"""/????/, /++++/, /[----]/ """, - ["regex /????/", "punct ,", "regex /++++/", "punct ,", "regex /[----]/"], - ), - # Stress cases from https://stackoverflow.com/questions/5533925/what-javascript-constructs-does-jslex-incorrectly-lex/5573409#5573409 # NOQA - (r"""/\[/""", [r"""regex /\[/"""]), - (r"""/[i]/""", [r"""regex /[i]/"""]), - (r"""/[\]]/""", [r"""regex /[\]]/"""]), - (r"""/a[\]]/""", [r"""regex /a[\]]/"""]), - (r"""/a[\]]b/""", [r"""regex /a[\]]b/"""]), - (r"""/[\]/]/gi""", [r"""regex /[\]/]/gi"""]), - (r"""/\[[^\]]+\]/gi""", [r"""regex /\[[^\]]+\]/gi"""]), - ( - r""" - rexl.re = { - NAME: /^(?![0-9])(?:\w)+|^"(?:[^"]|"")+"/, - UNQUOTED_LITERAL: /^@(?:(?![0-9])(?:\w|\:)+|^"(?:[^"]|"")+")\[[^\]]+\]/, - QUOTED_LITERAL: /^'(?:[^']|'')*'/, - NUMERIC_LITERAL: /^[0-9]+(?:\.[0-9]*(?:[eE][-+][0-9]+)?)?/, - SYMBOL: /^(?:==|=|<>|<=|<|>=|>|!~~|!~|~~|~|!==|!=|!~=|!~|!|&|\||\.|\:|,|\(|\)|\[|\]|\{|\}|\?|\:|;|@|\^|\/\+|\/|\*|\+|-)/ - }; - """, # NOQA - [ - "id rexl", - "punct .", - "id re", - "punct =", - "punct {", - "id NAME", - "punct :", - r"""regex /^(?![0-9])(?:\w)+|^"(?:[^"]|"")+"/""", - "punct ,", - "id UNQUOTED_LITERAL", - "punct :", - r"""regex /^@(?:(?![0-9])(?:\w|\:)+|^"(?:[^"]|"")+")\[[^\]]+\]/""", - "punct ,", - "id QUOTED_LITERAL", - "punct :", - r"""regex /^'(?:[^']|'')*'/""", - "punct ,", - "id NUMERIC_LITERAL", - "punct :", - r"""regex /^[0-9]+(?:\.[0-9]*(?:[eE][-+][0-9]+)?)?/""", - "punct ,", - "id SYMBOL", - "punct :", - r"""regex /^(?:==|=|<>|<=|<|>=|>|!~~|!~|~~|~|!==|!=|!~=|!~|!|&|\||\.|\:|,|\(|\)|\[|\]|\{|\}|\?|\:|;|@|\^|\/\+|\/|\*|\+|-)/""", # NOQA - "punct }", - "punct ;", - ], - ), - ( - r""" - rexl.re = { - NAME: /^(?![0-9])(?:\w)+|^"(?:[^"]|"")+"/, - UNQUOTED_LITERAL: /^@(?:(?![0-9])(?:\w|\:)+|^"(?:[^"]|"")+")\[[^\]]+\]/, - QUOTED_LITERAL: /^'(?:[^']|'')*'/, - NUMERIC_LITERAL: /^[0-9]+(?:\.[0-9]*(?:[eE][-+][0-9]+)?)?/, - SYMBOL: /^(?:==|=|<>|<=|<|>=|>|!~~|!~|~~|~|!==|!=|!~=|!~|!|&|\||\.|\:|,|\(|\)|\[|\]|\{|\}|\?|\:|;|@|\^|\/\+|\/|\*|\+|-)/ - }; - str = '"'; - """, # NOQA - [ - "id rexl", - "punct .", - "id re", - "punct =", - "punct {", - "id NAME", - "punct :", - r"""regex /^(?![0-9])(?:\w)+|^"(?:[^"]|"")+"/""", - "punct ,", - "id UNQUOTED_LITERAL", - "punct :", - r"""regex /^@(?:(?![0-9])(?:\w|\:)+|^"(?:[^"]|"")+")\[[^\]]+\]/""", - "punct ,", - "id QUOTED_LITERAL", - "punct :", - r"""regex /^'(?:[^']|'')*'/""", - "punct ,", - "id NUMERIC_LITERAL", - "punct :", - r"""regex /^[0-9]+(?:\.[0-9]*(?:[eE][-+][0-9]+)?)?/""", - "punct ,", - "id SYMBOL", - "punct :", - r"""regex /^(?:==|=|<>|<=|<|>=|>|!~~|!~|~~|~|!==|!=|!~=|!~|!|&|\||\.|\:|,|\(|\)|\[|\]|\{|\}|\?|\:|;|@|\^|\/\+|\/|\*|\+|-)/""", # NOQA - "punct }", - "punct ;", - "id str", - "punct =", - """string '"'""", - "punct ;", - ], - ), - ( - r' this._js = "e.str(\"" + this.value.replace(/\\/g, "\\\\")' - r'.replace(/"/g, "\\\"") + "\")"; ', - [ - "keyword this", - "punct .", - "id _js", - "punct =", - r'''string "e.str(\""''', - "punct +", - "keyword this", - "punct .", - "id value", - "punct .", - "id replace", - "punct (", - r"regex /\\/g", - "punct ,", - r'string "\\\\"', - "punct )", - "punct .", - "id replace", - "punct (", - r'regex /"/g', - "punct ,", - r'string "\\\""', - "punct )", - "punct +", - r'string "\")"', - "punct ;", - ], - ), - ] - - -def make_function(input, toks): - def test_func(self): - lexer = JsLexer() - result = [ - "%s %s" % (name, tok) for name, tok in lexer.lex(input) if name != "ws" - ] - self.assertEqual(result, toks) - - return test_func - - -for i, (input, toks) in enumerate(JsTokensTest.LEX_CASES): - setattr(JsTokensTest, "test_case_%d" % i, make_function(input, toks)) - - -GETTEXT_CASES = ( - ( - r""" - a = 1; /* /[0-9]+/ */ - b = 0x2a0b / 1; // /[0-9]+/ - c = 3; - """, - r""" - a = 1; /* /[0-9]+/ */ - b = 0x2a0b / 1; // /[0-9]+/ - c = 3; - """, - ), - ( - r""" - a = 1.234e-5; - /* - * /[0-9+/ - */ - b = .0123; - """, - r""" - a = 1.234e-5; - /* - * /[0-9+/ - */ - b = .0123; - """, - ), - ( - r""" - x = y / z; - alert(gettext("hello")); - x /= 3; - """, - r""" - x = y / z; - alert(gettext("hello")); - x /= 3; - """, - ), - ( - r""" - s = "Hello \"th/foo/ere\""; - s = 'He\x23llo \'th/foo/ere\''; - s = 'slash quote \", just quote "'; - """, - r""" - s = "Hello \"th/foo/ere\""; - s = "He\x23llo \'th/foo/ere\'"; - s = "slash quote \", just quote \""; - """, - ), - ( - r""" - s = "Line continuation\ - continued /hello/ still the string";/hello/; - """, - r""" - s = "Line continuation\ - continued /hello/ still the string";"REGEX"; - """, - ), - ( - r""" - var regex = /pattern/; - var regex2 = /matter/gm; - var regex3 = /[*/]+/gm.foo("hey"); - """, - r""" - var regex = "REGEX"; - var regex2 = "REGEX"; - var regex3 = "REGEX".foo("hey"); - """, - ), - ( - r""" - for (var x = a in foo && "" || mot ? z:/x:3;x<5;y" || mot ? z/x:3;x<5;y" || mot ? z:"REGEX"/i) {xyz(x++);} - for (var x = a in foo && "" || mot ? z/x:3;x<5;y<"REGEX") {xyz(x++);} - """, - ), - ( - """ - \\u1234xyz = gettext('Hello there'); - """, - r""" - Uu1234xyz = gettext("Hello there"); - """, - ), -) - - -class JsToCForGettextTest(SimpleTestCase): - pass - - -def make_function(js, c): - def test_func(self): - self.assertEqual(prepare_js_for_gettext(js), c) - - return test_func - - -for i, pair in enumerate(GETTEXT_CASES): - setattr(JsToCForGettextTest, "test_case_%d" % i, make_function(*pair)) diff --git a/tests/utils_tests/test_safestring.py b/tests/utils_tests/test_safestring.py index 1a79afbf48..2ae8e57b19 100644 --- a/tests/utils_tests/test_safestring.py +++ b/tests/utils_tests/test_safestring.py @@ -121,3 +121,65 @@ class SafeStringTest(SimpleTestCase): msg = "object has no attribute 'dynamic_attr'" with self.assertRaisesMessage(AttributeError, msg): s.dynamic_attr = True + + def test_add_str(self): + s = SafeString("a&b") + cases = [ + ("test", "a&btest"), + ("

    unsafe

    ", "a&b<p>unsafe</p>"), + (SafeString("

    safe

    "), SafeString("a&b

    safe

    ")), + ] + for case, expected in cases: + with self.subTest(case=case): + self.assertRenderEqual("{{ s }}", expected, s=s + case) + + def test_add_obj(self): + + base_str = "strange" + add_str = "hello
    " + + class Add: + def __add__(self, other): + return base_str + other + + class AddSafe: + def __add__(self, other): + return mark_safe(base_str) + other + + class Radd: + def __radd__(self, other): + return other + base_str + + class RaddSafe: + def __radd__(self, other): + return other + mark_safe(base_str) + + left_add_expected = f"{base_str}{add_str}" + right_add_expected = f"{add_str}{base_str}" + cases = [ + # Left-add test cases. + (Add(), add_str, left_add_expected, str), + (Add(), mark_safe(add_str), left_add_expected, str), + (AddSafe(), add_str, left_add_expected, str), + (AddSafe(), mark_safe(add_str), left_add_expected, SafeString), + # Right-add test cases. + (add_str, Radd(), right_add_expected, str), + (mark_safe(add_str), Radd(), right_add_expected, str), + (add_str, Radd(), right_add_expected, str), + (mark_safe(add_str), RaddSafe(), right_add_expected, SafeString), + ] + for lhs, rhs, expected, expected_type in cases: + with self.subTest(lhs=lhs, rhs=rhs): + result = lhs + rhs + self.assertEqual(result, expected) + self.assertEqual(type(result), expected_type) + + cases = [ + ("hello", Add()), + ("hello", AddSafe()), + (Radd(), "hello"), + (RaddSafe(), "hello"), + ] + for lhs, rhs in cases: + with self.subTest(lhs=lhs, rhs=rhs), self.assertRaises(TypeError): + lhs + rhs diff --git a/tests/validation/models.py b/tests/validation/models.py index 653be4a239..ed88750364 100644 --- a/tests/validation/models.py +++ b/tests/validation/models.py @@ -48,7 +48,7 @@ class ModelToValidate(models.Model): class UniqueFieldsModel(models.Model): unique_charfield = models.CharField(max_length=100, unique=True) - unique_integerfield = models.IntegerField(unique=True) + unique_integerfield = models.IntegerField(unique=True, db_default=42) non_unique_field = models.IntegerField() diff --git a/tests/validation/test_unique.py b/tests/validation/test_unique.py index 4a8b3894f0..36ee6e9da0 100644 --- a/tests/validation/test_unique.py +++ b/tests/validation/test_unique.py @@ -146,6 +146,20 @@ class PerformUniqueChecksTest(TestCase): mtv = ModelToValidate(number=10, name="Some Name") mtv.full_clean() + def test_unique_db_default(self): + UniqueFieldsModel.objects.create(unique_charfield="foo", non_unique_field=42) + um = UniqueFieldsModel(unique_charfield="bar", non_unique_field=42) + with self.assertRaises(ValidationError) as cm: + um.full_clean() + self.assertEqual( + cm.exception.message_dict, + { + "unique_integerfield": [ + "Unique fields model with this Unique integerfield already exists." + ] + }, + ) + def test_unique_for_date(self): Post.objects.create( title="Django 1.0 is released", diff --git a/tests/view_tests/tests/test_debug.py b/tests/view_tests/tests/test_debug.py index 9383c0d873..c65514a170 100644 --- a/tests/view_tests/tests/test_debug.py +++ b/tests/view_tests/tests/test_debug.py @@ -398,6 +398,15 @@ class DebugViewTests(SimpleTestCase): response, "

    The install worked successfully! Congratulations!

    " ) + @override_settings( + ROOT_URLCONF="view_tests.default_urls", FORCE_SCRIPT_NAME="/FORCED_PREFIX" + ) + def test_default_urlconf_script_name(self): + response = self.client.request(**{"path": "/FORCED_PREFIX/"}) + self.assertContains( + response, "

    The install worked successfully! Congratulations!

    " + ) + @override_settings(ROOT_URLCONF="view_tests.regression_21530_urls") def test_regression_21530(self): """ @@ -1552,6 +1561,14 @@ class ExceptionReporterFilterTests( """ rf = RequestFactory() + sensitive_settings = [ + "SECRET_KEY", + "SECRET_KEY_FALLBACKS", + "PASSWORD", + "API_KEY", + "SOME_TOKEN", + "MY_AUTH", + ] def test_non_sensitive_request(self): """ @@ -1774,42 +1791,30 @@ class ExceptionReporterFilterTests( The debug page should not show some sensitive settings (password, secret key, ...). """ - sensitive_settings = [ - "SECRET_KEY", - "SECRET_KEY_FALLBACKS", - "PASSWORD", - "API_KEY", - "AUTH_TOKEN", - ] - for setting in sensitive_settings: - with self.settings(DEBUG=True, **{setting: "should not be displayed"}): - response = self.client.get("/raises500/") - self.assertNotContains( - response, "should not be displayed", status_code=500 - ) + for setting in self.sensitive_settings: + with self.subTest(setting=setting): + with self.settings(DEBUG=True, **{setting: "should not be displayed"}): + response = self.client.get("/raises500/") + self.assertNotContains( + response, "should not be displayed", status_code=500 + ) def test_settings_with_sensitive_keys(self): """ The debug page should filter out some sensitive information found in dict settings. """ - sensitive_settings = [ - "SECRET_KEY", - "SECRET_KEY_FALLBACKS", - "PASSWORD", - "API_KEY", - "AUTH_TOKEN", - ] - for setting in sensitive_settings: + for setting in self.sensitive_settings: FOOBAR = { setting: "should not be displayed", "recursive": {setting: "should not be displayed"}, } - with self.settings(DEBUG=True, FOOBAR=FOOBAR): - response = self.client.get("/raises500/") - self.assertNotContains( - response, "should not be displayed", status_code=500 - ) + with self.subTest(setting=setting): + with self.settings(DEBUG=True, FOOBAR=FOOBAR): + response = self.client.get("/raises500/") + self.assertNotContains( + response, "should not be displayed", status_code=500 + ) def test_cleanse_setting_basic(self): reporter_filter = SafeExceptionReporterFilter() @@ -1883,10 +1888,26 @@ class ExceptionReporterFilterTests( ) def test_request_meta_filtering(self): - request = self.rf.get("/", headers={"secret-header": "super_secret"}) + headers = { + "API_URL": "super secret", + "A_SIGNATURE_VALUE": "super secret", + "MY_KEY": "super secret", + "PASSWORD": "super secret", + "SECRET_VALUE": "super secret", + "SOME_TOKEN": "super secret", + "THE_AUTH": "super secret", + } + request = self.rf.get("/", headers=headers) reporter_filter = SafeExceptionReporterFilter() + cleansed_headers = reporter_filter.get_safe_request_meta(request) + for header in headers: + with self.subTest(header=header): + self.assertEqual( + cleansed_headers[f"HTTP_{header}"], + reporter_filter.cleansed_substitute, + ) self.assertEqual( - reporter_filter.get_safe_request_meta(request)["HTTP_SECRET_HEADER"], + cleansed_headers["HTTP_COOKIE"], reporter_filter.cleansed_substitute, ) @@ -1910,9 +1931,7 @@ class ExceptionReporterFilterTests( class CustomExceptionReporterFilter(SafeExceptionReporterFilter): cleansed_substitute = "XXXXXXXXXXXXXXXXXXXX" - hidden_settings = _lazy_re_compile( - "API|TOKEN|KEY|SECRET|PASS|SIGNATURE|DATABASE_URL", flags=re.I - ) + hidden_settings = _lazy_re_compile("PASS|DATABASE", flags=re.I) @override_settings( 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